@atproto/bsky 0.0.123 → 0.0.125

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/api/app/bsky/notification/listNotifications.d.ts +7 -0
  3. package/dist/api/app/bsky/notification/listNotifications.d.ts.map +1 -1
  4. package/dist/api/app/bsky/notification/listNotifications.js +21 -5
  5. package/dist/api/app/bsky/notification/listNotifications.js.map +1 -1
  6. package/dist/config.d.ts +6 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +24 -15
  9. package/dist/config.js.map +1 -1
  10. package/dist/context.d.ts +6 -1
  11. package/dist/context.d.ts.map +1 -1
  12. package/dist/context.js +6 -0
  13. package/dist/context.js.map +1 -1
  14. package/dist/data-plane/client/hosts.d.ts +37 -0
  15. package/dist/data-plane/client/hosts.d.ts.map +1 -0
  16. package/dist/data-plane/client/hosts.js +106 -0
  17. package/dist/data-plane/client/hosts.js.map +1 -0
  18. package/dist/data-plane/client/index.d.ts +13 -0
  19. package/dist/data-plane/client/index.d.ts.map +1 -0
  20. package/dist/data-plane/client/index.js +133 -0
  21. package/dist/data-plane/client/index.js.map +1 -0
  22. package/dist/data-plane/{client.d.ts → client/util.d.ts} +3 -10
  23. package/dist/data-plane/client/util.d.ts.map +1 -0
  24. package/dist/data-plane/client/util.js +85 -0
  25. package/dist/data-plane/client/util.js.map +1 -0
  26. package/dist/data-plane/server/db/pagination.d.ts +69 -9
  27. package/dist/data-plane/server/db/pagination.d.ts.map +1 -1
  28. package/dist/data-plane/server/db/pagination.js +114 -14
  29. package/dist/data-plane/server/db/pagination.js.map +1 -1
  30. package/dist/data-plane/server/routes/notifs.d.ts.map +1 -1
  31. package/dist/data-plane/server/routes/notifs.js +3 -5
  32. package/dist/data-plane/server/routes/notifs.js.map +1 -1
  33. package/dist/etcd.d.ts +25 -0
  34. package/dist/etcd.d.ts.map +1 -0
  35. package/dist/etcd.js +109 -0
  36. package/dist/etcd.js.map +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +14 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/lexicon/index.d.ts +6 -0
  41. package/dist/lexicon/index.d.ts.map +1 -1
  42. package/dist/lexicon/index.js +12 -0
  43. package/dist/lexicon/index.js.map +1 -1
  44. package/dist/lexicon/lexicons.d.ts +412 -158
  45. package/dist/lexicon/lexicons.d.ts.map +1 -1
  46. package/dist/lexicon/lexicons.js +222 -81
  47. package/dist/lexicon/lexicons.js.map +1 -1
  48. package/dist/lexicon/types/app/bsky/embed/video.d.ts +1 -0
  49. package/dist/lexicon/types/app/bsky/embed/video.d.ts.map +1 -1
  50. package/dist/lexicon/types/app/bsky/embed/video.js.map +1 -1
  51. package/dist/lexicon/types/app/bsky/labeler/defs.d.ts +7 -0
  52. package/dist/lexicon/types/app/bsky/labeler/defs.d.ts.map +1 -1
  53. package/dist/lexicon/types/app/bsky/labeler/defs.js.map +1 -1
  54. package/dist/lexicon/types/app/bsky/labeler/service.d.ts +7 -0
  55. package/dist/lexicon/types/app/bsky/labeler/service.d.ts.map +1 -1
  56. package/dist/lexicon/types/app/bsky/labeler/service.js.map +1 -1
  57. package/dist/lexicon/types/com/atproto/identity/defs.d.ts +17 -0
  58. package/dist/lexicon/types/com/atproto/identity/defs.d.ts.map +1 -0
  59. package/dist/lexicon/types/com/atproto/identity/defs.js +16 -0
  60. package/dist/lexicon/types/com/atproto/identity/defs.js.map +1 -0
  61. package/dist/lexicon/types/com/atproto/identity/refreshIdentity.d.ts +39 -0
  62. package/dist/lexicon/types/com/atproto/identity/refreshIdentity.d.ts.map +1 -0
  63. package/dist/lexicon/types/com/atproto/identity/refreshIdentity.js +7 -0
  64. package/dist/lexicon/types/com/atproto/identity/refreshIdentity.js.map +1 -0
  65. package/dist/lexicon/types/com/atproto/identity/resolveDid.d.ts +40 -0
  66. package/dist/lexicon/types/com/atproto/identity/resolveDid.d.ts.map +1 -0
  67. package/dist/lexicon/types/com/atproto/identity/resolveDid.js +7 -0
  68. package/dist/lexicon/types/com/atproto/identity/resolveDid.js.map +1 -0
  69. package/dist/lexicon/types/com/atproto/identity/resolveHandle.d.ts +1 -0
  70. package/dist/lexicon/types/com/atproto/identity/resolveHandle.d.ts.map +1 -1
  71. package/dist/lexicon/types/com/atproto/identity/resolveIdentity.d.ts +36 -0
  72. package/dist/lexicon/types/com/atproto/identity/resolveIdentity.d.ts.map +1 -0
  73. package/dist/lexicon/types/com/atproto/identity/resolveIdentity.js +7 -0
  74. package/dist/lexicon/types/com/atproto/identity/resolveIdentity.js.map +1 -0
  75. package/dist/lexicon/types/com/atproto/moderation/defs.d.ts +2 -0
  76. package/dist/lexicon/types/com/atproto/moderation/defs.d.ts.map +1 -1
  77. package/dist/lexicon/types/com/atproto/repo/listRecords.d.ts +0 -4
  78. package/dist/lexicon/types/com/atproto/repo/listRecords.d.ts.map +1 -1
  79. package/dist/lexicon/types/com/atproto/repo/listRecords.js.map +1 -1
  80. package/dist/lexicon/types/com/atproto/sync/getRecord.d.ts +0 -2
  81. package/dist/lexicon/types/com/atproto/sync/getRecord.d.ts.map +1 -1
  82. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts +1 -30
  83. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts.map +1 -1
  84. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js +0 -27
  85. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js.map +1 -1
  86. package/dist/logger.d.ts +1 -0
  87. package/dist/logger.d.ts.map +1 -1
  88. package/dist/logger.js +2 -1
  89. package/dist/logger.js.map +1 -1
  90. package/dist/views/index.d.ts.map +1 -1
  91. package/dist/views/index.js +6 -3
  92. package/dist/views/index.js.map +1 -1
  93. package/package.json +14 -13
  94. package/src/api/app/bsky/notification/listNotifications.ts +28 -6
  95. package/src/config.ts +45 -15
  96. package/src/context.ts +12 -1
  97. package/src/data-plane/client/hosts.ts +103 -0
  98. package/src/data-plane/client/index.ts +123 -0
  99. package/src/data-plane/client/util.ts +66 -0
  100. package/src/data-plane/server/db/pagination.ts +158 -35
  101. package/src/data-plane/server/routes/notifs.ts +4 -9
  102. package/src/etcd.ts +90 -0
  103. package/src/index.ts +26 -2
  104. package/src/lexicon/index.ts +36 -0
  105. package/src/lexicon/lexicons.ts +243 -84
  106. package/src/lexicon/types/app/bsky/embed/video.ts +1 -0
  107. package/src/lexicon/types/app/bsky/labeler/defs.ts +7 -0
  108. package/src/lexicon/types/app/bsky/labeler/service.ts +7 -0
  109. package/src/lexicon/types/com/atproto/identity/defs.ts +30 -0
  110. package/src/lexicon/types/com/atproto/identity/refreshIdentity.ts +52 -0
  111. package/src/lexicon/types/com/atproto/identity/resolveDid.ts +52 -0
  112. package/src/lexicon/types/com/atproto/identity/resolveHandle.ts +1 -0
  113. package/src/lexicon/types/com/atproto/identity/resolveIdentity.ts +48 -0
  114. package/src/lexicon/types/com/atproto/moderation/defs.ts +3 -0
  115. package/src/lexicon/types/com/atproto/repo/listRecords.ts +0 -4
  116. package/src/lexicon/types/com/atproto/sync/getRecord.ts +0 -2
  117. package/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +0 -59
  118. package/src/logger.ts +2 -0
  119. package/src/views/index.ts +6 -3
  120. package/tests/etcd.test.ts +301 -0
  121. package/tests/views/__snapshots__/labeler-service.test.ts.snap +46 -0
  122. package/tests/views/__snapshots__/notifications.test.ts.snap +3 -3
  123. package/tests/views/labeler-service.test.ts +50 -1
  124. package/tests/views/notifications.test.ts +190 -10
  125. package/tsconfig.build.tsbuildinfo +1 -1
  126. package/tsconfig.tests.tsbuildinfo +1 -1
  127. package/dist/data-plane/client.d.ts.map +0 -1
  128. package/dist/data-plane/client.js +0 -156
  129. package/dist/data-plane/client.js.map +0 -1
  130. package/src/data-plane/client.ts +0 -154
