@atproto/bsky 0.0.64 → 0.0.65

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 (194) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/api/app/bsky/actor/searchActors.js +1 -1
  3. package/dist/api/app/bsky/actor/searchActors.js.map +1 -1
  4. package/dist/api/app/bsky/feed/getPostThread.js +1 -10
  5. package/dist/api/app/bsky/feed/getPostThread.js.map +1 -1
  6. package/dist/api/app/bsky/feed/searchPosts.js +1 -1
  7. package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
  8. package/dist/api/app/bsky/graph/getActorStarterPacks.d.ts +4 -0
  9. package/dist/api/app/bsky/graph/getActorStarterPacks.d.ts.map +1 -0
  10. package/dist/api/app/bsky/graph/getActorStarterPacks.js +58 -0
  11. package/dist/api/app/bsky/graph/getActorStarterPacks.js.map +1 -0
  12. package/dist/api/app/bsky/graph/getStarterPack.d.ts +4 -0
  13. package/dist/api/app/bsky/graph/getStarterPack.d.ts.map +1 -0
  14. package/dist/api/app/bsky/graph/getStarterPack.js +45 -0
  15. package/dist/api/app/bsky/graph/getStarterPack.js.map +1 -0
  16. package/dist/api/app/bsky/graph/getStarterPacks.d.ts +4 -0
  17. package/dist/api/app/bsky/graph/getStarterPacks.d.ts.map +1 -0
  18. package/dist/api/app/bsky/graph/getStarterPacks.js +40 -0
  19. package/dist/api/app/bsky/graph/getStarterPacks.js.map +1 -0
  20. package/dist/api/index.d.ts.map +1 -1
  21. package/dist/api/index.js +6 -0
  22. package/dist/api/index.js.map +1 -1
  23. package/dist/data-plane/server/db/database-schema.d.ts +2 -1
  24. package/dist/data-plane/server/db/database-schema.d.ts.map +1 -1
  25. package/dist/data-plane/server/db/migrations/20240606T222548219Z-starter-packs.d.ts +4 -0
  26. package/dist/data-plane/server/db/migrations/20240606T222548219Z-starter-packs.d.ts.map +1 -0
  27. package/dist/data-plane/server/db/migrations/20240606T222548219Z-starter-packs.js +47 -0
  28. package/dist/data-plane/server/db/migrations/20240606T222548219Z-starter-packs.js.map +1 -0
  29. package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
  30. package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
  31. package/dist/data-plane/server/db/migrations/index.js +2 -1
  32. package/dist/data-plane/server/db/migrations/index.js.map +1 -1
  33. package/dist/data-plane/server/db/tables/profile.d.ts +2 -0
  34. package/dist/data-plane/server/db/tables/profile.d.ts.map +1 -1
  35. package/dist/data-plane/server/db/tables/starter-pack.d.ts +14 -0
  36. package/dist/data-plane/server/db/tables/starter-pack.d.ts.map +1 -0
  37. package/dist/data-plane/server/db/tables/starter-pack.js +5 -0
  38. package/dist/data-plane/server/db/tables/starter-pack.js.map +1 -0
  39. package/dist/data-plane/server/indexing/index.d.ts +2 -0
  40. package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
  41. package/dist/data-plane/server/indexing/index.js +2 -0
  42. package/dist/data-plane/server/indexing/index.js.map +1 -1
  43. package/dist/data-plane/server/indexing/plugins/profile.d.ts.map +1 -1
  44. package/dist/data-plane/server/indexing/plugins/profile.js +18 -2
  45. package/dist/data-plane/server/indexing/plugins/profile.js.map +1 -1
  46. package/dist/data-plane/server/indexing/plugins/starter-pack.d.ts +11 -0
  47. package/dist/data-plane/server/indexing/plugins/starter-pack.d.ts.map +1 -0
  48. package/dist/data-plane/server/indexing/plugins/starter-pack.js +78 -0
  49. package/dist/data-plane/server/indexing/plugins/starter-pack.js.map +1 -0
  50. package/dist/data-plane/server/routes/index.d.ts.map +1 -1
  51. package/dist/data-plane/server/routes/index.js +2 -0
  52. package/dist/data-plane/server/routes/index.js.map +1 -1
  53. package/dist/data-plane/server/routes/interactions.d.ts.map +1 -1
  54. package/dist/data-plane/server/routes/interactions.js +57 -1
  55. package/dist/data-plane/server/routes/interactions.js.map +1 -1
  56. package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
  57. package/dist/data-plane/server/routes/profile.js +1 -0
  58. package/dist/data-plane/server/routes/profile.js.map +1 -1
  59. package/dist/data-plane/server/routes/records.d.ts.map +1 -1
  60. package/dist/data-plane/server/routes/records.js +1 -0
  61. package/dist/data-plane/server/routes/records.js.map +1 -1
  62. package/dist/data-plane/server/routes/starter-packs.d.ts +6 -0
  63. package/dist/data-plane/server/routes/starter-packs.d.ts.map +1 -0
  64. package/dist/data-plane/server/routes/starter-packs.js +25 -0
  65. package/dist/data-plane/server/routes/starter-packs.js.map +1 -0
  66. package/dist/data-plane/server/util.d.ts +6 -6
  67. package/dist/hydration/actor.d.ts +2 -0
  68. package/dist/hydration/actor.d.ts.map +1 -1
  69. package/dist/hydration/actor.js +2 -0
  70. package/dist/hydration/actor.js.map +1 -1
  71. package/dist/hydration/feed.d.ts +1 -5
  72. package/dist/hydration/feed.d.ts.map +1 -1
  73. package/dist/hydration/feed.js +0 -1
  74. package/dist/hydration/feed.js.map +1 -1
  75. package/dist/hydration/graph.d.ts +17 -1
  76. package/dist/hydration/graph.d.ts.map +1 -1
  77. package/dist/hydration/graph.js +30 -1
  78. package/dist/hydration/graph.js.map +1 -1
  79. package/dist/hydration/hydrator.d.ts +9 -3
  80. package/dist/hydration/hydrator.d.ts.map +1 -1
  81. package/dist/hydration/hydrator.js +114 -5
  82. package/dist/hydration/hydrator.js.map +1 -1
  83. package/dist/hydration/util.d.ts +4 -0
  84. package/dist/hydration/util.d.ts.map +1 -1
  85. package/dist/hydration/util.js.map +1 -1
  86. package/dist/lexicon/index.d.ts +7 -0
  87. package/dist/lexicon/index.d.ts.map +1 -1
  88. package/dist/lexicon/index.js +13 -0
  89. package/dist/lexicon/index.js.map +1 -1
  90. package/dist/lexicon/lexicons.d.ts +332 -0
  91. package/dist/lexicon/lexicons.d.ts.map +1 -1
  92. package/dist/lexicon/lexicons.js +335 -1
  93. package/dist/lexicon/lexicons.js.map +1 -1
  94. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +5 -0
  95. package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
  96. package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
  97. package/dist/lexicon/types/app/bsky/actor/profile.d.ts +3 -0
  98. package/dist/lexicon/types/app/bsky/actor/profile.d.ts.map +1 -1
  99. package/dist/lexicon/types/app/bsky/actor/profile.js.map +1 -1
  100. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +36 -1
  101. package/dist/lexicon/types/app/bsky/graph/defs.d.ts.map +1 -1
  102. package/dist/lexicon/types/app/bsky/graph/defs.js +23 -1
  103. package/dist/lexicon/types/app/bsky/graph/defs.js.map +1 -1
  104. package/dist/lexicon/types/app/bsky/graph/getActorStarterPacks.d.ts +39 -0
  105. package/dist/lexicon/types/app/bsky/graph/getActorStarterPacks.d.ts.map +1 -0
  106. package/dist/lexicon/types/app/bsky/graph/getActorStarterPacks.js +3 -0
  107. package/dist/lexicon/types/app/bsky/graph/getActorStarterPacks.js.map +1 -0
  108. package/dist/lexicon/types/app/bsky/graph/getStarterPack.d.ts +37 -0
  109. package/dist/lexicon/types/app/bsky/graph/getStarterPack.d.ts.map +1 -0
  110. package/dist/lexicon/types/app/bsky/graph/getStarterPack.js +3 -0
  111. package/dist/lexicon/types/app/bsky/graph/getStarterPack.js.map +1 -0
  112. package/dist/lexicon/types/app/bsky/graph/getStarterPacks.d.ts +36 -0
  113. package/dist/lexicon/types/app/bsky/graph/getStarterPacks.d.ts.map +1 -0
  114. package/dist/lexicon/types/app/bsky/graph/getStarterPacks.js +3 -0
  115. package/dist/lexicon/types/app/bsky/graph/getStarterPacks.js.map +1 -0
  116. package/dist/lexicon/types/app/bsky/graph/starterpack.d.ts +25 -0
  117. package/dist/lexicon/types/app/bsky/graph/starterpack.d.ts.map +1 -0
  118. package/dist/lexicon/types/app/bsky/graph/starterpack.js +27 -0
  119. package/dist/lexicon/types/app/bsky/graph/starterpack.js.map +1 -0
  120. package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts +2 -2
  121. package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts.map +1 -1
  122. package/dist/lexicon/types/app/bsky/notification/listNotifications.js.map +1 -1
  123. package/dist/proto/bsky_connect.d.ts +39 -1
  124. package/dist/proto/bsky_connect.d.ts.map +1 -1
  125. package/dist/proto/bsky_connect.js +38 -0
  126. package/dist/proto/bsky_connect.js.map +1 -1
  127. package/dist/proto/bsky_pb.d.ts +160 -0
  128. package/dist/proto/bsky_pb.d.ts.map +1 -1
  129. package/dist/proto/bsky_pb.js +508 -4
  130. package/dist/proto/bsky_pb.js.map +1 -1
  131. package/dist/views/index.d.ts +3 -1
  132. package/dist/views/index.d.ts.map +1 -1
  133. package/dist/views/index.js +64 -0
  134. package/dist/views/index.js.map +1 -1
  135. package/package.json +5 -5
  136. package/proto/bsky.proto +47 -0
  137. package/src/api/app/bsky/actor/searchActors.ts +1 -1
  138. package/src/api/app/bsky/feed/getPostThread.ts +1 -9
  139. package/src/api/app/bsky/feed/searchPosts.ts +1 -1
  140. package/src/api/app/bsky/graph/getActorStarterPacks.ts +99 -0
  141. package/src/api/app/bsky/graph/getStarterPack.ts +80 -0
  142. package/src/api/app/bsky/graph/getStarterPacks.ts +81 -0
  143. package/src/api/index.ts +6 -0
  144. package/src/data-plane/server/db/database-schema.ts +2 -0
  145. package/src/data-plane/server/db/migrations/20240606T222548219Z-starter-packs.ts +45 -0
  146. package/src/data-plane/server/db/migrations/index.ts +1 -0
  147. package/src/data-plane/server/db/tables/profile.ts +2 -0
  148. package/src/data-plane/server/db/tables/starter-pack.ts +16 -0
  149. package/src/data-plane/server/indexing/index.ts +3 -0
  150. package/src/data-plane/server/indexing/plugins/profile.ts +16 -2
  151. package/src/data-plane/server/indexing/plugins/starter-pack.ts +76 -0
  152. package/src/data-plane/server/routes/index.ts +2 -0
  153. package/src/data-plane/server/routes/interactions.ts +56 -2
  154. package/src/data-plane/server/routes/profile.ts +1 -0
  155. package/src/data-plane/server/routes/records.ts +1 -0
  156. package/src/data-plane/server/routes/starter-packs.ts +32 -0
  157. package/src/hydration/actor.ts +5 -0
  158. package/src/hydration/feed.ts +1 -2
  159. package/src/hydration/graph.ts +55 -2
  160. package/src/hydration/hydrator.ts +143 -6
  161. package/src/hydration/util.ts +2 -0
  162. package/src/lexicon/index.ts +37 -0
  163. package/src/lexicon/lexicons.ts +337 -1
  164. package/src/lexicon/types/app/bsky/actor/defs.ts +5 -0
  165. package/src/lexicon/types/app/bsky/actor/profile.ts +3 -0
  166. package/src/lexicon/types/app/bsky/graph/defs.ts +58 -0
  167. package/src/lexicon/types/app/bsky/graph/getActorStarterPacks.ts +49 -0
  168. package/src/lexicon/types/app/bsky/graph/getStarterPack.ts +47 -0
  169. package/src/lexicon/types/app/bsky/graph/getStarterPacks.ts +46 -0
  170. package/src/lexicon/types/app/bsky/graph/starterpack.ts +50 -0
  171. package/src/lexicon/types/app/bsky/notification/listNotifications.ts +2 -1
  172. package/src/proto/bsky_connect.ts +46 -0
  173. package/src/proto/bsky_pb.ts +512 -0
  174. package/src/views/index.ts +71 -1
  175. package/tests/__snapshots__/feed-generation.test.ts.snap +28 -0
  176. package/tests/data-plane/__snapshots__/indexing.test.ts.snap +9 -0
  177. package/tests/views/__snapshots__/author-feed.test.ts.snap +29 -0
  178. package/tests/views/__snapshots__/block-lists.test.ts.snap +16 -0
  179. package/tests/views/__snapshots__/blocks.test.ts.snap +4 -0
  180. package/tests/views/__snapshots__/follows.test.ts.snap +34 -0
  181. package/tests/views/__snapshots__/labeler-service.test.ts.snap +4 -0
  182. package/tests/views/__snapshots__/likes.test.ts.snap +1 -0
  183. package/tests/views/__snapshots__/list-feed.test.ts.snap +12 -0
  184. package/tests/views/__snapshots__/mute-lists.test.ts.snap +20 -0
  185. package/tests/views/__snapshots__/mutes.test.ts.snap +8 -0
  186. package/tests/views/__snapshots__/notifications.test.ts.snap +10 -0
  187. package/tests/views/__snapshots__/posts.test.ts.snap +6 -0
  188. package/tests/views/__snapshots__/profile.test.ts.snap +18 -0
  189. package/tests/views/__snapshots__/reposts.test.ts.snap +1 -0
  190. package/tests/views/__snapshots__/starter-packs.test.ts.snap +482 -0
  191. package/tests/views/__snapshots__/thread.test.ts.snap +22 -0
  192. package/tests/views/__snapshots__/threadgating.test.ts.snap +2 -0
  193. package/tests/views/__snapshots__/timeline.test.ts.snap +81 -0
  194. package/tests/views/starter-packs.test.ts +121 -0
