@atproto/dev-env 0.0.0-spaces-alpha-20260818163953

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 (187) hide show
  1. package/CHANGELOG.md +3424 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +39 -0
  4. package/bin.js +7 -0
  5. package/dist/bin-multi-pds.d.ts +2 -0
  6. package/dist/bin-multi-pds.d.ts.map +1 -0
  7. package/dist/bin-multi-pds.js +130 -0
  8. package/dist/bin-multi-pds.js.map +1 -0
  9. package/dist/bin.d.ts +2 -0
  10. package/dist/bin.d.ts.map +1 -0
  11. package/dist/bin.js +56 -0
  12. package/dist/bin.js.map +1 -0
  13. package/dist/bsky.d.ts +27 -0
  14. package/dist/bsky.d.ts.map +1 -0
  15. package/dist/bsky.js +165 -0
  16. package/dist/bsky.js.map +1 -0
  17. package/dist/bsync.d.ts +22 -0
  18. package/dist/bsync.d.ts.map +1 -0
  19. package/dist/bsync.js +54 -0
  20. package/dist/bsync.js.map +1 -0
  21. package/dist/const.d.ts +4 -0
  22. package/dist/const.d.ts.map +1 -0
  23. package/dist/const.js +4 -0
  24. package/dist/const.js.map +1 -0
  25. package/dist/env.d.ts +2 -0
  26. package/dist/env.d.ts.map +1 -0
  27. package/dist/env.js +10 -0
  28. package/dist/env.js.map +1 -0
  29. package/dist/feed-gen.d.ts +15 -0
  30. package/dist/feed-gen.d.ts.map +1 -0
  31. package/dist/feed-gen.js +73 -0
  32. package/dist/feed-gen.js.map +1 -0
  33. package/dist/index.d.ts +17 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +17 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/introspect.d.ts +22 -0
  38. package/dist/introspect.d.ts.map +1 -0
  39. package/dist/introspect.js +65 -0
  40. package/dist/introspect.js.map +1 -0
  41. package/dist/mock/data.d.ts +3 -0
  42. package/dist/mock/data.d.ts.map +1 -0
  43. package/dist/mock/data.js +46 -0
  44. package/dist/mock/data.js.map +1 -0
  45. package/dist/mock/img/blur-hash-avatar-b64.d.ts +3 -0
  46. package/dist/mock/img/blur-hash-avatar-b64.d.ts.map +1 -0
  47. package/dist/mock/img/blur-hash-avatar-b64.js +2 -0
  48. package/dist/mock/img/blur-hash-avatar-b64.js.map +1 -0
  49. package/dist/mock/img/labeled-img-b64.d.ts +3 -0
  50. package/dist/mock/img/labeled-img-b64.d.ts.map +1 -0
  51. package/dist/mock/img/labeled-img-b64.js +2 -0
  52. package/dist/mock/img/labeled-img-b64.js.map +1 -0
  53. package/dist/mock/index.d.ts +3 -0
  54. package/dist/mock/index.d.ts.map +1 -0
  55. package/dist/mock/index.js +574 -0
  56. package/dist/mock/index.js.map +1 -0
  57. package/dist/moderator-client.d.ts +58 -0
  58. package/dist/moderator-client.d.ts.map +1 -0
  59. package/dist/moderator-client.js +110 -0
  60. package/dist/moderator-client.js.map +1 -0
  61. package/dist/network-no-appview.d.ts +21 -0
  62. package/dist/network-no-appview.d.ts.map +1 -0
  63. package/dist/network-no-appview.js +74 -0
  64. package/dist/network-no-appview.js.map +1 -0
  65. package/dist/network.d.ts +32 -0
  66. package/dist/network.d.ts.map +1 -0
  67. package/dist/network.js +190 -0
  68. package/dist/network.js.map +1 -0
  69. package/dist/ozone.d.ts +31 -0
  70. package/dist/ozone.d.ts.map +1 -0
  71. package/dist/ozone.js +254 -0
  72. package/dist/ozone.js.map +1 -0
  73. package/dist/pds.d.ts +23 -0
  74. package/dist/pds.d.ts.map +1 -0
  75. package/dist/pds.js +97 -0
  76. package/dist/pds.js.map +1 -0
  77. package/dist/plc.d.ts +14 -0
  78. package/dist/plc.d.ts.map +1 -0
  79. package/dist/plc.js +28 -0
  80. package/dist/plc.js.map +1 -0
  81. package/dist/seed/author-feed.d.ts +4 -0
  82. package/dist/seed/author-feed.d.ts.map +1 -0
  83. package/dist/seed/author-feed.js +50 -0
  84. package/dist/seed/author-feed.js.map +1 -0
  85. package/dist/seed/basic.d.ts +16 -0
  86. package/dist/seed/basic.d.ts.map +1 -0
  87. package/dist/seed/basic.js +113 -0
  88. package/dist/seed/basic.js.map +1 -0
  89. package/dist/seed/client.d.ts +151 -0
  90. package/dist/seed/client.d.ts.map +1 -0
  91. package/dist/seed/client.js +365 -0
  92. package/dist/seed/client.js.map +1 -0
  93. package/dist/seed/follows.d.ts +4 -0
  94. package/dist/seed/follows.d.ts.map +1 -0
  95. package/dist/seed/follows.js +55 -0
  96. package/dist/seed/follows.js.map +1 -0
  97. package/dist/seed/index.d.ts +11 -0
  98. package/dist/seed/index.d.ts.map +1 -0
  99. package/dist/seed/index.js +11 -0
  100. package/dist/seed/index.js.map +1 -0
  101. package/dist/seed/likes.d.ts +4 -0
  102. package/dist/seed/likes.d.ts.map +1 -0
  103. package/dist/seed/likes.js +37 -0
  104. package/dist/seed/likes.js.map +1 -0
  105. package/dist/seed/quotes.d.ts +4 -0
  106. package/dist/seed/quotes.d.ts.map +1 -0
  107. package/dist/seed/quotes.js +21 -0
  108. package/dist/seed/quotes.js.map +1 -0
  109. package/dist/seed/reposts.d.ts +4 -0
  110. package/dist/seed/reposts.d.ts.map +1 -0
  111. package/dist/seed/reposts.js +17 -0
  112. package/dist/seed/reposts.js.map +1 -0
  113. package/dist/seed/thread-v2.d.ts +227 -0
  114. package/dist/seed/thread-v2.d.ts.map +1 -0
  115. package/dist/seed/thread-v2.js +755 -0
  116. package/dist/seed/thread-v2.js.map +1 -0
  117. package/dist/seed/users-bulk.d.ts +3 -0
  118. package/dist/seed/users-bulk.d.ts.map +1 -0
  119. package/dist/seed/users-bulk.js +224 -0
  120. package/dist/seed/users-bulk.js.map +1 -0
  121. package/dist/seed/users.d.ts +3 -0
  122. package/dist/seed/users.d.ts.map +1 -0
  123. package/dist/seed/users.js +45 -0
  124. package/dist/seed/users.js.map +1 -0
  125. package/dist/seed/verifications.d.ts +5 -0
  126. package/dist/seed/verifications.d.ts.map +1 -0
  127. package/dist/seed/verifications.js +164 -0
  128. package/dist/seed/verifications.js.map +1 -0
  129. package/dist/service-profile-lexicon.d.ts +11 -0
  130. package/dist/service-profile-lexicon.d.ts.map +1 -0
  131. package/dist/service-profile-lexicon.js +115 -0
  132. package/dist/service-profile-lexicon.js.map +1 -0
  133. package/dist/service-profile-ozone.d.ts +18 -0
  134. package/dist/service-profile-ozone.d.ts.map +1 -0
  135. package/dist/service-profile-ozone.js +333 -0
  136. package/dist/service-profile-ozone.js.map +1 -0
  137. package/dist/service-profile.d.ts +24 -0
  138. package/dist/service-profile.d.ts.map +1 -0
  139. package/dist/service-profile.js +61 -0
  140. package/dist/service-profile.js.map +1 -0
  141. package/dist/types.d.ts +61 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +2 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/util.d.ts +14 -0
  146. package/dist/util.d.ts.map +1 -0
  147. package/dist/util.js +86 -0
  148. package/dist/util.js.map +1 -0
  149. package/package.json +69 -0
  150. package/src/bin-multi-pds.ts +166 -0
  151. package/src/bin.ts +64 -0
  152. package/src/bsky.ts +198 -0
  153. package/src/bsync.ts +66 -0
  154. package/src/const.ts +3 -0
  155. package/src/env.ts +9 -0
  156. package/src/feed-gen.ts +96 -0
  157. package/src/index.ts +17 -0
  158. package/src/introspect.ts +91 -0
  159. package/src/mock/data.ts +46 -0
  160. package/src/mock/img/blur-hash-avatar-b64.ts +1 -0
  161. package/src/mock/img/labeled-img-b64.ts +1 -0
  162. package/src/mock/index.ts +702 -0
  163. package/src/moderator-client.ts +267 -0
  164. package/src/network-no-appview.ts +89 -0
  165. package/src/network.ts +243 -0
  166. package/src/ozone.ts +287 -0
  167. package/src/pds.ts +118 -0
  168. package/src/plc.ts +33 -0
  169. package/src/seed/author-feed.ts +98 -0
  170. package/src/seed/basic.ts +189 -0
  171. package/src/seed/client.ts +676 -0
  172. package/src/seed/follows.ts +57 -0
  173. package/src/seed/index.ts +11 -0
  174. package/src/seed/likes.ts +43 -0
  175. package/src/seed/quotes.ts +53 -0
  176. package/src/seed/reposts.ts +18 -0
  177. package/src/seed/thread-v2.ts +925 -0
  178. package/src/seed/users-bulk.ts +229 -0
  179. package/src/seed/users.ts +64 -0
  180. package/src/seed/verifications.ts +254 -0
  181. package/src/service-profile-lexicon.ts +127 -0
  182. package/src/service-profile-ozone.ts +375 -0
  183. package/src/service-profile.ts +95 -0
  184. package/src/types.ts +68 -0
  185. package/src/util.ts +118 -0
  186. package/tsconfig.build.json +25 -0
  187. package/tsconfig.json +4 -0