@@ -20,10 +20,6 @@ export interface QueryParams {
20
20
  /** The number of records to return. */
21
21
  limit: number
22
22
  cursor?: string
23
- /** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */
24
- rkeyStart?: string
25
- /** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */
26
- rkeyEnd?: string
27
23
  /** Flag to reverse the order of the returned records. */
28
24
  reverse?: boolean
29
25
  }
@@ -19,8 +19,6 @@ export interface QueryParams {
19
19
  collection: string
20
20
  /** Record Key */
21
21
  rkey: string
22
- /** DEPRECATED: referenced a repo commit by CID, and retrieved record as of that commit */
23
- commit?: string
24
22
  }
25
23
 
26
24
  export type InputSchema = undefined
@@ -22,9 +22,6 @@ export type OutputSchema =
22
22
  | $Typed<Sync>
23
23
  | $Typed<Identity>
24
24
  | $Typed<Account>
25
- | $Typed<Handle>
26
- | $Typed<Migrate>
27
- | $Typed<Tombstone>
28
25
  | $Typed<Info>
29
26
  | { $type: string }
30
27
  export type HandlerError = ErrorFrame<'FutureCursor' | 'ConsumerTooSlow'>
@@ -150,62 +147,6 @@ export function validateAccount<V>(v: V) {
150
147
  return validate<Account & V>(v, id, hashAccount)
151
148
  }