@@ -0,0 +1,81 @@
1
+ import { dedupeStrs, mapDefined } from '@atproto/common'
2
+ import { Server } from '../../../../lexicon'
3
+ import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getStarterPacks'
4
+ import AppContext from '../../../../context'
5
+ import { resHeaders } from '../../../util'
6
+ import { createPipeline, noRules } from '../../../../pipeline'
7
+ import {
8
+ HydrateCtx,
9
+ HydrationState,
10
+ Hydrator,
11
+ } from '../../../../hydration/hydrator'
12
+ import { Views } from '../../../../views'
13
+
14
+ export default function (server: Server, ctx: AppContext) {
15
+ const getStarterPacks = createPipeline(
16
+ skeleton,
17
+ hydration,
18
+ noRules,
19
+ presentation,
20
+ )
21
+ server.app.bsky.graph.getStarterPacks({
22
+ auth: ctx.authVerifier.standardOptional,
23
+ handler: async ({ auth, params, req }) => {
24
+ const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth)
25
+ const labelers = ctx.reqLabelers(req)
26
+ const hydrateCtx = await ctx.hydrator.createContext({
27
+ viewer,
28
+ labelers,
29
+ includeTakedowns,
30
+ })
31
+
32
+ const result = await getStarterPacks({ ...params, hydrateCtx }, ctx)
33
+
34
+ return {
35
+ encoding: 'application/json',
36
+ body: result,
37
+ headers: resHeaders({ labelers: hydrateCtx.labelers }),
38
+ }
39
+ },
40
+ })
41
+ }
42
+
43
+ const skeleton = async (inputs: { params: Params }) => {
44
+ return { uris: inputs.params.uris }
45
+ }
46
+
47
+ const hydration = async (input: {
48
+ ctx: Context
49
+ params: Params
50
+ skeleton: SkeletonState
51
+ }) => {
52
+ const { ctx, params, skeleton } = input
53
+ return ctx.hydrator.hydrateStarterPacksBasic(
54
+ dedupeStrs(skeleton.uris),
55
+ params.hydrateCtx,
56
+ )
57
+ }
58
+
59
+ const presentation = (input: {
60
+ ctx: Context
61
+ params: Params
62
+ skeleton: SkeletonState
63
+ hydration: HydrationState
64
+ }) => {
65
+ const { ctx, skeleton, hydration } = input
66
+ const starterPacks = mapDefined(skeleton.uris, (did) =>
67
+ ctx.views.starterPackBasic(did, hydration),
68
+ )
69
+ return { starterPacks }
70
+ }
71
+
72
+ type Context = {
73
+ hydrator: Hydrator
74
+ views: Views
75
+ }
76
+
77
+ type Params = QueryParams & {
78
+ hydrateCtx: HydrateCtx
79
+ }
80
+
81
+ type SkeletonState = { uris: string[] }
package/src/api/index.ts CHANGED
@@ -16,6 +16,7 @@ import getActorLikes from './app/bsky/feed/getActorLikes'
16
16
  import getProfile from './app/bsky/actor/getProfile'
