@atproto/bsky 0.0.176 → 0.0.178

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 (64) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/api/app/bsky/graph/getList.js +1 -6
  3. package/dist/api/app/bsky/graph/getList.js.map +1 -1
  4. package/dist/api/app/bsky/graph/getLists.d.ts.map +1 -1
  5. package/dist/api/app/bsky/graph/getLists.js +16 -4
  6. package/dist/api/app/bsky/graph/getLists.js.map +1 -1
  7. package/dist/api/app/bsky/graph/getListsWithMembership.d.ts +4 -0
  8. package/dist/api/app/bsky/graph/getListsWithMembership.d.ts.map +1 -0
  9. package/dist/api/app/bsky/graph/getListsWithMembership.js +88 -0
  10. package/dist/api/app/bsky/graph/getListsWithMembership.js.map +1 -0
  11. package/dist/api/app/bsky/graph/getStarterPacksWithMembership.d.ts +4 -0
  12. package/dist/api/app/bsky/graph/getStarterPacksWithMembership.d.ts.map +1 -0
  13. package/dist/api/app/bsky/graph/getStarterPacksWithMembership.js +72 -0
  14. package/dist/api/app/bsky/graph/getStarterPacksWithMembership.js.map +1 -0
  15. package/dist/api/index.d.ts.map +1 -1
  16. package/dist/api/index.js +4 -0
  17. package/dist/api/index.js.map +1 -1
  18. package/dist/hydration/graph.d.ts +4 -0
  19. package/dist/hydration/graph.d.ts.map +1 -1
  20. package/dist/hydration/graph.js.map +1 -1
  21. package/dist/hydration/hydrator.d.ts +3 -1
  22. package/dist/hydration/hydrator.d.ts.map +1 -1
  23. package/dist/hydration/hydrator.js +27 -0
  24. package/dist/hydration/hydrator.js.map +1 -1
  25. package/dist/lexicon/index.d.ts +234 -230
  26. package/dist/lexicon/index.d.ts.map +1 -1
  27. package/dist/lexicon/index.js +682 -674
  28. package/dist/lexicon/index.js.map +1 -1
  29. package/dist/lexicon/lexicons.d.ts +17073 -16785
  30. package/dist/lexicon/lexicons.d.ts.map +1 -1
  31. package/dist/lexicon/lexicons.js +9126 -8980
  32. package/dist/lexicon/lexicons.js.map +1 -1
  33. package/dist/lexicon/types/app/bsky/graph/getLists.d.ts +2 -0
  34. package/dist/lexicon/types/app/bsky/graph/getLists.d.ts.map +1 -1
  35. package/dist/lexicon/types/app/bsky/graph/getListsWithMembership.d.ts +40 -0
  36. package/dist/lexicon/types/app/bsky/graph/getListsWithMembership.d.ts.map +1 -0
  37. package/dist/lexicon/types/app/bsky/graph/getListsWithMembership.js +16 -0
  38. package/dist/lexicon/types/app/bsky/graph/getListsWithMembership.js.map +1 -0
  39. package/dist/lexicon/types/app/bsky/graph/getStarterPacksWithMembership.d.ts +38 -0
  40. package/dist/lexicon/types/app/bsky/graph/getStarterPacksWithMembership.d.ts.map +1 -0
  41. package/dist/lexicon/types/app/bsky/graph/getStarterPacksWithMembership.js +16 -0
  42. package/dist/lexicon/types/app/bsky/graph/getStarterPacksWithMembership.js.map +1 -0
  43. package/dist/views/index.d.ts +2 -1
  44. package/dist/views/index.d.ts.map +1 -1
  45. package/dist/views/index.js +6 -0
  46. package/dist/views/index.js.map +1 -1
  47. package/package.json +11 -11
  48. package/src/api/app/bsky/graph/getList.ts +3 -5
  49. package/src/api/app/bsky/graph/getLists.ts +18 -5
  50. package/src/api/app/bsky/graph/getListsWithMembership.ts +139 -0
  51. package/src/api/app/bsky/graph/getStarterPacksWithMembership.ts +135 -0
  52. package/src/api/index.ts +4 -0
  53. package/src/hydration/graph.ts +8 -0
  54. package/src/hydration/hydrator.ts +43 -0
  55. package/src/lexicon/index.ts +1247 -1221
  56. package/src/lexicon/lexicons.ts +9494 -9341
  57. package/src/lexicon/types/app/bsky/graph/getLists.ts +2 -0
  58. package/src/lexicon/types/app/bsky/graph/getListsWithMembership.ts +63 -0
  59. package/src/lexicon/types/app/bsky/graph/getStarterPacksWithMembership.ts +65 -0
  60. package/src/views/index.ts +11 -0
  61. package/tests/views/__snapshots__/lists.test.ts.snap +160 -8
  62. package/tests/views/lists.test.ts +270 -36
  63. package/tests/views/starter-packs.test.ts +132 -3
  64. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,139 @@