152
149
 
153
- /** DEPRECATED -- Use #identity event instead */
154
- export interface Handle {
155
- $type?: 'com.atproto.sync.subscribeRepos#handle'
156
- seq: number
157
- did: string
158
- handle: string
159
- time: string
160
- }
161
-
162
- const hashHandle = 'handle'
163
-
164
- export function isHandle<V>(v: V) {
165
- return is$typed(v, id, hashHandle)
166
- }
167
-
168
- export function validateHandle<V>(v: V) {
169
- return validate<Handle & V>(v, id, hashHandle)
170
- }
171
-
172
- /** DEPRECATED -- Use #account event instead */
173
- export interface Migrate {
174
- $type?: 'com.atproto.sync.subscribeRepos#migrate'
175
- seq: number
176
- did: string
177
- migrateTo: string | null
178
- time: string
179
- }
180
-
181
- const hashMigrate = 'migrate'
182
-
183
- export function isMigrate<V>(v: V) {
184
- return is$typed(v, id, hashMigrate)
185
- }
186
-
187
- export function validateMigrate<V>(v: V) {
188
- return validate<Migrate & V>(v, id, hashMigrate)
189
- }
190
-
191
- /** DEPRECATED -- Use #account event instead */
192
- export interface Tombstone {
193
- $type?: 'com.atproto.sync.subscribeRepos#tombstone'
194
- seq: number
195
- did: string
196
- time: string
197
- }
198
-
199
- const hashTombstone = 'tombstone'
200
-
201
- export function isTombstone<V>(v: V) {
202
- return is$typed(v, id, hashTombstone)
203
- }
204
-
205
- export function validateTombstone<V>(v: V) {
206
- return validate<Tombstone & V>(v, id, hashTombstone)
207
- }
208
-
209
150
  export interface Info {
210
151
  $type?: 'com.atproto.sync.subscribeRepos#info'
211
152
  name: 'OutdatedCursor' | (string & {})
package/src/logger.ts CHANGED
@@ -15,6 +15,8 @@ export const hydrationLogger: ReturnType<typeof subsystemLogger> =
15
15
  subsystemLogger('bsky:hydration')
16
16
  export const featureGatesLogger: ReturnType<typeof subsystemLogger> =
17
17
  subsystemLogger('bsky:featuregates')
18
+ export const dataplaneLogger: ReturnType<typeof subsystemLogger> =
19
+ subsystemLogger('bsky:dp')
18
20
  export const httpLogger: ReturnType<typeof subsystemLogger> =
19
21
  subsystemLogger('bsky')
20
22
 
@@ -592,12 +592,15 @@ export class Views {
592
592
  ): Un$Typed<LabelerViewDetailed> | undefined {
593
593
  const baseView = this.labeler(did, state)
594
594
  if (!baseView) return
595
- const record = state.labelers?.get(did)
596
- if (!record) return
595
+ const labeler = state.labelers?.get(did)
596
+ if (!labeler) return
597
597
 
598
598
  return {
599
599
  ...baseView,
600
- policies: record.record.policies,
600
+ policies: labeler.record.policies,
601
+ reasonTypes: labeler.record.reasonTypes,
602
+ subjectTypes: labeler.record.subjectTypes,
603
+ subjectCollections: labeler.record.subjectCollections,
601
604
  }
602
605
  }
603
606
 
@@ -0,0 +1,301 @@
1
+ import EventEmitter from 'node:events'
2
+ import { Etcd3, IKeyValue } from 'etcd3'
3
+ import { EtcdHostList } from '../src'
4
+ import { EtcdMap } from '../src/etcd'
5
+
6
+ describe('etcd', () => {
7
+ describe('EtcdMap', () => {
8
+ it('initializes values based on current keys', async () => {
9
+ const etcd = new MockEtcd()
10
+ etcd.watcher.set('service/a', { value: '1' })
11
+ etcd.watcher.set('service/b', { value: '2' })
12
+ etcd.watcher.set('service/c', { value: '3' })
13
+ const map = new EtcdMap(etcd as unknown as Etcd3)
14
+ await map.connect()
15
+ expect(map.get('service/a')).toBe('1')
16
+ expect(map.get('service/b')).toBe('2')
17
+ expect(map.get('service/c')).toBe('3')
18
+ expect([...map.values()]).toEqual(['1', '2', '3'])
19
+ })
20
+
21
+ it('maintains key updates', async () => {
22
+ const etcd = new MockEtcd()
23
+ etcd.watcher.set('service/a', { value: '1' })
24
+ etcd.watcher.set('service/b', { value: '2' })
25
+ etcd.watcher.set('service/c', { value: '3' })
26
+ const map = new EtcdMap(etcd as unknown as Etcd3)
27
+ await map.connect()
28
+ etcd.watcher.set('service/b', { value: '4' })
29
+ expect(map.get('service/a')).toBe('1')
30
+ expect(map.get('service/b')).toBe('4')
31
+ expect(map.get('service/c')).toBe('3')
32
+ expect([...map.values()]).toEqual(['1', '4', '3'])
33
+ })
34
+
35
+ it('maintains key creates', async () => {
36
+ const etcd = new MockEtcd()
37
+ etcd.watcher.set('service/a', { value: '1' })
38
+ const map = new EtcdMap(etcd as unknown as Etcd3)
39
+ await map.connect()
40
+ etcd.watcher.set('service/b', { value: '2' })
41
+ expect(map.get('service/a')).toBe('1')
42
+ expect(map.get('service/b')).toBe('2')
43
+ expect([...map.values()]).toEqual(['1', '2'])
44
+ })
45
+
46
+ it('maintains key deletions', async () => {
47
+ const etcd = new MockEtcd()
48
+ etcd.watcher.set('service/a', { value: '1' })
49
+ etcd.watcher.set('service/b', { value: '2' })
50
+ const map = new EtcdMap(etcd as unknown as Etcd3)
51
+ await map.connect()
52
+ etcd.watcher.del('service/b')
53
+ expect(map.get('service/a')).toBe('1')
54
+ expect(map.get('service/b')).toBe(null)
55
+ expect([...map.values()]).toEqual(['1'])
56
+ })
57
+
58
+ it('notifies of updates', async () => {
59
+ const etcd = new MockEtcd()
60
+ etcd.watcher.set('service/a', { value: '1' })
61
+ etcd.watcher.set('service/b', { value: '2' })
62
+ const map = new EtcdMap(etcd as unknown as Etcd3)
63
+ await map.connect()
64
+ const states: string[][] = [[...map.values()]]
65
+ map.onUpdate((update) => {
66
+ states.push([...update.values()])
67
+ })
68
+ etcd.watcher.set('service/c', { value: '3' })
69
+ etcd.watcher.del('service/b')
70
+ etcd.watcher.set('service/a', { value: '4' })
71
+ expect(states).toEqual([
72
+ ['1', '2'],
73
+ ['1', '2', '3'],
74
+ ['1', '3'],
75
+ ['4', '3'],
76
+ ])
77
+ })
78
+
79
+ it('ignores out-of-order updates', async () => {
80
+ const etcd = new MockEtcd()
81
+ etcd.watcher.set('service/a', { value: '1' })
82
+ const map = new EtcdMap(etcd as unknown as Etcd3)
83
+ await map.connect()
84
+ const states: string[][] = [[...map.values()]]
85
+ map.onUpdate((update) => {
86
+ states.push([...update.values()])
87
+ })
88
+ etcd.watcher.set('service/a', { value: '2' })
89
+ etcd.watcher.set('service/a', { value: '3', overrideRev: 1 }) // old rev
90
+ etcd.watcher.set('service/a', { value: '4' })
91
+ expect(map.get('service/a')).toBe('4')
92
+ expect(states).toEqual([['1'], ['2'], ['4']]) // never witnessed 3
93
+ })
94
+ })
95
+
96
+ describe('EtcdHostList', () => {
97
+ it('initializes values based on current keys', async () => {
98
+ const etcd = new MockEtcd()
99
+ etcd.watcher.set('service/a', { value: 'http://192.168.1.1' })
100
+ etcd.watcher.set('service/b', { value: 'http://192.168.1.2' })
101
+ etcd.watcher.set('service/c', { value: 'http://192.168.1.3' })
102
+ const hostList = new EtcdHostList(etcd as unknown as Etcd3, '')
103
+ await hostList.connect()
104
+ expect([...hostList.get()]).toEqual([
105
+ 'http://192.168.1.1',
106
+ 'http://192.168.1.2',
107
+ 'http://192.168.1.3',
108
+ ])
109
+ })
110
+
111
+ it('maintains key updates', async () => {
112
+ const etcd = new MockEtcd()
113
+ etcd.watcher.set('service/a', { value: 'http://192.168.1.1' })
114
+ etcd.watcher.set('service/b', { value: 'http://192.168.1.2' })
115
+ etcd.watcher.set('service/c', { value: 'http://192.168.1.3' })
116
+ const hostList = new EtcdHostList(etcd as unknown as Etcd3, '')
117
+ await hostList.connect()
118
+ etcd.watcher.set('service/b', { value: 'http://192.168.1.4' })
119
+ expect([...hostList.get()]).toEqual([
120
+ 'http://192.168.1.1',
121
+ 'http://192.168.1.4',
122
+ 'http://192.168.1.3',
123
+ ])
124
+ })
125
+
126
+ it('maintains key creates', async () => {
127
+ const etcd = new MockEtcd()
128
+ etcd.watcher.set('service/a', { value: 'http://192.168.1.1' })
129
+ const hostList = new EtcdHostList(etcd as unknown as Etcd3, '')
130
+ await hostList.connect()
131
+ etcd.watcher.set('service/b', { value: 'http://192.168.1.2' })
132
+ expect([...hostList.get()]).toEqual([
133
+ 'http://192.168.1.1',
134
+ 'http://192.168.1.2',
135
+ ])
136
+ })
137
+
138
+ it('maintains key deletions', async () => {
139
+ const etcd = new MockEtcd()
140
+ etcd.watcher.set('service/a', { value: 'http://192.168.1.1' })
141
+ etcd.watcher.set('service/b', { value: 'http://192.168.1.2' })
142
+ const hostList = new EtcdHostList(etcd as unknown as Etcd3, '')
143
+ await hostList.connect()
144
+ etcd.watcher.del('service/b')
145
+ expect([...hostList.get()]).toEqual(['http://192.168.1.1'])
146
+ })
147
+
148
+ it('notifies of updates', async () => {
149
+ const etcd = new MockEtcd()
150
+ etcd.watcher.set('service/a', { value: 'http://192.168.1.1' })
151
+ etcd.watcher.set('service/b', { value: 'http://192.168.1.2' })
152
+ const hostList = new EtcdHostList(etcd as unknown as Etcd3, '')
153
+ await hostList.connect()
154
+ const states: string[][] = [[...hostList.get()]]
155
+ hostList.onUpdate((updated) => {
156
+ expect([...updated]).toEqual([...hostList.get()])
157
+ states.push([...updated])
158
+ })
159
+ etcd.watcher.set('service/c', { value: 'http://192.168.1.3' })
160
+ etcd.watcher.del('service/b')
161
+ etcd.watcher.set('service/a', { value: 'http://192.168.1.4' })
162
+ expect(states).toEqual([
163
+ ['http://192.168.1.1', 'http://192.168.1.2'],
164
+ ['http://192.168.1.1', 'http://192.168.1.2', 'http://192.168.1.3'],
165
+ ['http://192.168.1.1', 'http://192.168.1.3'],
166
+ ['http://192.168.1.4', 'http://192.168.1.3'],
167
+ ])
168
+ })
169
+
170
+ it('ignores bad host values', async () => {
171
+ const etcd = new MockEtcd()
172
+ etcd.watcher.set('service/a', { value: 'not-a-host' })
173
+ etcd.watcher.set('service/b', { value: 'http://192.168.1.2' })
174
+ const hostList = new EtcdHostList(etcd as unknown as Etcd3, '')
175
+ await hostList.connect()
176
+ expect([...hostList.get()]).toEqual(['http://192.168.1.2'])
177
+ etcd.watcher.set('service/a', { value: 'http://192.168.1.1' })
178
+ etcd.watcher.set('service/c', { value: 'not-a-host' })
179
+ expect([...hostList.get()]).toEqual([
180
+ 'http://192.168.1.1',
181
+ 'http://192.168.1.2',
182
+ ])
183
+ etcd.watcher.set('service/c', { value: 'http://192.168.1.3' })
184
+ expect([...hostList.get()]).toEqual([
185
+ 'http://192.168.1.1',
186
+ 'http://192.168.1.2',
187
+ 'http://192.168.1.3',
188
+ ])
189
+ })
190
+
191
+ it('falls back to static host list when uninitialized or no keys available', async () => {
192
+ const etcd = new MockEtcd()
193
+ const hostList = new EtcdHostList(etcd as unknown as Etcd3, '', [
194
+ 'http://10.0.0.1',
195
+ 'http://10.0.0.2',
196
+ ])
197
+ etcd.watcher.set('service/a', { value: 'http://192.168.1.1' })
198
+ expect([...hostList.get()]).toEqual([
199
+ 'http://10.0.0.1',
200
+ 'http://10.0.0.2',
201
+ ])
202
+ await hostList.connect()
203
+ const states: string[][] = [[...hostList.get()]]
204
+ hostList.onUpdate((updated) => {
205
+ states.push([...updated])
206
+ })
207
+ etcd.watcher.del('service/a')
208
+ etcd.watcher.set('service/b', { value: 'http://192.168.1.2' })
209
+ expect(states).toEqual([
210
+ ['http://192.168.1.1'],
211
+ ['http://10.0.0.1', 'http://10.0.0.2'],
212
+ ['http://192.168.1.2'],
213
+ ])
214
+ })
215
+ })
216
+ })
217
+
218
+ class MockEtcd {
219
+ public watcher = new MockWatcher()
220
+ watch() {
221
+ const watcher = this.watcher
222
+ return {
223
+ prefix() {
224
+ return {
225
+ watcher() {
226
+ return watcher
227
+ },
228
+ }
229
+ },
230
+ }
231
+ }
232
+ getAll() {
233
+ const watcher = this.watcher
234
+ return {
235
+ prefix() {
236
+ return {
237
+ async exec(): Promise<{ kvs: IKeyValue[] }> {
238
+ return { kvs: watcher.getAll() }
239
+ },
240
+ }
241
+ },
242
+ }
243
+ }
244
+ }
245
+
246
+ class MockWatcher extends EventEmitter {
247
+ rev = 1
248
+ kvs: IKeyValue[] = []
249
+ constructor() {
250
+ super()
251
+ process.nextTick(() => this.emit('connected', {}))
252
+ }
253
+ get(key: string): IKeyValue | null {
254
+ const found = this.kvs.find((kv) => kv.key.toString() === key)
255
+ return found ?? null
256
+ }
257
+ getAll(): IKeyValue[] {
258
+ return [...this.kvs]
259
+ }
260
+ set(
261
+ key: string,
262
+ { value, overrideRev }: { value: string; overrideRev?: number },
263
+ ) {
264
+ const found = this.kvs.find((kv) => kv.key.toString() === key)
265
+ const rev = overrideRev ?? ++this.rev
266
+ if (found) {
267
+ found.value = Buffer.from(value)
268
+ found.mod_revision = rev.toString()
269
+ found.version = (parseInt(found.version, 10) + 1).toString()
270
+ this.emit('put', found)
271
+ } else {
272
+ const created = {
273
+ key: Buffer.from(key),
274
+ value: Buffer.from(value),
275
+ create_revision: rev.toString(),
276
+ mod_revision: rev.toString(),
277
+ version: '1',
278
+ lease: '0',
279
+ }
280
+ this.kvs.push(created)
281
+ this.emit('put', created)
282
+ }
283
+ }
284
+ del(key: string) {
285
+ const foundIdx = this.kvs.findIndex((kv) => kv.key.toString() === key)
286
+ if (foundIdx === -1) return
287
+ const [deleted] = this.kvs.splice(foundIdx, 1)
288
+ const rev = ++this.rev
289
+ deleted.value = Buffer.from('')
290
+ deleted.mod_revision = rev.toString()
291
+ deleted.create_revision = '0'
292
+ deleted.version = '0'
293
+ this.emit('delete', deleted)
294
+ }
295
+ on(evt: 'connected', listener: (res: unknown) => void): any
296
+ on(evt: 'put', listener: (kv: IKeyValue) => void): any
297
+ on(evt: 'delete', listener: (kv: IKeyValue) => void): any
298
+ on(evt: string, listener: (...args: any[]) => void) {
299
+ super.on(evt, listener)
300
+ }
301
+ }
@@ -170,3 +170,49 @@ Object {
170
170
  ],
171
171
  }