17
17
  import getProfiles from './app/bsky/actor/getProfiles'
18
18
  import getRepostedBy from './app/bsky/feed/getRepostedBy'
19
+ import getActorStarterPacks from './app/bsky/graph/getActorStarterPacks'
19
20
  import getBlocks from './app/bsky/graph/getBlocks'
20
21
  import getListBlocks from './app/bsky/graph/getListBlocks'
21
22
  import getFollowers from './app/bsky/graph/getFollowers'
@@ -26,6 +27,8 @@ import getLists from './app/bsky/graph/getLists'
26
27
  import getListMutes from './app/bsky/graph/getListMutes'
27
28
  import getMutes from './app/bsky/graph/getMutes'
28
29
  import getRelationships from './app/bsky/graph/getRelationships'
30
+ import getStarterPack from './app/bsky/graph/getStarterPack'
31
+ import getStarterPacks from './app/bsky/graph/getStarterPacks'
29
32
  import muteActor from './app/bsky/graph/muteActor'
30
33
  import unmuteActor from './app/bsky/graph/unmuteActor'
31
34
  import muteActorList from './app/bsky/graph/muteActorList'
@@ -75,6 +78,7 @@ export default function (server: Server, ctx: AppContext) {
75
78
  getProfile(server, ctx)
76
79
  getProfiles(server, ctx)
77
80
  getRepostedBy(server, ctx)
81
+ getActorStarterPacks(server, ctx)
78
82
  getBlocks(server, ctx)
79
83
  getListBlocks(server, ctx)
80
84
  getFollowers(server, ctx)
@@ -85,6 +89,8 @@ export default function (server: Server, ctx: AppContext) {
85
89
  getListMutes(server, ctx)
86
90
  getMutes(server, ctx)
87
91
  getRelationships(server, ctx)
92
+ getStarterPack(server, ctx)
93
+ getStarterPacks(server, ctx)
88
94
  muteActor(server, ctx)
89
95
  unmuteActor(server, ctx)
90
96
  muteActorList(server, ctx)
@@ -34,6 +34,7 @@ import * as suggestedFeed from './tables/suggested-feed'
34
34
  import * as taggedSuggestion from './tables/tagged-suggestion'
35
35
  import * as blobTakedown from './tables/blob-takedown'
36
36
  import * as labeler from './tables/labeler'
37
+ import * as starterPack from './tables/starter-pack'
37
38
 
38
39
  export type DatabaseSchemaType = duplicateRecord.PartialDB &
39
40
  profile.PartialDB &
@@ -69,6 +70,7 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB &
69
70
  suggestedFeed.PartialDB &
70
71
  blobTakedown.PartialDB &
71
72
  labeler.PartialDB &
73
+ starterPack.PartialDB &
72
74
  taggedSuggestion.PartialDB
73
75
 
74
76
  export type DatabaseSchema = Kysely<DatabaseSchemaType>
@@ -0,0 +1,45 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('starter_pack')
6
+ .addColumn('uri', 'varchar', (col) => col.primaryKey())
7
+ .addColumn('cid', 'varchar', (col) => col.notNull())
8
+ .addColumn('creator', 'varchar', (col) => col.notNull())
9
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
10
+ .addColumn('indexedAt', 'varchar', (col) => col.notNull())
11
+ .addColumn('sortAt', 'varchar', (col) =>
12
+ col
13
+ .generatedAlwaysAs(sql`least("createdAt", "indexedAt")`)
14
+ .stored()
15
+ .notNull(),
16
+ )
17
+ .execute()
18
+ await db.schema
19
+ .createIndex('starter_pack_creator_order_by_idx')
20
+ .on('starter_pack')
21
+ .columns(['creator', 'sortAt', 'cid'])
22
+ .execute()
23
+ await db.schema
24
+ .alterTable('profile')
25
+ .addColumn('joinedViaStarterPackUri', 'varchar')
26
+ .execute()
27
+ await db.schema
28
+ .alterTable('profile')
29
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
30
+ .execute()
31
+ await db.schema
32
+ .createIndex('profile_starter_pack_joined_idx')
33
+ .on('profile')
34
+ .columns(['joinedViaStarterPackUri', 'createdAt'])
35
+ .execute()
36
+ }
37
+
38
+ export async function down(db: Kysely<unknown>): Promise<void> {
39
+ await db.schema.dropTable('starter_pack').execute()
40
+ await db.schema
41
+ .alterTable('profile')
42
+ .dropColumn('joinedViaStarterPackUri')
43
+ .execute()
44
+ await db.schema.alterTable('profile').dropColumn('createdAt').execute()
45
+ }
@@ -36,3 +36,4 @@ export * as _20240124T023719200Z from './20240124T023719200Z-tagged-suggestions'
36
36
  export * as _20240226T225725627Z from './20240226T225725627Z-labelers'
37
37
  export * as _20240530T170337073Z from './20240530T170337073Z-account-deactivation'
38
38
  export * as _20240606T171229898Z from './20240606T171229898Z-thread-mutes'
39
+ export * as _20240606T222548219Z from './20240606T222548219Z-starter-packs'
@@ -8,6 +8,8 @@ export interface Profile {
8
8
  description: string | null
9
9
  avatarCid: string | null
10
10
  bannerCid: string | null
11
+ joinedViaStarterPackUri: string | null
12
+ createdAt: string
11
13
  indexedAt: string
12
14
  }
13
15
  export type PartialDB = { [tableName]: Profile }
@@ -0,0 +1,16 @@
1
+ import { GeneratedAlways } from 'kysely'
2
+
3
+ export const tableName = 'starter_pack'
4
+
5
+ export interface StarterPack {
6
+ uri: string
7
+ cid: string
8
+ creator: string
9
+ createdAt: string
10
+ indexedAt: string
11
+ sortAt: GeneratedAlways<string>
12
+ }
13
+
14
+ export type PartialDB = {
15
+ [tableName]: StarterPack
16
+ }
@@ -26,6 +26,7 @@ import * as ListItem from './plugins/list-item'
26
26
  import * as ListBlock from './plugins/list-block'
27
27
  import * as Block from './plugins/block'
28
28
  import * as FeedGenerator from './plugins/feed-generator'
29
+ import * as StarterPack from './plugins/starter-pack'
29
30
  import * as Labeler from './plugins/labeler'
30
31
  import * as ChatDeclaration from './plugins/chat-declaration'
31
32
  import RecordProcessor from './processor'
@@ -46,6 +47,7 @@ export class IndexingService {
46
47
  listBlock: ListBlock.PluginType
47
48
  block: Block.PluginType
48
49
  feedGenerator: FeedGenerator.PluginType
50
+ starterPack: StarterPack.PluginType
49
51
  labeler: Labeler.PluginType
50
52
  chatDeclaration: ChatDeclaration.PluginType
51
53
  }
@@ -67,6 +69,7 @@ export class IndexingService {
67
69
  listBlock: ListBlock.makePlugin(this.db, this.background),
68
70
  block: Block.makePlugin(this.db, this.background),
69
71
  feedGenerator: FeedGenerator.makePlugin(this.db, this.background),
72
+ starterPack: StarterPack.makePlugin(this.db, this.background),
70
73
  labeler: Labeler.makePlugin(this.db, this.background),
71
74
  chatDeclaration: ChatDeclaration.makePlugin(this.db, this.background),
72
75
  }
@@ -28,6 +28,8 @@ const insertFn = async (
28
28
  description: obj.description,
29
29
  avatarCid: obj.avatar?.ref.toString(),
30
30
  bannerCid: obj.banner?.ref.toString(),
31
+ joinedViaStarterPackUri: obj.joinedViaStarterPack?.uri,
32
+ createdAt: obj.createdAt ?? new Date().toISOString(),
31
33
  indexedAt: timestamp,
32
34
  })