1
+ import { mapDefined } from '@atproto/common'
2
+ import { InvalidRequestError } from '@atproto/xrpc-server'
3
+ import { AppContext } from '../../../../context'
4
+ import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator'
5
+ import { Server } from '../../../../lexicon'
6
+ import {
7
+ CURATELIST,
8
+ MODLIST,
9
+ } from '../../../../lexicon/types/app/bsky/graph/defs'
10
+ import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getListsWithMembership'
11
+ import {
12
+ HydrationFnInput,
13
+ PresentationFnInput,
14
+ RulesFnInput,
15
+ SkeletonFnInput,
16
+ createPipeline,
17
+ } from '../../../../pipeline'
18
+ import { Views } from '../../../../views'
19
+ import { clearlyBadCursor, resHeaders } from '../../../util'
20
+
21
+ export default function (server: Server, ctx: AppContext) {
22
+ const getListsWithMembership = createPipeline(
23
+ skeleton,
24
+ hydration,
25
+ filterPurposes,
26
+ presentation,
27
+ )
28
+ server.app.bsky.graph.getListsWithMembership({
29
+ auth: ctx.authVerifier.standard,
30
+ handler: async ({ params, auth, req }) => {
31
+ const viewer = auth.credentials.iss
32
+ const labelers = ctx.reqLabelers(req)
33
+ const hydrateCtx = await ctx.hydrator.createContext({
34
+ labelers,
35
+ viewer,
36
+ })
37
+ const result = await getListsWithMembership(
38
+ { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) },
39
+ ctx,
40
+ )
41
+
42
+ return {
43
+ encoding: 'application/json',
44
+ body: result,
45
+ headers: resHeaders({ labelers: hydrateCtx.labelers }),
46
+ }
47
+ },
48
+ })
49
+ }
50
+
51
+ const skeleton = async (
52
+ input: SkeletonFnInput<Context, Params>,
53
+ ): Promise<SkeletonState> => {
54
+ const { ctx, params } = input
55
+ const [actorDid] = await ctx.hydrator.actor.getDids([params.actor])
56
+ if (!actorDid) throw new InvalidRequestError('Profile not found')
57
+
58
+ if (clearlyBadCursor(params.cursor)) {
59
+ return { actorDid, listUris: [] }
60
+ }
61
+
62
+ const { listUris, cursor } = await ctx.hydrator.dataplane.getActorLists({
63
+ actorDid: params.hydrateCtx.viewer,
64
+ cursor: params.cursor,
65
+ limit: params.limit,
66
+ })
67
+ return { actorDid, listUris, cursor: cursor || undefined }
68
+ }
69
+
70
+ const hydration = async (
71
+ input: HydrationFnInput<Context, Params, SkeletonState>,
72
+ ) => {
73
+ const { ctx, params, skeleton } = input
74
+ const { actorDid, listUris } = skeleton
75
+ return ctx.hydrator.hydrateListsMembership(
76
+ listUris,
77
+ actorDid,
78
+ params.hydrateCtx,
79
+ )
80
+ }
81
+
82
+ const filterPurposes = (
83
+ input: RulesFnInput<Context, Params, SkeletonState>,
84
+ ) => {
85
+ const { skeleton, hydration, params } = input
86
+ const purposes = params.purposes || ['modlist', 'curatelist']
87
+
88
+ const acceptedPurposes = new Set()
89
+ if (purposes.includes('modlist')) acceptedPurposes.add(MODLIST)
90
+ if (purposes.includes(MODLIST)) acceptedPurposes.add(MODLIST)
91
+ if (purposes.includes('curatelist')) acceptedPurposes.add(CURATELIST)
92
+ if (purposes.includes(CURATELIST)) acceptedPurposes.add(CURATELIST)
93
+
94
+ // @NOTE: While we don't support filtering on the dataplane, this might result in empty pages.
95
+ // Despite the empty pages, the pagination still can enumerate all items for the specified filters.
96
+ skeleton.listUris = skeleton.listUris.filter((uri) => {
97
+ const list = hydration.lists?.get(uri)
98
+ return acceptedPurposes.has(list?.record.purpose)
99
+ })
100
+ return skeleton
101
+ }
102
+
103
+ const presentation = (
104
+ input: PresentationFnInput<Context, Params, SkeletonState>,
105
+ ) => {
106
+ const { ctx, skeleton, hydration } = input
107
+ const { actorDid, listUris, cursor } = skeleton
108
+ const listsWithMembership = mapDefined(listUris, (uri) => {
109
+ const list = ctx.views.list(uri, hydration)
110
+ if (!list) return
111
+
112
+ const listItemUri = hydration.listMemberships
113
+ ?.get(uri)
114
+ ?.get(actorDid)?.actorListItemUri
115
+
116
+ return {
117
+ list,
118
+ listItem: listItemUri
119
+ ? ctx.views.listItemView(listItemUri, actorDid, hydration)
120
+ : undefined,
121
+ }
122
+ })
123
+ return { listsWithMembership, cursor }
124
+ }
125
+
126
+ type Context = {
127
+ hydrator: Hydrator
128
+ views: Views
129
+ }
130
+
131
+ type Params = QueryParams & {
132
+ hydrateCtx: HydrateCtx & { viewer: string }
133
+ }
134
+
135
+ type SkeletonState = {
136
+ actorDid: string
137
+ listUris: string[]
138
+ cursor?: string
139
+ }
@@ -0,0 +1,135 @@
1
+ import { mapDefined } from '@atproto/common'
2
+ import { InvalidRequestError } from '@atproto/xrpc-server'
3
+ import { AppContext } from '../../../../context'
4
+ import {
5
+ HydrateCtx,
6
+ Hydrator,
7
+ mergeManyStates,
8
+ } from '../../../../hydration/hydrator'
9
+ import { Server } from '../../../../lexicon'
10
+ import {
11
+ OutputSchema,
12
+ QueryParams,
13
+ } from '../../../../lexicon/types/app/bsky/graph/getStarterPacksWithMembership'
14
+ import {
15
+ HydrationFnInput,
16
+ PresentationFnInput,
17
+ SkeletonFnInput,
18
+ createPipeline,
19
+ noRules,
20
+ } from '../../../../pipeline'
21
+ import { Views } from '../../../../views'
22
+ import { clearlyBadCursor, resHeaders } from '../../../util'
23
+
24
+ export default function (server: Server, ctx: AppContext) {
25
+ const getStarterPacksWithMembership = createPipeline(
26
+ skeleton,
27
+ hydration,
28
+ noRules,
29
+ presentation,
30
+ )
31
+ server.app.bsky.graph.getStarterPacksWithMembership({
32
+ auth: ctx.authVerifier.standard,
33
+ handler: async ({ params, auth, req }) => {
34
+ const viewer = auth.credentials.iss
35
+ const labelers = ctx.reqLabelers(req)
36
+ const hydrateCtx = await ctx.hydrator.createContext({
37
+ labelers,
38
+ viewer,
39
+ })
40
+ const result = await getStarterPacksWithMembership(
41
+ { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) },
42
+ ctx,
43
+ )
44
+
45
+ return {
46
+ encoding: 'application/json',
47
+ body: result,
48
+ headers: resHeaders({ labelers: hydrateCtx.labelers }),
49
+ }
50
+ },
51
+ })
52
+ }
53
+
54
+ const skeleton = async (
55
+ input: SkeletonFnInput<Context, Params>,
56
+ ): Promise<SkeletonState> => {
57
+ const { ctx, params } = input
58
+ const [actorDid] = await ctx.hydrator.actor.getDids([params.actor])
59
+ if (!actorDid) throw new InvalidRequestError('Profile not found')
60
+
61
+ if (clearlyBadCursor(params.cursor)) {
62
+ return { actorDid, starterPackUris: [] }
63
+ }
64
+
65
+ const { uris: starterPackUris, cursor } =
66
+ await ctx.hydrator.dataplane.getActorStarterPacks({
67
+ actorDid: params.hydrateCtx.viewer,
68
+ cursor: params.cursor,
69
+ limit: params.limit,
70
+ })
71
+
72
+ return { actorDid, starterPackUris, cursor: cursor || undefined }
73
+ }
74
+
75
+ const hydration = async (
76
+ input: HydrationFnInput<Context, Params, SkeletonState>,
77
+ ) => {
78
+ const { ctx, params, skeleton } = input
79
+ const { actorDid, starterPackUris } = skeleton
80
+ const spHydrationState = await ctx.hydrator.hydrateStarterPacks(
81
+ starterPackUris,
82
+ params.hydrateCtx,
83
+ )
84
+ const listUris = mapDefined(
85
+ starterPackUris,
86
+ (uri) => spHydrationState.starterPacks?.get(uri)?.record.list,
87
+ )
88
+ const listMembershipHydrationState =
89
+ await ctx.hydrator.hydrateListsMembership(
90
+ listUris,
91
+ actorDid,
92
+ params.hydrateCtx,
93
+ )
94
+ return mergeManyStates(spHydrationState, listMembershipHydrationState)
95
+ }
96
+
97
+ const presentation = (
98
+ input: PresentationFnInput<Context, Params, SkeletonState>,
99
+ ): OutputSchema => {
100
+ const { ctx, skeleton, hydration } = input
101
+ const { actorDid, starterPackUris, cursor } = skeleton
102
+
103
+ const starterPacksWithMembership = mapDefined(starterPackUris, (spUri) => {
104
+ const listUri = hydration.starterPacks?.get(spUri)?.record.list
105
+ const starterPack = ctx.views.starterPack(spUri, hydration)
106
+ if (!listUri || !starterPack) return
107
+
108
+ const listItemUri = hydration.listMemberships
109
+ ?.get(listUri)
110
+ ?.get(actorDid)?.actorListItemUri
111
+
112
+ return {
113
+ starterPack,
114
+ listItem: listItemUri
115
+ ? ctx.views.listItemView(listItemUri, actorDid, hydration)
116
+ : undefined,
117
+ }
118
+ })
119
+ return { starterPacksWithMembership, cursor }
120
+ }
121
+
122
+ type Context = {
123
+ hydrator: Hydrator
124
+ views: Views
125
+ }
126
+
127
+ type Params = QueryParams & {
128
+ hydrateCtx: HydrateCtx & { viewer: string }
129
+ }
130
+
131
+ type SkeletonState = {
132
+ actorDid: string
133
+ starterPackUris: string[]
134
+ cursor?: string
135
+ }
package/src/api/index.ts CHANGED
@@ -29,10 +29,12 @@ import getList from './app/bsky/graph/getList'
29
29
  import getListBlocks from './app/bsky/graph/getListBlocks'