@@ -0,0 +1,925 @@
1
+ import type { AppBskyFeedPost } from '@atproto/api'
2
+ import type { DatabaseSchema } from '@atproto/bsky'
3
+ import type { DidString, HandleString } from '@atproto/syntax'
4
+ import type { TestNetworkNoAppView } from '../network-no-appview.js'
5
+ import type { TestNetwork } from '../network.js'
6
+ import type { RecordRef, SeedClient } from './client.js'
7
+
8
+ type User = {
9
+ id: string
10
+ did: DidString
11
+ email: string
12
+ handle: HandleString
13
+ password: string
14
+ displayName: string
15
+ description: string
16
+ selfLabels: undefined
17
+ }
18
+
19
+ function createUserStub(name: string): User {
20
+ return {
21
+ id: name,
22
+ // @ts-ignore overwritten during seeding
23
+ did: undefined,
24
+ email: `${name}@test.com`,
25
+ handle: `${name}.test` as HandleString,
26
+ password: `${name}-pass`,
27
+ displayName: name,
28
+ description: `hi im ${name} label_me`,
29
+ selfLabels: undefined,
30
+ }
31
+ }
32
+
33
+ async function createUsers<T extends readonly string[]>(
34
+ seedClient: SeedClient<TestNetwork | TestNetworkNoAppView>,
35
+ prefix: string,
36
+ handles: T,
37
+ ) {
38
+ const stubs = handles.reduce((acc, handle) => {
39
+ acc[handle] = createUserStub(`${prefix}-${handle}`)
40
+ return acc
41
+ }, {}) as Record<(typeof handles)[number], User>
42
+ const users = await Promise.all(
43
+ handles
44
+ .map((h) => prefix + '-' + h)
45
+ .map(async (handle) => {
46
+ const user = createUserStub(handle)
47
+ await seedClient.createAccount(handle, user)
48
+ user.did = seedClient.dids[handle]
49
+ return user
50
+ }),
51
+ )
52
+ return users.reduce((acc, user) => {
53
+ const id = user.id.split('-')[1]
54
+ acc[id].did = user.did
55
+ return acc
56
+ }, stubs)
57
+ }
58
+
59
+ type ReplyFn = (
60
+ replyAuthor: User,
61
+ overridesOrCb?: Partial<AppBskyFeedPost.Record> | ReplyCb,
62
+ maybeReplyCb?: ReplyCb,
63
+ ) => Promise<void>
64
+
65
+ type ReplyCb = (r: ReplyFn) => Promise<void>
66
+
67
+ export const TAG_BUMP_DOWN = 'down'
68
+ export const TAG_HIDE = 'hide'
69
+
70
+ const rootReplyFnBuilder = <T extends TestNetworkNoAppView>(
71
+ sc: SeedClient<T>,
72
+ root: RecordRef,
73
+ parent: RecordRef,
74
+ prevBreadcrumbs: string,
75
+ posts: Record<
76
+ string,
77
+ | Awaited<ReturnType<SeedClient['post']>>
78
+ | Awaited<ReturnType<SeedClient['reply']>>
79
+ >,
80
+ ): ReplyFn => {
81
+ let index = 0
82
+ return async (
83
+ replyAuthor: User,
84
+ overridesOrCb?: Partial<AppBskyFeedPost.Record> | ReplyCb,
85
+ maybeReplyCb?: ReplyCb,
86
+ ) => {
87
+ let overrides: Partial<AppBskyFeedPost.Record> | undefined
88
+ let replyCb: ReplyCb | undefined
89
+ if (overridesOrCb && typeof overridesOrCb === 'function') {
90
+ replyCb = overridesOrCb
91
+ } else {
92
+ overrides = overridesOrCb
93
+ replyCb = maybeReplyCb
94
+ }
95
+
96
+ const breadcrumbs = prevBreadcrumbs
97
+ ? `${prevBreadcrumbs}.${index++}`
98
+ : `${index++}`
99
+ const text = breadcrumbs
100
+ const reply = await sc.reply(
101
+ replyAuthor.did,
102
+ root,
103
+ parent,
104
+ text,
105
+ undefined,
106
+ undefined,
107
+ overrides,
108
+ )
109
+ posts[breadcrumbs] = reply
110
+ // Await for this post to be processed before replying to it.
111
+ if (replyCb) {
112
+ await sc.network.processAll()
113
+ await replyCb(rootReplyFnBuilder(sc, root, reply.ref, breadcrumbs, posts))
114
+ }
115
+ }
116
+ }
117
+
118
+ const createThread = async <T extends TestNetworkNoAppView>(
119
+ sc: SeedClient<T>,
120
+ rootAuthor: User,
121
+ overridesOrCb?: Partial<AppBskyFeedPost.Record> | ReplyCb,
122
+ maybeReplyCb?: ReplyCb,
123
+ ) => {
124
+ let overrides: Partial<AppBskyFeedPost.Record> | undefined
125
+ let replyCb: ReplyCb | undefined
126
+ if (overridesOrCb && typeof overridesOrCb === 'function') {
127
+ replyCb = overridesOrCb
128
+ } else {
129
+ overrides = overridesOrCb
130
+ replyCb = maybeReplyCb
131
+ }
132
+
133
+ const replies: Record<string, Awaited<ReturnType<SeedClient['reply']>>> = {}
134
+ const breadcrumbs = ''
135
+ const text = 'root'
136
+ const root = await sc.post(
137
+ rootAuthor.did,
138
+ text,
139
+ undefined,
140
+ undefined,
141
+ undefined,
142
+ overrides,
143
+ )
144
+ // Await for this post to be processed before replying to it.
145
+ if (replyCb) {
146
+ await sc.network.processAll()
147
+ await replyCb(
148
+ rootReplyFnBuilder(sc, root.ref, root.ref, breadcrumbs, replies),
149
+ )
150
+ }
151
+ return { root, replies }
152
+ }
153
+
154
+ export async function simple(sc: SeedClient<TestNetwork>, prefix = 'simple') {
155
+ const users = await createUsers(sc, prefix, [
156
+ 'op',
157
+ 'alice',
158
+ 'bob',
159
+ 'carol',
160
+ ] as const)
161
+ const { op, alice, bob, carol } = users
162
+
163
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
164
+ await r(op, async (r) => {
165
+ await r(op)
166
+ })
167
+ await r(alice)
168
+ await r(bob, async (r) => {
169
+ await r(alice)
170
+ })
171
+ await r(carol)
172
+ })
173
+
174
+ return {
175
+ seedClient: sc,
176
+ users,
177
+ root,
178
+ r,
179
+ }
180
+ }
181
+
182
+ export async function long(sc: SeedClient<TestNetwork>) {
183
+ const users = await createUsers(sc, 'long', [
184
+ 'op',
185
+ 'alice',
186
+ 'bob',
187
+ 'carol',
188
+ 'dan',
189
+ ] as const)
190
+ const { op, alice, bob, carol, dan } = users
191
+
192
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
193
+ await r(op, async (r) => {
194
+ await r(op, async (r) => {
195
+ await r(op, async (r) => {
196
+ await r(op, async (r) => {
197
+ await r(op)
198
+ })
199
+ })
200
+ await r(op)
201
+ })
202
+ })
203
+
204
+ await r(alice)
205
+ await r(bob)
206
+ await r(carol)
207
+
208
+ await r(op, async (r) => {
209
+ await r(op, async (r) => {
210
+ await r(alice, async (r) => {
211
+ await r(op, async (r) => {
212
+ await r(op)
213
+ })
214
+ })
215
+ })
216
+ })
217
+
218
+ await r(alice)
219
+ await r(bob)
220
+ await r(carol)
221
+ })
222
+
223
+ await sc.like(op.did, r['5'].ref)
224
+ await sc.like(bob.did, r['5'].ref)
225
+ await sc.like(carol.did, r['5'].ref)
226
+ await sc.like(dan.did, r['5'].ref)
227
+
228
+ await sc.like(op.did, r['6'].ref)
229
+ await sc.like(alice.did, r['6'].ref)
230
+ await sc.like(carol.did, r['6'].ref)
231
+
232
+ await sc.like(op.did, r['7'].ref)
233
+ await sc.like(bob.did, r['7'].ref)
234
+
235
+ return {
236
+ seedClient: sc,
237
+ users,
238
+ root,
239
+ r,
240
+ }
241
+ }
242
+
243
+ export async function deep(sc: SeedClient<TestNetwork>) {
244
+ const users = await createUsers(sc, 'deep', ['op'] as const)
245
+ const { op } = users
246
+
247
+ let counter = 0
248
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
249
+ const recursiveReply = async (rFn: ReplyFn) => {
250
+ if (counter < 18) {
251
+ counter++
252
+ await rFn(op, async (r) => recursiveReply(r))
253
+ }
254
+ }
255
+ await recursiveReply(r)
256
+ })
257
+
258
+ return {
259
+ seedClient: sc,
260
+ users,
261
+ root,
262
+ r,
263
+ }
264
+ }
265
+
266
+ export async function branchingFactor(sc: SeedClient<TestNetwork>) {
267
+ const users = await createUsers(sc, 'bf', ['op', 'bob'] as const)
268
+ const { op, bob } = users
269
+
270
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
271
+ await r(bob, async (r) => {
272
+ await r(bob, async (r) => {
273
+ await r(bob)
274
+ await r(bob)
275
+ await r(bob)
276
+ await r(bob)
277
+ })
278
+ await r(bob, async (r) => {
279
+ await r(bob)
280
+ await r(bob)
281
+ await r(bob)
282
+ await r(bob)
283
+ })
284
+ await r(bob, async (r) => {
285
+ await r(bob)
286
+ await r(bob)
287
+ await r(bob)
288
+ await r(bob)
289
+ })
290
+ await r(bob, async (r) => {
291
+ await r(bob)
292
+ await r(bob)
293
+ await r(bob)
294
+ await r(bob)
295
+ })
296
+ })
297
+ await r(bob, async (r) => {
298
+ await r(bob, async (r) => {
299
+ // This is the only case in this seed where a reply has 1 reply instead of 4,
300
+ // to have cases of different lengths in the same tree.
301
+ await r(bob)
302
+ })
303
+ await r(bob, async (r) => {
304
+ await r(bob)
305
+ await r(bob)
306
+ await r(bob)
307
+ await r(bob)
308
+ })
309
+ await r(bob, async (r) => {
310
+ await r(bob)
311
+ await r(bob)
312
+ await r(bob)
313
+ await r(bob)
314
+ })
315
+ await r(bob, async (r) => {
316
+ await r(bob)
317
+ await r(bob)
318
+ await r(bob)
319
+ await r(bob)
320
+ })
321
+ })
322
+ await r(bob, async (r) => {
323
+ await r(bob, async (r) => {
324
+ await r(bob)
325
+ await r(bob)
326
+ await r(bob)
327
+ await r(bob)
328
+ })
329
+ await r(bob, async (r) => {
330
+ await r(bob)
331
+ await r(bob)
332
+ await r(bob)
333
+ await r(bob)
334
+ })
335
+ await r(bob, async (r) => {
336
+ await r(bob)
337
+ await r(bob)
338
+ await r(bob)
339
+ await r(bob)
340
+ })
341
+ await r(bob, async (r) => {
342
+ await r(bob)
343
+ await r(bob)
344
+ await r(bob)
345
+ await r(bob)
346
+ })
347
+ })
348
+ await r(bob, async (r) => {
349
+ await r(bob, async (r) => {
350
+ await r(bob)
351
+ await r(bob)
352
+ await r(bob)
353
+ await r(bob)
354
+ // This is the only case in this seed where a reply has 5 replies instead of 4,
355
+ // to have cases of different lengths in the same tree.
356
+ await r(bob)
357
+ })
358
+ await r(bob, async (r) => {
359
+ await r(bob)
360
+ await r(bob)
361
+ await r(bob)
362
+ await r(bob)
363
+ })
364
+ await r(bob, async (r) => {
365
+ await r(bob)
366
+ await r(bob)
367
+ await r(bob)
368
+ await r(bob)
369
+ })
370
+ await r(bob, async (r) => {
371
+ await r(bob)
372
+ await r(bob)
373
+ await r(bob)
374
+ await r(bob)
375
+ })
376
+ })
377
+ })
378
+
379
+ return {
380
+ seedClient: sc,
381
+ users,
382
+ root,
383
+ r,
384
+ }
385
+ }
386
+
387
+ export async function annotateMoreReplies(sc: SeedClient<TestNetwork>) {
388
+ const users = await createUsers(sc, 'mr', ['op', 'alice'] as const)
389
+ const { op, alice } = users
390
+
391
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
392
+ await r(alice, async (r) => {
393
+ await r(alice, async (r) => {
394
+ await r(alice, async (r) => {
395
+ await r(alice, async (r) => {
396
+ // more replies... (below = 4)
397
+ await r(alice, async (r) => {
398
+ await r(alice)
399
+ })
400
+ await r(alice)
401
+ await r(alice, async (r) => {
402
+ await r(alice, async (r) => {
403
+ await r(alice)
404
+ })
405
+ })
406
+ await r(alice)
407
+ await r(alice)
408
+ })
409
+ })
410
+ })
411
+ await r(alice, async (r) => {
412
+ await r(alice, async (r) => {
413
+ await r(alice)
414
+ })
415
+ })
416
+ })
417
+ await r(alice, async (r) => {
418
+ await r(alice, async (r) => {
419
+ await r(alice)
420
+ await r(alice)
421
+ // more replies... (branchingFactor = 2)
422
+ await r(alice)
423
+ await r(alice)
424
+ await r(alice)
425
+ })
426
+ await r(alice, async (r) => {
427
+ await r(alice)
428
+ await r(alice)
429
+ })
430
+ // more replies... (branchingFactor = 2)
431
+ await r(alice)
432
+ })
433
+ await r(alice) // anchor reply not limited by branchingFactor
434
+ })
435
+
436
+ return {
437
+ seedClient: sc,
438
+ users,
439
+ root,
440
+ r,
441
+ }
442
+ }
443
+
444
+ export async function annotateOP(sc: SeedClient<TestNetwork>) {
445
+ const users = await createUsers(sc, 'op', ['op', 'alice', 'bob'] as const)
446
+ const { op, alice, bob } = users
447
+
448
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
449
+ await r(op, async (r) => {
450
+ await r(op, async (r) => {
451
+ await r(op)
452
+ })
453
+ })
454
+ await r(alice, async (r) => {
455
+ await r(alice)
456
+ })
457
+ await r(op, async (r) => {
458
+ await r(bob, async (r) => {
459
+ await r(op)
460
+ })
461
+ })
462
+ })
463
+
464
+ return {
465
+ seedClient: sc,
466
+ users,
467
+ root,
468
+ r,
469
+ }
470
+ }
471
+
472
+ export async function sort(sc: SeedClient<TestNetwork>) {
473
+ const users = await createUsers(sc, 'sort', [
474
+ 'op',
475
+ 'alice',
476
+ 'bob',
477
+ 'carol',
478
+ ] as const)
479
+ const { op, alice, bob, carol } = users
480
+
481
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
482
+ // 0 likes
483
+ await r(alice, async (r) => {
484
+ await r(carol) // 0 likes
485
+ await r(alice) // 2 likes
486
+ await r(bob) // 1 like
487
+ })
488
+ // 3 likes
489
+ await r(carol, async (r) => {
490
+ await r(bob) // 1 like
491
+ await r(carol) // 2 likes
492
+ await r(alice) // 0 likes
493
+ })
494
+ // 2 likes
495
+ await r(bob, async (r) => {
496
+ await r(bob) // 2 likes
497
+ await r(alice) // 1 like
498
+ await r(carol) // 0 likes
499
+ })
500
+ })
501
+
502
+ // likes depth 1
503
+ await sc.like(alice.did, r['2'].ref)
504
+ await sc.like(carol.did, r['2'].ref)
505
+ await sc.like(op.did, r['1'].ref) // op like
506
+ await sc.like(bob.did, r['1'].ref)
507
+ await sc.like(carol.did, r['1'].ref)
508
+
509
+ // likes depth 2
510
+ await sc.like(bob.did, r['0.1'].ref)
511
+ await sc.like(carol.did, r['0.1'].ref)
512
+ await sc.like(op.did, r['0.2'].ref) // op like
513
+ await sc.like(bob.did, r['1.1'].ref)
514
+ await sc.like(carol.did, r['1.1'].ref)
515
+ await sc.like(bob.did, r['1.0'].ref)
516
+ await sc.like(bob.did, r['2.0'].ref)
517
+ await sc.like(carol.did, r['2.0'].ref)
518
+ await sc.like(bob.did, r['2.1'].ref)
519
+
520
+ return {
521
+ seedClient: sc,
522
+ users,
523
+ root,
524
+ r,
525
+ }
526
+ }
527
+
528
+ export async function bumpOpAndViewer(sc: SeedClient<TestNetwork>) {
529
+ const users = await createUsers(sc, 'bumpOV', [
530
+ 'op',
531
+ 'viewer',
532
+ 'alice',
533
+ 'bob',
534
+ 'carol',
535
+ ] as const)
536
+ const { op, viewer, alice, bob, carol } = users
537
+
538
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
539
+ // 1 like
540
+ await r(alice, async (r) => {
541
+ await r(carol) // 0 likes
542
+ await r(alice) // 2 likes
543
+ await r(bob) // 1 like
544
+ await r(viewer) // 0 likes
545
+ await r(op) // 0 likes
546
+ })
547
+ // 3 likes
548
+ await r(carol, async (r) => {
549
+ await r(bob) // 1 like
550
+ await r(carol) // 2 likes
551
+ await r(op) // 0 likes
552
+ await r(viewer) // 1 like
553
+ await r(alice) // 0 likes
554
+ })
555
+ // 2 likes
556
+ await r(bob, async (r) => {
557
+ await r(viewer) // 0 likes
558
+ await r(bob) // 4 likes
559
+ await r(op) // 0 likes
560
+ await r(alice) // 1 like
561
+ await r(carol) // 1 like
562
+ })
563
+ // 0 likes
564
+ await r(op, async (r) => {
565
+ await r(viewer) // 0 likes
566
+ await r(bob) // 0 likes
567
+ await r(op) // 0 likes
568
+ await r(alice) // 0 likes
569
+ await r(carol) // 0 likes
570
+ })
571
+ // 0 likes
572
+ await r(viewer, async (r) => {
573
+ await r(bob) // 1 like
574
+ await r(carol) // 1 like
575
+ await r(op) // 0 likes
576
+ await r(viewer) // 0 likes
577
+ await r(alice) // 0 likes
578
+ })
579
+ })
580
+
581
+ // likes depth 1
582
+ await sc.like(alice.did, r['2'].ref)
583
+ await sc.like(carol.did, r['2'].ref)
584
+ await sc.like(viewer.did, r['0'].ref)
585
+ await sc.like(op.did, r['1'].ref) // op like
586
+ await sc.like(bob.did, r['1'].ref)
587
+ await sc.like(carol.did, r['1'].ref)
588
+
589
+ // likes depth 2
590
+ await sc.like(bob.did, r['0.1'].ref)
591
+ await sc.like(carol.did, r['0.1'].ref)
592
+ await sc.like(op.did, r['0.2'].ref) // op like
593
+ await sc.like(bob.did, r['1.1'].ref)
594
+ await sc.like(carol.did, r['1.1'].ref)
595
+ await sc.like(bob.did, r['1.0'].ref)
596
+ await sc.like(alice.did, r['2.1'].ref)
597
+ await sc.like(bob.did, r['2.1'].ref)
598
+ await sc.like(carol.did, r['2.1'].ref)
599
+ await sc.like(viewer.did, r['2.1'].ref)
600
+ await sc.like(bob.did, r['1.3'].ref)
601
+ await sc.like(bob.did, r['2.3'].ref)
602
+ await sc.like(viewer.did, r['2.4'].ref)
603
+ await sc.like(viewer.did, r['4.0'].ref)
604
+ await sc.like(alice.did, r['4.1'].ref)
605
+
606
+ return {
607
+ seedClient: sc,
608
+ users,
609
+ root,
610
+ r,
611
+ }
612
+ }
613
+
614
+ export async function bumpGroupSorting(sc: SeedClient<TestNetwork>) {
615
+ const users = await createUsers(sc, 'bumpGS', [
616
+ 'op',
617
+ 'viewer',
618
+ 'alice',
619
+ ] as const)
620
+ const { op, viewer, alice } = users
621
+
622
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
623
+ await r(viewer)
624
+ await r(op)
625
+ await r(alice)
626
+ await r(op)
627
+ await r(viewer)
628
+ await r(op)
629
+ await r(alice)
630
+ await r(viewer)
631
+ })
632
+
633
+ return {
634
+ seedClient: sc,
635
+ users,
636
+ root,
637
+ r,
638
+ }
639
+ }
640
+
641
+ export async function bumpFollows(sc: SeedClient<TestNetwork>) {
642
+ const users = await createUsers(sc, 'bumpF', [
643
+ 'op',
644
+ 'viewerF',
645
+ 'viewerNoF',
646
+ 'alice',
647
+ 'bob',
648
+ 'carol',
649
+ ] as const)
650
+
651
+ const { op, viewerF, viewerNoF, alice, bob, carol } = users
652
+
653
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
654
+ await r(alice)
655
+ await r(bob)
656
+ await r(carol)
657
+ await r(op)
658
+ await r(viewerF)
659
+ await r(viewerNoF)
660
+ })
661
+
662
+ await sc.follow(viewerF.did, alice.did)
663
+ await sc.follow(viewerF.did, bob.did)
664
+ // Does not follow carol.
665
+
666
+ return {
667
+ seedClient: sc,
668
+ users,
669
+ root,
670
+ r,
671
+ }
672
+ }
673
+
674
+ export async function blockDeletionAuth(
675
+ sc: SeedClient<TestNetwork>,
676
+ labelerDid: string,
677
+ ) {
678
+ const users = await createUsers(sc, 'bda', [
679
+ 'op',
680
+ 'opBlocked',
681
+ 'alice',
682
+ 'auth',
683
+ 'blocker',
684
+ 'blocked',
685
+ ] as const)
686
+
687
+ const { op, opBlocked, alice, auth, blocker, blocked } = users
688
+
689
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
690
+ // 1p block, hidden for `blocked`.
691
+ await r(blocker, async (r) => {
692
+ await r(alice)
693
+ })
694
+
695
+ // 3p block, hidden for all.
696
+ await r(opBlocked, async (r) => {
697
+ await r(op)
698
+ await r(alice)
699
+ })
700
+
701
+ // Deleted, hidden for all.
702
+ await r(alice, async (r) => {
703
+ await r(alice)
704
+ })
705
+
706
+ // User configured to only be seen by authenticated users.
707
+ // Requires the test sets a `!no-unauthenticated` label for this user.
708
+ await r(auth, async (r) => {
709
+ // Another auth-only to show that the parent chain is preserved in the thread.
710
+ await r(auth, async (r) => {
711
+ await r(alice)
712
+ })
713
+ })
714
+ })
715
+
716
+ await sc.deletePost(alice.did, r['2'].ref.uri)
717
+ await sc.block(blocker.did, blocked.did)
718
+ await sc.block(op.did, opBlocked.did)
719
+
720
+ const db = sc.network.bsky.db.db
721
+ await createLabel(db, {
722
+ src: labelerDid,
723
+ uri: auth.did,
724
+ cid: '',
725
+ val: '!no-unauthenticated',
726
+ })
727
+
728
+ return {
729
+ seedClient: sc,
730
+ users,
731
+ root,
732
+ r,
733
+ }
734
+ }
735
+
736
+ export async function mutes(sc: SeedClient<TestNetwork>) {
737
+ const users = await createUsers(sc, 'mutes', [
738
+ 'op',
739
+ 'opMuted',
740
+ 'alice',
741
+ 'muted',
742
+ 'muter',
743
+ ] as const)
744
+
745
+ const { op, opMuted, alice, muted, muter } = users
746
+
747
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
748
+ await r(opMuted, async (r) => {
749
+ await r(alice)
750
+ await r(muted)
751
+ })
752
+
753
+ await r(muted, async (r) => {
754
+ await r(opMuted)
755
+ await r(alice)
756
+ })
757
+ })
758
+
759
+ await sc.mute(op.did, opMuted.did)
760
+ await sc.mute(muter.did, muted.did)
761
+
762
+ return {
763
+ seedClient: sc,
764
+ users,
765
+ root,
766
+ r,
767
+ }
768
+ }
769
+
770
+ export async function threadgated(sc: SeedClient<TestNetwork>) {
771
+ const users = await createUsers(sc, 'tg', [
772
+ 'op',
773
+ 'opMuted',
774
+ 'viewer',
775
+ 'alice',
776
+ 'bob',
777
+ ] as const)
778
+
779
+ const { op, opMuted, alice, bob } = users
780
+
781
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
782
+ // Muted moves down below threadgated.
783
+ await r(opMuted)
784
+
785
+ // Threadgated moves down.
786
+ await r(alice, async (r) => {
787
+ await r(alice)
788
+ await r(bob)
789
+ await r(op) // OP moves up.
790
+ })
791
+
792
+ await r(bob, async (r) => {
793
+ await r(alice)
794
+ await r(bob) // Threadgated is omitted if fetched from the root.
795
+ await r(op) // OP moves down.
796
+ })
797
+ })
798
+
799
+ await sc.agent.app.bsky.feed.threadgate.create(
800
+ {
801
+ repo: op.did,
802
+ rkey: root.ref.uri.rkey,
803
+ },
804
+ {
805
+ post: root.ref.uriStr,
806
+ createdAt: new Date().toISOString(),
807
+ hiddenReplies: [r['1'].ref.uriStr, r['2.1'].ref.uriStr],
808
+ },
809
+ sc.getHeaders(op.did),
810
+ )
811
+
812
+ // Just throw a mute there to test the prioritization between muted and threadgated.
813
+ await sc.mute(op.did, opMuted.did)
814
+
815
+ return {
816
+ seedClient: sc,
817
+ users,
818
+ root,
819
+ r,
820
+ }
821
+ }
822
+
823
+ export async function tags(sc: SeedClient<TestNetwork>) {
824
+ const users = await createUsers(sc, 'tags', [
825
+ 'op',
826
+ 'alice',
827
+ 'down',
828
+ 'following',
829
+ 'hide',
830
+ 'viewer',
831
+ ] as const)
832
+
833
+ const { op, alice, down, following, hide, viewer } = users
834
+
835
+ const { root, replies: r } = await createThread(sc, op, async (r) => {
836
+ await r(alice, async (r) => {
837
+ await r(alice)
838
+ await r(down)
839
+ await r(hide)
840
+ })
841
+ await r(down, async (r) => {
842
+ await r(alice)
843
+ await r(down)
844
+ await r(hide)
845
+ })
846
+ await r(hide, async (r) => {
847
+ await r(alice)
848
+ await r(down)
849
+ await r(hide)
850
+ })
851
+ await r(op)
852
+ await r(viewer)
853
+ await r(following)
854
+ })
855
+
856
+ await sc.network.processAll()
857
+
858
+ await sc.follow(viewer.did, following.did)
859
+
860
+ const db = sc.network.bsky.db.db
861
+ await createTag(db, { uri: r['1'].ref.uriStr, val: TAG_BUMP_DOWN })
862
+ await createTag(db, { uri: r['0.1'].ref.uriStr, val: TAG_BUMP_DOWN })
863
+ await createTag(db, { uri: r['1.1'].ref.uriStr, val: TAG_BUMP_DOWN })
864
+ await createTag(db, { uri: r['2.1'].ref.uriStr, val: TAG_BUMP_DOWN })
865
+
866
+ await createTag(db, { uri: r['2'].ref.uriStr, val: TAG_HIDE })
867
+ await createTag(db, { uri: r['0.2'].ref.uriStr, val: TAG_HIDE })
868
+ await createTag(db, { uri: r['1.2'].ref.uriStr, val: TAG_HIDE })
869
+ await createTag(db, { uri: r['2.2'].ref.uriStr, val: TAG_HIDE })
870
+
871
+ // Neither tag affect op, viewer.
872
+ await createTag(db, { uri: r['3'].ref.uriStr, val: TAG_BUMP_DOWN })
873
+ await createTag(db, { uri: r['4'].ref.uriStr, val: TAG_HIDE })
874
+
875
+ // Tags affect following depending on the config to prioritize following.
876
+ await createTag(db, { uri: r['5'].ref.uriStr, val: TAG_HIDE })
877
+
878
+ return {
879
+ seedClient: sc,
880
+ users,
881
+ root,
882
+ r,
883
+ }
884
+ }
885
+
886
+ const createLabel = async (
887
+ db: DatabaseSchema,
888
+ opts: {
889
+ src: string
890
+ uri: string
891
+ cid: string
892
+ val: string
893
+ exp?: string
894
+ },
895
+ ) => {
896
+ await db
897
+ .insertInto('label')
898
+ .values({
899
+ uri: opts.uri,
900
+ cid: opts.cid,
901
+ val: opts.val,
902
+ cts: new Date().toISOString(),
903
+ exp: opts.exp ?? null,
904
+ neg: false,
905
+ src: opts.src,
906
+ })
907
+ .execute()
908
+ }
909
+
910
+ const createTag = async (
911
+ db: DatabaseSchema,
912
+ opts: {
913
+ uri: string
914
+ val: string
915
+ },
916
+ ) => {
917
+ await db
918
+ .updateTable('record')
919
+ .set({
920
+ tags: JSON.stringify([opts.val]),
921
+ })
922
+ .where('uri', '=', opts.uri)
923
+ .returningAll()
924
+ .execute()
925
+ }