33
35
  .onConflict((oc) => oc.doNothing())
@@ -40,8 +42,20 @@ const findDuplicate = async (): Promise<AtUri | null> => {
40
42
  return null
41
43
  }
42
44
 
43
- const notifsForInsert = () => {
44
- return []
45
+ const notifsForInsert = (obj: IndexedProfile) => {
46
+ if (!obj.joinedViaStarterPackUri) return []
47
+ const starterPackUri = new AtUri(obj.joinedViaStarterPackUri)
48
+ return [
49
+ {
50
+ did: starterPackUri.host,
51
+ author: obj.creator,
52
+ recordUri: obj.uri,
53
+ recordCid: obj.cid,
54
+ reason: 'starterpack-joined' as const,
55
+ reasonSubject: obj.joinedViaStarterPackUri,
56
+ sortAt: obj.indexedAt,
57
+ },
58
+ ]
45
59
  }
46
60
 
47
61
  const deleteFn = async (
@@ -0,0 +1,76 @@
1
+ import { Selectable } from 'kysely'
2
+ import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax'
3
+ import { CID } from 'multiformats/cid'
4
+ import * as StarterPack from '../../../../lexicon/types/app/bsky/graph/starterpack'
5
+ import * as lex from '../../../../lexicon/lexicons'
6
+ import { Database } from '../../db'
7
+ import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema'
8
+ import RecordProcessor from '../processor'
9
+ import { BackgroundQueue } from '../../background'
10
+
11
+ const lexId = lex.ids.AppBskyGraphStarterpack
12
+ type IndexedStarterPack = Selectable<DatabaseSchemaType['starter_pack']>
13
+
14
+ const insertFn = async (
15
+ db: DatabaseSchema,
16
+ uri: AtUri,
17
+ cid: CID,
18
+ obj: StarterPack.Record,
19
+ timestamp: string,
20
+ ): Promise<IndexedStarterPack | null> => {
21
+ const inserted = await db
22
+ .insertInto('starter_pack')
23
+ .values({
24
+ uri: uri.toString(),
25
+ cid: cid.toString(),
26
+ creator: uri.host,
27
+ createdAt: normalizeDatetimeAlways(obj.createdAt),
28
+ indexedAt: timestamp,
29
+ })
30
+ .onConflict((oc) => oc.doNothing())
31
+ .returningAll()
32
+ .executeTakeFirst()
33
+ return inserted || null
34
+ }
35
+
36
+ const findDuplicate = async (): Promise<AtUri | null> => {
37
+ return null
38
+ }
39
+
40
+ const notifsForInsert = () => {
41
+ return []
42
+ }
43
+
44
+ const deleteFn = async (
45
+ db: DatabaseSchema,
46
+ uri: AtUri,
47
+ ): Promise<IndexedStarterPack | null> => {
48
+ const deleted = await db
49
+ .deleteFrom('starter_pack')
50
+ .where('uri', '=', uri.toString())
51
+ .returningAll()
52
+ .executeTakeFirst()
53
+ return deleted || null
54
+ }
55
+
56
+ const notifsForDelete = () => {
57
+ return { notifs: [], toDelete: [] }
58
+ }
59
+
60
+ export type PluginType = RecordProcessor<StarterPack.Record, IndexedStarterPack>
61
+
62
+ export const makePlugin = (
63
+ db: Database,
64
+ background: BackgroundQueue,
65
+ ): PluginType => {
66
+ return new RecordProcessor(db, background, {
67
+ lexId,
68
+ insertFn,
69
+ findDuplicate,
70
+ deleteFn,
71
+ notifsForInsert,
72
+ notifsForDelete,
73
+ })
74
+ }
75
+
76
+ export default makePlugin
@@ -22,6 +22,7 @@ import search from './search'
22
22
  import suggestions from './suggestions'
23
23
  import sync from './sync'
24
24
  import threads from './threads'
25
+ import starterPacks from './starter-packs'
25
26
  import { Database } from '../db'
26
27
 
27
28
  export default (db: Database, idResolver: IdResolver) =>
@@ -48,6 +49,7 @@ export default (db: Database, idResolver: IdResolver) =>
48
49
  ...suggestions(db),
49
50
  ...sync(db),
50
51
  ...threads(db),
52
+ ...starterPacks(db),
51
53
 
52
54
  async ping() {
53
55
  return {}
@@ -1,4 +1,4 @@
1
- import { keyBy } from '@atproto/common'
1
+ import { DAY, keyBy } from '@atproto/common'
2
2
  import { ServiceImpl } from '@connectrpc/connect'
3
3
  import { Service } from '../../../proto/bsky_connect'
4
4
  import { Database } from '../db'
@@ -24,7 +24,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
24
24
  },
25
25
  async getCountsForUsers(req) {
26
26
  if (req.dids.length === 0) {
27
- return { followers: [], following: [], posts: [] }
27
+ return {}
28
28
  }
29
29
  const { ref } = db.db.dynamic
30
30
  const res = await db.db
@@ -42,6 +42,11 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
42
42
  .whereRef('creator', '=', ref('profile_agg.did'))
43
43
  .select(countAll.as('val'))
44
44
  .as('listsCount'),
45
+ db.db
46
+ .selectFrom('starter_pack')
47
+ .whereRef('creator', '=', ref('profile_agg.did'))
48
+ .select(countAll.as('val'))
49
+ .as('starterPacksCount'),
45
50
  ])
46
51
  .execute()
47
52
  const byDid = keyBy(res, 'did')
@@ -51,6 +56,55 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
51
56
  posts: req.dids.map((uri) => byDid[uri]?.postsCount ?? 0),
52
57
  lists: req.dids.map((uri) => byDid[uri]?.listsCount ?? 0),
53
58
  feeds: req.dids.map((uri) => byDid[uri]?.feedGensCount ?? 0),
59
+ starterPacks: req.dids.map((uri) => byDid[uri]?.starterPacksCount ?? 0),
60
+ }
61
+ },
62
+ async getStarterPackCounts(req) {
63
+ const weekAgo = new Date(Date.now() - 7 * DAY)
64
+ const uris = req.refs.map((ref) => ref.uri)
65
+ if (uris.length === 0) {
66
+ return { joinedAllTime: [], joinedWeek: [] }
67
+ }
68
+ const countsAllTime = await db.db
69
+ .selectFrom('profile')
70
+ .where('joinedViaStarterPackUri', 'in', uris)
71
+ .select(['joinedViaStarterPackUri as uri', countAll.as('count')])
72
+ .groupBy('joinedViaStarterPackUri')
73
+ .execute()
74
+ const countsWeek = await db.db
75
+ .selectFrom('profile')
76
+ .where('joinedViaStarterPackUri', 'in', uris)
77
+ .where('createdAt', '>', weekAgo.toISOString())
78
+ .select(['joinedViaStarterPackUri as uri', countAll.as('count')])
79
+ .groupBy('joinedViaStarterPackUri')
80
+ .execute()
81
+ const countsWeekByUri = countsWeek.reduce((cur, item) => {
82
+ if (!item.uri) return cur
83
+ return cur.set(item.uri, item.count)
84
+ }, new Map<string, number>())
85
+ const countsAllTimeByUri = countsAllTime.reduce((cur, item) => {
86
+ if (!item.uri) return cur
87
+ return cur.set(item.uri, item.count)
88
+ }, new Map<string, number>())
89
+ return {
90
+ joinedWeek: uris.map((uri) => countsWeekByUri.get(uri) ?? 0),
91
+ joinedAllTime: uris.map((uri) => countsAllTimeByUri.get(uri) ?? 0),
92
+ }
93
+ },
94
+ async getListCounts(req) {
95
+ const uris = req.refs.map((ref) => ref.uri)
96
+ if (uris.length === 0) {
97
+ return { listItems: [] }
98
+ }
99
+ const countsListItems = await db.db
100
+ .selectFrom('list_item')
101
+ .where('listUri', 'in', uris)
102
+ .select(['listUri as uri', countAll.as('count')])
103
+ .groupBy('listUri')
104
+ .execute()
105
+ const countsByUri = keyBy(countsListItems, 'uri')
106
+ return {
107
+ listItems: uris.map((uri) => countsByUri[uri]?.count ?? 0),
54
108
  }
55
109
  },