172
172
  `;
173
+
174
+ exports[`labeler service views returns additional labeler data 1`] = `
175
+ Object {
176
+ "views": Array [
177
+ Object {
178
+ "$type": "app.bsky.labeler.defs#labelerViewDetailed",
179
+ "cid": "cids(0)",
180
+ "creator": Object {
181
+ "associated": Object {
182
+ "labeler": true,
183
+ },
184
+ "did": "user(0)",
185
+ "handle": "carol.test",
186
+ "labels": Array [],
187
+ "viewer": Object {
188
+ "blockedBy": false,
189
+ "following": "record(1)",
190
+ "muted": false,
191
+ },
192
+ },
193
+ "indexedAt": "1970-01-01T00:00:00.000Z",
194
+ "labels": Array [],
195
+ "likeCount": 0,
196
+ "policies": Object {
197
+ "labelValues": Array [
198
+ "spam",
199
+ "!hide",
200
+ "scam",
201
+ "impersonation",
202
+ ],
203
+ },
204
+ "reasonTypes": Array [
205
+ "com.atproto.moderation.defs#reasonOther",
206
+ ],
207
+ "subjectCollections": Array [
208
+ "app.bsky.feed.post",
209
+ ],
210
+ "subjectTypes": Array [
211
+ "record",
212
+ ],
213
+ "uri": "record(0)",
214
+ "viewer": Object {},
215
+ },
216
+ ],
217
+ }
218
+ `;
@@ -384,7 +384,7 @@ Array [
384
384
 
385
385
  exports[`notification views fetches notifications with default priority 1`] = `