30
30
  import getListMutes from './app/bsky/graph/getListMutes'
31
31
  import getLists from './app/bsky/graph/getLists'
32
+ import getListsWithMembership from './app/bsky/graph/getListsWithMembership'
32
33
  import getMutes from './app/bsky/graph/getMutes'
33
34
  import getRelationships from './app/bsky/graph/getRelationships'
34
35
  import getStarterPack from './app/bsky/graph/getStarterPack'
35
36
  import getStarterPacks from './app/bsky/graph/getStarterPacks'
37
+ import getStarterPacksWithMembership from './app/bsky/graph/getStarterPacksWithMembership'
36
38
  import getSuggestedFollowsByActor from './app/bsky/graph/getSuggestedFollowsByActor'
37
39
  import muteActor from './app/bsky/graph/muteActor'
38
40
  import muteActorList from './app/bsky/graph/muteActorList'
@@ -108,11 +110,13 @@ export default function (server: Server, ctx: AppContext) {
108
110
  getFollows(server, ctx)
109
111
  getList(server, ctx)
110
112
  getLists(server, ctx)
113
+ getListsWithMembership(server, ctx)
111
114
  getListMutes(server, ctx)
112
115
  getMutes(server, ctx)
113
116
  getRelationships(server, ctx)
114
117
  getStarterPack(server, ctx)
115
118
  getStarterPacks(server, ctx)
119
+ getStarterPacksWithMembership(server, ctx)
116
120
  searchStarterPacks(server, ctx)
117
121
  muteActor(server, ctx)
118
122
  unmuteActor(server, ctx)
@@ -22,6 +22,14 @@ export type ListViewerState = {
22
22
 
23
23
  export type ListViewerStates = HydrationMap<ListViewerState>
24
24
 
25
+ export type ListMembershipState = {
26
+ actorListItemUri?: string
27
+ }
28
+ // list uri => actor did => state
29
+ export type ListMembershipStates = HydrationMap<
30
+ HydrationMap<ListMembershipState>
31
+ >
32
+
25
33
  export type Follow = RecordInfo<FollowRecord>
26
34
  export type Follows = HydrationMap<Follow>
27
35
 
@@ -43,6 +43,8 @@ import {
43
43
  GraphHydrator,
44
44
  ListAggs,
45
45
  ListItems,
46
+ ListMembershipState,
47
+ ListMembershipStates,
46
48
  ListViewerStates,
47
49
  Lists,
48
50
  RelationshipPair,
@@ -109,6 +111,7 @@ export type HydrationState = {
109
111
  postgates?: Postgates
110
112
  lists?: Lists
111
113
  listAggs?: ListAggs
114
+ listMemberships?: ListMembershipStates
112
115
  listViewers?: ListViewerStates
113
116
  listItems?: ListItems
114
117
  likes?: Likes
@@ -363,6 +366,45 @@ export class Hydrator {
363
366
  return mergeStates(profileState, { listItems, ctx })
364
367
  }
365
368
 
369
+ async hydrateListsMembership(
370
+ uris: string[],
371
+ did: string,
372
+ ctx: HydrateCtx,
373
+ ): Promise<HydrationState> {
374
+ const [
375
+ actorsHydrationState,
376
+ listsHydrationState,
377
+ { listitemUris: listItemUris },
378
+ ] = await Promise.all([
379
+ this.hydrateProfiles([did], ctx),
380
+ this.hydrateLists(uris, ctx),
381
+ this.dataplane.getListMembership({
382
+ actorDid: did,
383
+ listUris: uris,
384
+ }),
385
+ ])
386
+
387
+ // mapping uri -> did -> { actorListItemUri }
388
+ const listMemberships = new HydrationMap(
389
+ uris.map((uri, i) => {
390
+ const listItemUri = listItemUris[i]
391
+ return [
392
+ uri,
393
+ new HydrationMap<ListMembershipState>([
394
+ listItemUri
395
+ ? [did, { actorListItemUri: listItemUri }]
396
+ : [did, null],
397
+ ]),
398
+ ]
399
+ }),
400
+ )
401
+
402
+ return mergeManyStates(actorsHydrationState, listsHydrationState, {
403
+ listMemberships,
404
+ ctx,
405
+ })
406
+ }
407
+
366
408
  // app.bsky.feed.defs#postView
367
409
  // - post
368
410
  // - profile
@@ -1356,6 +1398,7 @@ export const mergeStates = (
1356
1398
  postgates: mergeMaps(stateA.postgates, stateB.postgates),
1357
1399
  lists: mergeMaps(stateA.lists, stateB.lists),
1358
1400
  listAggs: mergeMaps(stateA.listAggs, stateB.listAggs),
1401
+ listMemberships: mergeMaps(stateA.listMemberships, stateB.listMemberships),
1359
1402
  listViewers: mergeMaps(stateA.listViewers, stateB.listViewers),
1360
1403
  listItems: mergeMaps(stateA.listItems, stateB.listItems),
1361
1404
  likes: mergeMaps(stateA.likes, stateB.likes),