56
110
  })
@@ -54,6 +54,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
54
54
  ? chatDeclaration['allowIncoming']
55
55
  : undefined,
56
56
  upstreamStatus: row?.upstreamStatus ?? '',
57
+ createdAt: profiles.records[i].createdAt, // @NOTE profile creation date not trusted in production
57
58
  }
58
59
  })
59
60
  return { actors }
@@ -22,6 +22,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
22
22
  getThreadGateRecords: getRecords(db, ids.AppBskyFeedThreadgate),
23
23
  getLabelerRecords: getRecords(db, ids.AppBskyLabelerService),
24
24
  getActorChatDeclarationRecords: getRecords(db, ids.ChatBskyActorDeclaration),
25
+ getStarterPackRecords: getRecords(db, ids.AppBskyGraphStarterpack),
25
26
  })
26
27
 
27
28
  export const getRecords =
@@ -0,0 +1,32 @@
1
+ import { ServiceImpl } from '@connectrpc/connect'
2
+ import { Service } from '../../../proto/bsky_connect'
3
+ import { Database } from '../db'
4
+ import { TimeCidKeyset, paginate } from '../db/pagination'
5
+
6
+ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
7
+ async getActorStarterPacks(req) {
8
+ const { actorDid, limit, cursor } = req
9
+
10
+ const { ref } = db.db.dynamic
11
+ let builder = db.db
12
+ .selectFrom('starter_pack')
13
+ .selectAll()
14
+ .where('creator', '=', actorDid)
15
+
16
+ const keyset = new TimeCidKeyset(
17
+ ref('starter_pack.sortAt'),
18
+ ref('starter_pack.cid'),
19
+ )
20
+ builder = paginate(builder, {
21
+ limit,
22
+ cursor,
23
+ keyset,
24
+ })
25
+ const starterPacks = await builder.execute()
26
+
27
+ return {
28
+ uris: starterPacks.map((sp) => sp.uri),
29
+ cursor: keyset.packFromResult(starterPacks),
30
+ }
31
+ },
32
+ })
@@ -1,3 +1,4 @@
1
+ import { Timestamp } from '@bufbuild/protobuf'
1
2
  import { DataPlaneClient } from '../data-plane/client'
