@atproto/bsky 0.0.124 → 0.0.126
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.
- package/CHANGELOG.md +22 -0
- package/dist/api/app/bsky/notification/listNotifications.d.ts +7 -0
- package/dist/api/app/bsky/notification/listNotifications.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/listNotifications.js +21 -5
- package/dist/api/app/bsky/notification/listNotifications.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -15
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +6 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -0
- package/dist/context.js.map +1 -1
- package/dist/data-plane/client/hosts.d.ts +37 -0
- package/dist/data-plane/client/hosts.d.ts.map +1 -0
- package/dist/data-plane/client/hosts.js +106 -0
- package/dist/data-plane/client/hosts.js.map +1 -0
- package/dist/data-plane/client/index.d.ts +13 -0
- package/dist/data-plane/client/index.d.ts.map +1 -0
- package/dist/data-plane/client/index.js +133 -0
- package/dist/data-plane/client/index.js.map +1 -0
- package/dist/data-plane/{client.d.ts → client/util.d.ts} +3 -10
- package/dist/data-plane/client/util.d.ts.map +1 -0
- package/dist/data-plane/client/util.js +85 -0
- package/dist/data-plane/client/util.js.map +1 -0
- package/dist/data-plane/server/db/pagination.d.ts +69 -9
- package/dist/data-plane/server/db/pagination.d.ts.map +1 -1
- package/dist/data-plane/server/db/pagination.js +114 -14
- package/dist/data-plane/server/db/pagination.js.map +1 -1
- package/dist/data-plane/server/routes/notifs.d.ts.map +1 -1
- package/dist/data-plane/server/routes/notifs.js +3 -5
- package/dist/data-plane/server/routes/notifs.js.map +1 -1
- package/dist/data-plane/server/subscription.d.ts.map +1 -1
- package/dist/data-plane/server/subscription.js +6 -0
- package/dist/data-plane/server/subscription.js.map +1 -1
- package/dist/etcd.d.ts +25 -0
- package/dist/etcd.d.ts.map +1 -0
- package/dist/etcd.js +109 -0
- package/dist/etcd.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +6 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +12 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +304 -156
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +168 -80
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/video.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/embed/video.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/video.js.map +1 -1
- package/dist/lexicon/types/com/atproto/identity/defs.d.ts +17 -0
- package/dist/lexicon/types/com/atproto/identity/defs.d.ts.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/defs.js +16 -0
- package/dist/lexicon/types/com/atproto/identity/defs.js.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/refreshIdentity.d.ts +39 -0
- package/dist/lexicon/types/com/atproto/identity/refreshIdentity.d.ts.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/refreshIdentity.js +7 -0
- package/dist/lexicon/types/com/atproto/identity/refreshIdentity.js.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveDid.d.ts +40 -0
- package/dist/lexicon/types/com/atproto/identity/resolveDid.d.ts.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveDid.js +7 -0
- package/dist/lexicon/types/com/atproto/identity/resolveDid.js.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveHandle.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveHandle.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/identity/resolveIdentity.d.ts +36 -0
- package/dist/lexicon/types/com/atproto/identity/resolveIdentity.d.ts.map +1 -0
- package/dist/lexicon/types/com/atproto/identity/resolveIdentity.js +7 -0
- package/dist/lexicon/types/com/atproto/identity/resolveIdentity.js.map +1 -0
- package/dist/lexicon/types/com/atproto/repo/listRecords.d.ts +0 -4
- package/dist/lexicon/types/com/atproto/repo/listRecords.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/repo/listRecords.js.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/getRecord.d.ts +0 -2
- package/dist/lexicon/types/com/atproto/sync/getRecord.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts +1 -30
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js +0 -27
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js.map +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +2 -1
- package/dist/logger.js.map +1 -1
- package/package.json +16 -15
- package/src/api/app/bsky/notification/listNotifications.ts +28 -6
- package/src/config.ts +45 -15
- package/src/context.ts +12 -1
- package/src/data-plane/client/hosts.ts +103 -0
- package/src/data-plane/client/index.ts +123 -0
- package/src/data-plane/client/util.ts +66 -0
- package/src/data-plane/server/db/pagination.ts +158 -35
- package/src/data-plane/server/routes/notifs.ts +4 -9
- package/src/data-plane/server/subscription.ts +7 -2
- package/src/etcd.ts +90 -0
- package/src/index.ts +26 -2
- package/src/lexicon/index.ts +36 -0
- package/src/lexicon/lexicons.ts +183 -83
- package/src/lexicon/types/app/bsky/embed/video.ts +1 -0
- package/src/lexicon/types/com/atproto/identity/defs.ts +30 -0
- package/src/lexicon/types/com/atproto/identity/refreshIdentity.ts +52 -0
- package/src/lexicon/types/com/atproto/identity/resolveDid.ts +52 -0
- package/src/lexicon/types/com/atproto/identity/resolveHandle.ts +1 -0
- package/src/lexicon/types/com/atproto/identity/resolveIdentity.ts +48 -0
- package/src/lexicon/types/com/atproto/repo/listRecords.ts +0 -4
- package/src/lexicon/types/com/atproto/sync/getRecord.ts +0 -2
- package/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +0 -59
- package/src/logger.ts +2 -0
- package/tests/etcd.test.ts +301 -0
- package/tests/views/__snapshots__/notifications.test.ts.snap +3 -3
- package/tests/views/notifications.test.ts +190 -10
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
- package/dist/data-plane/client.d.ts.map +0 -1
- package/dist/data-plane/client.js +0 -156
- package/dist/data-plane/client.js.map +0 -1
- package/src/data-plane/client.ts +0 -154
|
@@ -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
|
+
}
|
|
@@ -384,7 +384,7 @@ Array [
|
|
|
384
384
|
|
|
385
385
|
exports[`notification views fetches notifications with default priority 1`] = `
|
|
386
386
|
Object {
|
|
387
|
-
"cursor": "
|
|
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": "
|
|
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": "
|
|
591
|
+
"cursor": "1970-01-01T00:00:00.000Z",
|
|
592
592
|
"notifications": Array [
|
|
593
593
|
Object {
|
|
594
594
|
"author": Object {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AtpAgent } from '@atproto/api'
|
|
2
2
|
import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env'
|
|
3
|
+
import { delayCursor } from '../../src/api/app/bsky/notification/listNotifications'
|
|
3
4
|
import { ids } from '../../src/lexicon/lexicons'
|
|
4
5
|
import { Notification } from '../../src/lexicon/types/app/bsky/notification/listNotifications'
|
|
5
6
|
import { forSnapshot, paginateAll } from '../_util'
|
|
@@ -497,17 +498,196 @@ describe('notification views', () => {
|
|
|
497
498
|
expect(results(paginatedAll)).toEqual(results([full.data]))
|
|
498
499
|
})
|
|
499
500
|
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
501
|
+
describe('notifications delay', () => {
|
|
502
|
+
const notificationsDelayMs = 5_000
|
|
503
|
+
|
|
504
|
+
let delayNetwork: TestNetwork
|
|
505
|
+
let delayAgent: AtpAgent
|
|
506
|
+
let delaySc: SeedClient
|
|
507
|
+
let delayAlice: string
|
|
508
|
+
|
|
509
|
+
beforeAll(async () => {
|
|
510
|
+
delayNetwork = await TestNetwork.create({
|
|
511
|
+
bsky: {
|
|
512
|
+
notificationsDelayMs,
|
|
509
513
|
},
|
|
514
|
+
dbPostgresSchema: 'bsky_views_notifications_delay',
|
|
515
|
+
})
|
|
516
|
+
delayAgent = delayNetwork.bsky.getClient()
|
|
517
|
+
delaySc = delayNetwork.getSeedClient()
|
|
518
|
+
await basicSeed(delaySc)
|
|
519
|
+
await delayNetwork.processAll()
|
|
520
|
+
delayAlice = delaySc.dids.alice
|
|
521
|
+
|
|
522
|
+
// Add to reply chain, post ancestors: alice -> bob -> alice -> carol.
|
|
523
|
+
// Should have added one notification for each of alice and bob.
|
|
524
|
+
await delaySc.reply(
|
|
525
|
+
delaySc.dids.carol,
|
|
526
|
+
delaySc.posts[delayAlice][1].ref,
|
|
527
|
+
delaySc.replies[delayAlice][0].ref,
|
|
528
|
+
'indeed',
|
|
529
|
+
)
|
|
530
|
+
await delayNetwork.processAll()
|
|
531
|
+
|
|
532
|
+
// @NOTE: Use fake timers after inserting seed data,
|
|
533
|
+
// to avoid inserting all notifications with the same timestamp.
|
|
534
|
+
jest.useFakeTimers({
|
|
535
|
+
doNotFake: [
|
|
536
|
+
'nextTick',
|
|
537
|
+
'performance',
|
|
538
|
+
'setImmediate',
|
|
539
|
+
'setInterval',
|
|
540
|
+
'setTimeout',
|
|
541
|
+
],
|
|
542
|
+
})
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
afterAll(async () => {
|
|
546
|
+
jest.useRealTimers()
|
|
547
|
+
await delayNetwork.close()
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
it('paginates', async () => {
|
|
551
|
+
const firstNotification = await delayNetwork.bsky.db.db
|
|
552
|
+
.selectFrom('notification')
|
|
553
|
+
.selectAll()
|
|
554
|
+
.limit(1)
|
|
555
|
+
.orderBy('sortAt', 'asc')
|
|
556
|
+
.executeTakeFirstOrThrow()
|
|
557
|
+
// Sets the system time to when the first notification happened.
|
|
558
|
+
// At this point we won't have any notifications that already crossed the delay threshold.
|
|
559
|
+
jest.setSystemTime(new Date(firstNotification.sortAt))
|
|
560
|
+
|
|
561
|
+
const results = (results) =>
|
|
562
|
+
sort(results.flatMap((res) => res.notifications))
|
|
563
|
+
const paginator = async (cursor?: string) => {
|
|
564
|
+
const res =
|
|
565
|
+
await delayAgent.api.app.bsky.notification.listNotifications(
|
|
566
|
+
{ cursor, limit: 6 },
|
|
567
|
+
{
|
|
568
|
+
headers: await delayNetwork.serviceHeaders(
|
|
569
|
+
delayAlice,
|
|
570
|
+
ids.AppBskyNotificationListNotifications,
|
|
571
|
+
),
|
|
572
|
+
},
|
|
573
|
+
)
|
|
574
|
+
return res.data
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const paginatedAllBeforeDelay = await paginateAll(paginator)
|
|
578
|
+
paginatedAllBeforeDelay.forEach((res) =>
|
|
579
|
+
expect(res.notifications.length).toBe(0),
|
|
580
|
+
)
|
|
581
|
+
const fullBeforeDelay =
|
|
582
|
+
await delayAgent.api.app.bsky.notification.listNotifications(
|
|
583
|
+
{},
|
|
584
|
+
{
|
|
585
|
+
headers: await delayNetwork.serviceHeaders(
|
|
586
|
+
delayAlice,
|
|
587
|
+
ids.AppBskyNotificationListNotifications,
|
|
588
|
+
),
|
|
589
|
+
},
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
expect(fullBeforeDelay.data.notifications.length).toEqual(0)
|
|
593
|
+
expect(results(paginatedAllBeforeDelay)).toEqual(
|
|
594
|
+
results([fullBeforeDelay.data]),
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
const lastNotification = await delayNetwork.bsky.db.db
|
|
598
|
+
.selectFrom('notification')
|
|
599
|
+
.selectAll()
|
|
600
|
+
.limit(1)
|
|
601
|
+
.orderBy('sortAt', 'desc')
|
|
602
|
+
.executeTakeFirstOrThrow()
|
|
603
|
+
// Sets the system time to when the last notification happened and the delay has elapsed.
|
|
604
|
+
// At this point we all notifications already crossed the delay threshold.
|
|
605
|
+
jest.setSystemTime(
|
|
606
|
+
new Date(
|
|
607
|
+
new Date(lastNotification.sortAt).getTime() +
|
|
608
|
+
notificationsDelayMs +
|
|
609
|
+
1,
|
|
610
|
+
),
|
|
510
611
|
)
|
|
511
|
-
|
|
612
|
+
|
|
613
|
+
const paginatedAllAfterDelay = await paginateAll(paginator)
|
|
614
|
+
paginatedAllAfterDelay.forEach((res) =>
|
|
615
|
+
expect(res.notifications.length).toBeLessThanOrEqual(6),
|
|
616
|
+
)
|
|
617
|
+
const fullAfterDelay =
|
|
618
|
+
await delayAgent.api.app.bsky.notification.listNotifications(
|
|
619
|
+
{},
|
|
620
|
+
{
|
|
621
|
+
headers: await delayNetwork.serviceHeaders(
|
|
622
|
+
delayAlice,
|
|
623
|
+
ids.AppBskyNotificationListNotifications,
|
|
624
|
+
),
|
|
625
|
+
},
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
expect(fullAfterDelay.data.notifications.length).toEqual(13)
|
|
629
|
+
expect(results(paginatedAllAfterDelay)).toEqual(
|
|
630
|
+
results([fullAfterDelay.data]),
|
|
631
|
+
)
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
describe('cursor delay', () => {
|
|
635
|
+
const delay0s = 0
|
|
636
|
+
const delay5s = 5_000
|
|
637
|
+
|
|
638
|
+
const now = '2021-01-01T01:00:00.000Z'
|
|
639
|
+
const nowMinus2s = '2021-01-01T00:59:58.000Z'
|
|
640
|
+
const nowMinus5s = '2021-01-01T00:59:55.000Z'
|
|
641
|
+
const nowMinus8s = '2021-01-01T00:59:52.000Z'
|
|
642
|
+
|
|
643
|
+
beforeAll(async () => {
|
|
644
|
+
jest.useFakeTimers({ doNotFake: ['performance'] })
|
|
645
|
+
jest.setSystemTime(new Date(now))
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
afterAll(async () => {
|
|
649
|
+
jest.useRealTimers()
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
describe('for undefined cursor', () => {
|
|
653
|
+
it('returns now minus delay', async () => {
|
|
654
|
+
const delayedCursor = delayCursor(undefined, delay5s)
|
|
655
|
+
expect(delayedCursor).toBe(nowMinus5s)
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
it('returns now if delay is 0', async () => {
|
|
659
|
+
const delayedCursor = delayCursor(undefined, delay0s)
|
|
660
|
+
expect(delayedCursor).toBe(now)
|
|
661
|
+
})
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
describe('for defined cursor', () => {
|
|
665
|
+
it('returns original cursor if delay is 0', async () => {
|
|
666
|
+
const originalCursor = nowMinus2s
|
|
667
|
+
const delayedCursor = delayCursor(originalCursor, delay0s)
|
|
668
|
+
expect(delayedCursor).toBe(originalCursor)
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
it('returns "now minus delay" for cursor that is after that', async () => {
|
|
672
|
+
// Cursor is "now - 2s", should become "now - 5s"
|
|
673
|
+
const originalCursor = nowMinus2s
|
|
674
|
+
const cursor = delayCursor(originalCursor, delay5s)
|
|
675
|
+
expect(cursor).toBe(nowMinus5s)
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
it('returns original cursor for cursor that is before "now minus delay"', async () => {
|
|
679
|
+
// Cursor is "now - 8s", should stay like that.
|
|
680
|
+
const originalCursor = nowMinus8s
|
|
681
|
+
const cursor = delayCursor(originalCursor, delay5s)
|
|
682
|
+
expect(cursor).toBe(originalCursor)
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
it('passes through a non-date cursor', async () => {
|
|
686
|
+
const originalCursor = '123_abc'
|
|
687
|
+
const cursor = delayCursor(originalCursor, delay5s)
|
|
688
|
+
expect(cursor).toBe(originalCursor)
|
|
689
|
+
})
|
|
690
|
+
})
|
|
691
|
+
})
|
|
512
692
|
})
|
|
513
693
|
})
|