386
386
  Object {
387
- "cursor": "0000000000000__bafycid",
387
+ "cursor": "1970-01-01T00:00:00.000Z",
388
388
  "notifications": Array [
389
389
  Object {
390
390
  "author": Object {
@@ -486,7 +486,7 @@ Object {
486
486
 
487
487
  exports[`notification views fetches notifications with explicit priority 1`] = `
488
488
  Object {
489
- "cursor": "0000000000000__bafycid",
489
+ "cursor": "1970-01-01T00:00:00.000Z",
490
490
  "notifications": Array [
491
491
  Object {
492
492
  "author": Object {
@@ -588,7 +588,7 @@ Object {
588
588
 
589
589
  exports[`notification views fetches notifications with explicit priority 2`] = `
590
590
  Object {
591
- "cursor": "0000000000000__bafycid",
591
+ "cursor": "1970-01-01T00:00:00.000Z",
592
592
  "notifications": Array [
593
593
  Object {
594
594
  "author": Object {
@@ -1,5 +1,9 @@
1
1
  import assert from 'node:assert'
2
- import { AtpAgent } from '@atproto/api'
2
+ import {
3
+ AppBskyLabelerDefs,
4
+ AtpAgent,
5
+ ComAtprotoModerationDefs,
6
+ } from '@atproto/api'
3
7
  import { RecordRef, SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env'
4
8
  import { ids } from '../../src/lexicon/lexicons'
5
9
  import { isView as isRecordEmbedView } from '../../src/lexicon/types/app/bsky/embed/record'
@@ -14,6 +18,7 @@ describe('labeler service views', () => {
14
18
  // account dids, for convenience
15
19
  let alice: string
16
20
  let bob: string
21
+ let carol: string
17
22
 
18
23
  let aliceService: RecordRef
19
24
 
@@ -27,6 +32,7 @@ describe('labeler service views', () => {
27
32
  await basicSeed(sc)
28
33
  alice = sc.dids.alice
29
34
  bob = sc.dids.bob
35
+ carol = sc.dids.carol
30
36
 
31
37
  const aliceRes = await pdsAgent.api.com.atproto.repo.createRecord(
32
38
  {
@@ -190,4 +196,47 @@ describe('labeler service views', () => {
190
196
  // Cleanup
191
197
  await network.bsky.ctx.dataplane.untakedownActor({ did: alice })
192
198
  })
199
+
200
+ it(`returns additional labeler data`, async () => {
201
+ await pdsAgent.api.com.atproto.repo.createRecord(
202
+ {
203
+ repo: carol,
204
+ collection: ids.AppBskyLabelerService,
205
+ rkey: 'self',
206
+ record: {
207
+ policies: {
208
+ labelValues: ['spam', '!hide', 'scam', 'impersonation'],
209
+ },
210
+ createdAt: new Date().toISOString(),
211
+ reasonTypes: [ComAtprotoModerationDefs.REASONOTHER],
212
+ subjectTypes: ['record'],
213
+ subjectCollections: ['app.bsky.feed.post'],
214
+ },
215
+ },
216
+ { headers: sc.getHeaders(carol), encoding: 'application/json' },
217
+ )
218
+ await network.processAll()
219
+
220
+ const view = await agent.api.app.bsky.labeler.getServices(
221
+ { dids: [carol], detailed: true },
222
+ {
223
+ headers: await network.serviceHeaders(
224
+ bob,
225
+ ids.AppBskyLabelerGetServices,
226
+ ),
227
+ },
228
+ )
229
+
230
+ const labelerView = view.data.views[0]
231
+ expect(AppBskyLabelerDefs.isLabelerViewDetailed(labelerView)).toBe(true)
232
+ // for TS only
233
+ if (!AppBskyLabelerDefs.isLabelerViewDetailed(labelerView)) return
234
+ expect(labelerView).toBeTruthy()
235
+ expect(labelerView.reasonTypes).toEqual([
236
+ ComAtprotoModerationDefs.REASONOTHER,
237
+ ])
238
+ expect(labelerView.subjectTypes).toEqual(['record'])
239
+ expect(labelerView.subjectCollections).toEqual(['app.bsky.feed.post'])
240
+ expect(forSnapshot(view.data)).toMatchSnapshot()
241
+ })
193
242
  })