2
3
  import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile'
3
4
  import { Record as ChatDeclarationRecord } from '../lexicon/types/chat/bsky/actor/declaration'
@@ -21,6 +22,7 @@ export type Actor = {
21
22
  isLabeler: boolean
22
23
  allowIncomingChatsFrom?: string
23
24
  upstreamStatus?: string
25
+ createdAt?: Date
24
26
  }
25
27
 
26
28
  export type Actors = HydrationMap<Actor>
@@ -54,6 +56,7 @@ export type ProfileAgg = {
54
56
  posts: number
55
57
  lists: number
56
58
  feeds: number
59
+ starterPacks: number
57
60
  }
58
61
 
59
62
  export type ProfileAggs = HydrationMap<ProfileAgg>
@@ -127,6 +130,7 @@ export class ActorHydrator {
127
130
  isLabeler: actor.labeler ?? false,
128
131
  allowIncomingChatsFrom: actor.allowIncomingChatsFrom || undefined,
129
132
  upstreamStatus: actor.upstreamStatus || undefined,
133
+ createdAt: actor.createdAt?.toDate(),
130
134
  })
131
135
  }, new HydrationMap<Actor>())
132
136
  }
@@ -211,6 +215,7 @@ export class ActorHydrator {
211
215
  posts: counts.posts[i] ?? 0,
212
216
  lists: counts.lists[i] ?? 0,
213
217
  feeds: counts.feeds[i] ?? 0,
218
+ starterPacks: counts.starterPacks[i] ?? 0,
214
219
  })
215
220
  }, new HydrationMap<ProfileAgg>())
216
221
  }
@@ -6,6 +6,7 @@ import { Record as FeedGenRecord } from '../lexicon/types/app/bsky/feed/generato
6
6
  import { Record as ThreadgateRecord } from '../lexicon/types/app/bsky/feed/threadgate'
7
7
  import {
8
8
  HydrationMap,
9
+ ItemRef,
9
10
  RecordInfo,
10
11
  parseRecord,
11
12
  parseString,
@@ -58,7 +59,6 @@ export type FeedGenViewerStates = HydrationMap<FeedGenViewerState>
58
59
  export type Threadgate = RecordInfo<ThreadgateRecord>
59
60
  export type Threadgates = HydrationMap<Threadgate>
60
61
 
61
- export type ItemRef = { uri: string; cid?: string }
62
62
  export type ThreadRef = ItemRef & { threadRoot: string }
63
63
 
64
64
  // @NOTE the feed item types in the protos for author feeds and timelines
@@ -203,7 +203,6 @@ export class FeedHydrator {
203
203
  }, new HydrationMap<Threadgate>())
204
204
  }
205
205
 
206
- // @TODO may not be supported yet by data plane
207
206
  async getLikes(uris: string[], includeTakedowns = false): Promise<Likes> {
208
207
  if (!uris.length) return new HydrationMap<Like>()
209
208
  const res = await this.dataplane.getLikeRecords({ uris })
@@ -1,9 +1,10 @@
1
1
  import { Record as FollowRecord } from '../lexicon/types/app/bsky/graph/follow'
2
2
  import { Record as BlockRecord } from '../lexicon/types/app/bsky/graph/block'
3
+ import { Record as StarterPackRecord } from '../lexicon/types/app/bsky/graph/starterpack'
3
4
  import { Record as ListRecord } from '../lexicon/types/app/bsky/graph/list'
4
5
  import { Record as ListItemRecord } from '../lexicon/types/app/bsky/graph/listitem'
5
6
  import { DataPlaneClient } from '../data-plane/client'
6
- import { HydrationMap, RecordInfo, parseRecord } from './util'
7
+ import { HydrationMap, ItemRef, RecordInfo, parseRecord } from './util'
7
8
  import { FollowInfo } from '../proto/bsky_pb'
8
9
 
9
10
  export type List = RecordInfo<ListRecord>
@@ -25,6 +26,23 @@ export type Follows = HydrationMap<Follow>
25
26
 
26
27
  export type Block = RecordInfo<BlockRecord>
27
28
 
29
+ export type StarterPack = RecordInfo<StarterPackRecord>
30
+ export type StarterPacks = HydrationMap<StarterPack>
31
+
32
+ export type StarterPackAgg = {
33
+ joinedWeek: number
34
+ joinedAllTime: number
35
+ listItemSampleUris?: string[] // gets set during starter pack hydration (not for basic view)
36
+ }
37
+
38
+ export type StarterPackAggs = HydrationMap<StarterPackAgg>
39
+
40
+ export type ListAgg = {
41
+ listItems: number
42
+ }
43
+
44
+ export type ListAggs = HydrationMap<ListAgg>
45
+
28
46
  export type RelationshipPair = [didA: string, didB: string]
29
47
 
30
48
  const dedupePairs = (pairs: RelationshipPair[]): RelationshipPair[] => {
@@ -84,7 +102,6 @@ export class GraphHydrator {
84
102
  }, new HydrationMap<List>())
85
103
  }
86
104
 
87
- // @TODO may not be supported yet by data plane
88
105
  async getListItems(
89
106
  uris: string[],
90
107
  includeTakedowns = false,
@@ -195,4 +212,40 @@ export class GraphHydrator {
195
212
  })
196
213
  return { followers: res.followers, cursor: res.cursor }
197
214
  }
215
+
216
+ async getStarterPacks(
217
+ uris: string[],
218
+ includeTakedowns = false,
219
+ ): Promise<StarterPacks> {
220
+ if (!uris.length) return new HydrationMap<StarterPack>()
221
+ const res = await this.dataplane.getStarterPackRecords({ uris })
222
+ return uris.reduce((acc, uri, i) => {
223
+ const record = parseRecord<StarterPackRecord>(
224
+ res.records[i],
225
+ includeTakedowns,
226
+ )
227
+ return acc.set(uri, record ?? null)
228
+ }, new HydrationMap<StarterPack>())
229
+ }
230
+
231
+ async getStarterPackAggregates(refs: ItemRef[]) {
232
+ if (!refs.length) return new HydrationMap<StarterPackAgg>()
233
+ const counts = await this.dataplane.getStarterPackCounts({ refs })
234
+ return refs.reduce((acc, { uri }, i) => {
235
+ return acc.set(uri, {
236
+ joinedWeek: counts.joinedWeek[i] ?? 0,
237
+ joinedAllTime: counts.joinedAllTime[i] ?? 0,
238
+ })
239
+ }, new HydrationMap<StarterPackAgg>())
240
+ }
241
+
242
+ async getListAggregates(refs: ItemRef[]) {
243
+ if (!refs.length) return new HydrationMap<ListAgg>()
244
+ const counts = await this.dataplane.getListCounts({ refs })
245
+ return refs.reduce((acc, { uri }, i) => {
246
+ return acc.set(uri, {
247
+ listItems: counts.listItems[i] ?? 0,
248
+ })
249
+ }, new HydrationMap<ListAgg>())
250
+ }
198
251
  }