@atproto/pds 0.4.99 → 0.4.101

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 (82) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/actor-store/repo/transactor.d.ts +5 -5
  3. package/dist/actor-store/repo/transactor.d.ts.map +1 -1
  4. package/dist/actor-store/repo/transactor.js +38 -18
  5. package/dist/actor-store/repo/transactor.js.map +1 -1
  6. package/dist/actor-store/repo/util.d.ts +5 -0
  7. package/dist/actor-store/repo/util.d.ts.map +1 -0
  8. package/dist/actor-store/repo/util.js +25 -0
  9. package/dist/actor-store/repo/util.js.map +1 -0
  10. package/dist/api/com/atproto/repo/applyWrites.js +1 -1
  11. package/dist/api/com/atproto/repo/applyWrites.js.map +1 -1
  12. package/dist/api/com/atproto/repo/createRecord.d.ts.map +1 -1
  13. package/dist/api/com/atproto/repo/createRecord.js +3 -3
  14. package/dist/api/com/atproto/repo/createRecord.js.map +1 -1
  15. package/dist/api/com/atproto/repo/deleteRecord.js +1 -1
  16. package/dist/api/com/atproto/repo/deleteRecord.js.map +1 -1
  17. package/dist/api/com/atproto/repo/putRecord.d.ts.map +1 -1
  18. package/dist/api/com/atproto/repo/putRecord.js +1 -1
  19. package/dist/api/com/atproto/repo/putRecord.js.map +1 -1
  20. package/dist/api/com/atproto/server/activateAccount.d.ts.map +1 -1
  21. package/dist/api/com/atproto/server/activateAccount.js +4 -1
  22. package/dist/api/com/atproto/server/activateAccount.js.map +1 -1
  23. package/dist/api/com/atproto/server/createAccount.js +1 -1
  24. package/dist/api/com/atproto/server/createAccount.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/lexicon/lexicons.d.ts +100 -34
  28. package/dist/lexicon/lexicons.d.ts.map +1 -1
  29. package/dist/lexicon/lexicons.js +71 -15
  30. package/dist/lexicon/lexicons.js.map +1 -1
  31. package/dist/lexicon/types/com/atproto/sync/getRepoStatus.d.ts +1 -1
  32. package/dist/lexicon/types/com/atproto/sync/getRepoStatus.d.ts.map +1 -1
  33. package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts +1 -1
  34. package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts.map +1 -1
  35. package/dist/lexicon/types/com/atproto/sync/listRepos.js.map +1 -1
  36. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts +25 -7
  37. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts.map +1 -1
  38. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js +9 -0
  39. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js.map +1 -1
  40. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -2
  41. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  42. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
  43. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  44. package/dist/repo/types.d.ts +12 -1
  45. package/dist/repo/types.d.ts.map +1 -1
  46. package/dist/repo/types.js.map +1 -1
  47. package/dist/scripts/rebuild-repo.d.ts.map +1 -1
  48. package/dist/scripts/rebuild-repo.js +4 -1
  49. package/dist/scripts/rebuild-repo.js.map +1 -1
  50. package/dist/sequencer/events.d.ts +22 -16
  51. package/dist/sequencer/events.d.ts.map +1 -1
  52. package/dist/sequencer/events.js +31 -39
  53. package/dist/sequencer/events.js.map +1 -1
  54. package/dist/sequencer/sequencer.d.ts +2 -3
  55. package/dist/sequencer/sequencer.d.ts.map +1 -1
  56. package/dist/sequencer/sequencer.js +2 -2
  57. package/dist/sequencer/sequencer.js.map +1 -1
  58. package/package.json +6 -6
  59. package/src/actor-store/repo/transactor.ts +47 -21
  60. package/src/actor-store/repo/util.ts +22 -0
  61. package/src/api/com/atproto/repo/applyWrites.ts +1 -1
  62. package/src/api/com/atproto/repo/createRecord.ts +28 -31
  63. package/src/api/com/atproto/repo/deleteRecord.ts +1 -1
  64. package/src/api/com/atproto/repo/putRecord.ts +3 -3
  65. package/src/api/com/atproto/server/activateAccount.ts +4 -1
  66. package/src/api/com/atproto/server/createAccount.ts +1 -1
  67. package/src/index.ts +1 -1
  68. package/src/lexicon/lexicons.ts +80 -16
  69. package/src/lexicon/types/com/atproto/sync/getRepoStatus.ts +8 -1
  70. package/src/lexicon/types/com/atproto/sync/listRepos.ts +8 -1
  71. package/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +41 -6
  72. package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -2
  73. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +1 -0
  74. package/src/repo/types.ts +14 -1
  75. package/src/scripts/rebuild-repo.ts +4 -1
  76. package/src/sequencer/events.ts +35 -49
  77. package/src/sequencer/sequencer.ts +3 -5
  78. package/tests/crud.test.ts +1 -1
  79. package/tests/sequencer.test.ts +1 -5
  80. package/tests/sync/invertible-ops.test.ts +104 -0
  81. package/tsconfig.build.tsbuildinfo +1 -1
  82. package/tsconfig.tests.tsbuildinfo +1 -1
@@ -23,7 +23,14 @@ export interface OutputSchema {
23
23
  did: string
24
24
  active: boolean
25
25
  /** If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted. */
26
- status?: 'takendown' | 'suspended' | 'deactivated' | (string & {})
26
+ status?:
27
+ | 'takendown'
28
+ | 'suspended'
29
+ | 'deleted'
30
+ | 'deactivated'
31
+ | 'desynchronized'
32
+ | 'throttled'
33
+ | (string & {})
27
34
  /** Optional field, the current rev of the repo, if active=true */
28
35
  rev?: string
29
36
  }
@@ -58,7 +58,14 @@ export interface Repo {
58
58
  rev: string
59
59
  active?: boolean
60
60
  /** If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted. */
61
- status?: 'takendown' | 'suspended' | 'deactivated' | (string & {})
61
+ status?:
62
+ | 'takendown'
63
+ | 'suspended'
64
+ | 'deleted'
65
+ | 'deactivated'
66
+ | 'desynchronized'
67
+ | 'throttled'
68
+ | (string & {})
62
69
  }
63
70
 
64
71
  const hashRepo = 'repo'
@@ -19,6 +19,7 @@ export interface QueryParams {
19
19
 
20
20
  export type OutputSchema =
21
21
  | $Typed<Commit>
22
+ | $Typed<Sync>
22
23
  | $Typed<Identity>
23
24
  | $Typed<Account>
24
25
  | $Typed<Handle>
@@ -45,22 +46,22 @@ export interface Commit {
45
46
  seq: number
46
47
  /** DEPRECATED -- unused */
47
48
  rebase: boolean
48
- /** Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data. */
49
+ /** DEPRECATED -- replaced by #sync event and data limits. Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data. */
49
50
  tooBig: boolean
50
- /** The repo this event comes from. */
51
+ /** The repo this event comes from. Note that all other message types name this field 'did'. */
51
52
  repo: string
52
53
  /** Repo commit object CID. */
53
54
  commit: CID
54
- /** DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability. */
55
- prev?: CID | null
56
55
  /** The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event. */
57
56
  rev: string
58
57
  /** The rev of the last emitted commit from this repo (if any). */
59
58
  since: string | null
60
- /** CAR file containing relevant blocks, as a diff since the previous repo state. */
59
+ /** CAR file containing relevant blocks, as a diff since the previous repo state. The commit must be included as a block, and the commit block CID must be the first entry in the CAR header 'roots' list. */
61
60
  blocks: Uint8Array
62
61
  ops: RepoOp[]
63
62
  blobs: CID[]
63
+ /** The root CID of the MST tree for the previous commit from this repo (indicated by the 'since' revision field in this message). Corresponds to the 'data' field in the repo commit object. NOTE: this field is effectively required for the 'inductive' version of firehose. */
64
+ prevData?: CID
64
65
  /** Timestamp of when this message was originally broadcast. */
65
66
  time: string
66
67
  }
@@ -75,6 +76,31 @@ export function validateCommit<V>(v: V) {
75
76
  return validate<Commit & V>(v, id, hashCommit)
76
77
  }
77
78
 
79
+ /** Updates the repo to a new state, without necessarily including that state on the firehose. Used to recover from broken commit streams, data loss incidents, or in situations where upstream host does not know recent state of the repository. */
80
+ export interface Sync {
81
+ $type?: 'com.atproto.sync.subscribeRepos#sync'
82
+ /** The stream sequence number of this message. */
83
+ seq: number
84
+ /** The account this repo event corresponds to. Must match that in the commit object. */
85
+ did: string
86
+ /** CAR file containing the commit, as a block. The CAR header must include the commit block CID as the first 'root'. */
87
+ blocks: Uint8Array
88
+ /** The rev of the commit. This value must match that in the commit object. */
89
+ rev: string
90
+ /** Timestamp of when this message was originally broadcast. */
91
+ time: string
92
+ }
93
+
94
+ const hashSync = 'sync'
95
+
96
+ export function isSync<V>(v: V) {
97
+ return is$typed(v, id, hashSync)
98
+ }
99
+
100
+ export function validateSync<V>(v: V) {
101
+ return validate<Sync & V>(v, id, hashSync)
102
+ }
103
+
78
104
  /** Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache. */
79
105
  export interface Identity {
80
106
  $type?: 'com.atproto.sync.subscribeRepos#identity'
@@ -104,7 +130,14 @@ export interface Account {
104
130
  /** Indicates that the account has a repository which can be fetched from the host that emitted this event. */
105
131
  active: boolean
106
132
  /** If active=false, this optional field indicates a reason for why the account is not active. */
107
- status?: 'takendown' | 'suspended' | 'deleted' | 'deactivated' | (string & {})
133
+ status?:
134
+ | 'takendown'
135
+ | 'suspended'
136
+ | 'deleted'
137
+ | 'deactivated'
138
+ | 'desynchronized'
139
+ | 'throttled'
140
+ | (string & {})
108
141
  }
109
142
 
110
143
  const hashAccount = 'account'
@@ -196,6 +229,8 @@ export interface RepoOp {
196
229
  path: string
197
230
  /** For creates and updates, the new record CID. For deletions, null. */
198
231
  cid: CID | null
232
+ /** For updates and deletes, the previous record CID (required for inductive firehose). For creations, field should not be defined. */
233
+ prev?: CID
199
234
  }
200
235
 
201
236
  const hashRepoOp = 'repoOp'
@@ -284,10 +284,10 @@ export function validateModEventResolveAppeal<V>(v: V) {
284
284
  return validate<ModEventResolveAppeal & V>(v, id, hashModEventResolveAppeal)
285
285
  }
286
286
 
287
- /** Add a comment to a subject */
287
+ /** Add a comment to a subject. An empty comment will clear any previously set sticky comment. */
288
288
  export interface ModEventComment {
289
289
  $type?: 'tools.ozone.moderation.defs#modEventComment'
290
- comment: string
290
+ comment?: string
291
291
  /** Make the comment persistent on the subject */
292
292
  sticky?: boolean
293
293
  }
@@ -32,6 +32,7 @@ export interface InputSchema {
32
32
  | $Typed<ToolsOzoneModerationDefs.ModEventReverseTakedown>
33
33
  | $Typed<ToolsOzoneModerationDefs.ModEventResolveAppeal>
34
34
  | $Typed<ToolsOzoneModerationDefs.ModEventEmail>
35
+ | $Typed<ToolsOzoneModerationDefs.ModEventDivert>
35
36
  | $Typed<ToolsOzoneModerationDefs.ModEventTag>
36
37
  | $Typed<ToolsOzoneModerationDefs.AccountEvent>
37
38
  | $Typed<ToolsOzoneModerationDefs.IdentityEvent>
package/src/repo/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CID } from 'multiformats/cid'
2
2
  import { RepoRecord } from '@atproto/lexicon'
3
- import { WriteOpAction } from '@atproto/repo'
3
+ import { CidSet, CommitData, WriteOpAction } from '@atproto/repo'
4
4
  import { AtUri } from '@atproto/syntax'
5
5
 
6
6
  export type ValidationStatus = 'valid' | 'unknown' | undefined
@@ -42,6 +42,19 @@ export type PreparedDelete = {
42
42
  swapCid?: CID | null
43
43
  }
44
44
 
45
+ export type CommitOp = {
46
+ action: 'create' | 'update' | 'delete'
47
+ path: string
48
+ cid: CID | null
49
+ prev?: CID
50
+ }
51
+
52
+ export type CommitDataWithOps = CommitData & {
53
+ ops: CommitOp[]
54
+ blobs: CidSet
55
+ prevData: CID | null
56
+ }
57
+
45
58
  export type PreparedWrite = PreparedCreate | PreparedUpdate | PreparedDelete
46
59
 
47
60
  export class InvalidRecordError extends Error {}
@@ -71,10 +71,13 @@ export const rebuildRepo = async (ctx: AppContext, args: string[]) => {
71
71
  newBlocks,
72
72
  relevantBlocks: newBlocks,
73
73
  removedCids: toDelete,
74
+ ops: [],
75
+ blobs: new CidSet(),
76
+ prevData: null,
74
77
  }
75
78
  })
76
79
  await ctx.accountManager.updateRepoRoot(did, commit.cid, rev)
77
- await ctx.sequencer.sequenceCommit(did, commit, [])
80
+ await ctx.sequencer.sequenceCommit(did, commit)
78
81
  }
79
82
 
80
83
  const promptContinue = async (): Promise<boolean> => {
@@ -1,74 +1,59 @@
1
- import { CID } from 'multiformats/cid'
2
1
  import { z } from 'zod'
3
- import { cborEncode, schema } from '@atproto/common'
4
- import {
5
- BlockMap,
6
- CidSet,
7
- CommitData,
8
- WriteOpAction,
9
- blocksToCarFile,
10
- } from '@atproto/repo'
2
+ import { cborEncode, noUndefinedVals, schema } from '@atproto/common'
3
+ import { BlockMap, blocksToCarFile } from '@atproto/repo'
11
4
  import { AccountStatus } from '../account-manager'
12
- import { PreparedWrite } from '../repo'
5
+ import { CommitDataWithOps } from '../repo'
13
6
  import { RepoSeqInsert } from './db'
14
7
 
15
8
  export const formatSeqCommit = async (
16
9
  did: string,
17
- commitData: CommitData,
18
- writes: PreparedWrite[],
10
+ commitData: CommitDataWithOps,
19
11
  ): Promise<RepoSeqInsert> => {
20
- let tooBig: boolean
21
- const ops: CommitEvtOp[] = []
22
- const blobs = new CidSet()
23
- let carSlice: Uint8Array
24
-
25
12
  const blocksToSend = new BlockMap()
26
13
  blocksToSend.addMap(commitData.newBlocks)
27
14
  blocksToSend.addMap(commitData.relevantBlocks)
28
15
 
29
- // max 200 ops or 1MB of data
30
- if (writes.length > 200 || blocksToSend.byteSize > 1000000) {
31
- tooBig = true
16
+ let evt: CommitEvt
17
+
18
+ // If event is too big (max 200 ops or 1MB of data)
19
+ if (commitData.ops.length > 200 || blocksToSend.byteSize > 1000000) {
32
20
  const justRoot = new BlockMap()
33
21
  const rootBlock = blocksToSend.get(commitData.cid)
34
22
  if (rootBlock) {
35
23
  justRoot.set(commitData.cid, rootBlock)
36
24
  }
37
- carSlice = await blocksToCarFile(commitData.cid, justRoot)
25
+
26
+ evt = {
27
+ rebase: false,
28
+ tooBig: true,
29
+ repo: did,
30
+ commit: commitData.cid,
31
+ rev: commitData.rev,
32
+ since: commitData.since,
33
+ blocks: await blocksToCarFile(commitData.cid, justRoot),
34
+ ops: [],
35
+ blobs: [],
36
+ prevData: commitData.prevData ?? undefined,
37
+ }
38
38
  } else {
39
- tooBig = false
40
- for (const w of writes) {
41
- const path = w.uri.collection + '/' + w.uri.rkey
42
- let cid: CID | null
43
- if (w.action === WriteOpAction.Delete) {
44
- cid = null
45
- } else {
46
- cid = w.cid
47
- w.blobs.forEach((blob) => {
48
- blobs.add(blob.cid)
49
- })
50
- }
51
- ops.push({ action: w.action, path, cid })
39
+ evt = {
40
+ rebase: false,
41
+ tooBig: false,
42
+ repo: did,
43
+ commit: commitData.cid,
44
+ rev: commitData.rev,
45
+ since: commitData.since,
46
+ blocks: await blocksToCarFile(commitData.cid, blocksToSend),
47
+ ops: commitData.ops,
48
+ blobs: commitData.blobs.toList(),
49
+ prevData: commitData.prevData ?? undefined,
52
50
  }
53
- carSlice = await blocksToCarFile(commitData.cid, blocksToSend)
54
51
  }
55
52
 
56
- const evt: CommitEvt = {
57
- rebase: false,
58
- tooBig,
59
- repo: did,
60
- commit: commitData.cid,
61
- prev: commitData.prev,
62
- rev: commitData.rev,
63
- since: commitData.since,
64
- ops,
65
- blocks: carSlice,
66
- blobs: blobs.toList(),
67
- }
68
53
  return {
69
54
  did,
70
55
  eventType: 'append' as const,
71
- event: cborEncode(evt),
56
+ event: cborEncode(noUndefinedVals(evt)),
72
57
  sequencedAt: new Date().toISOString(),
73
58
  }
74
59
  }
@@ -149,6 +134,7 @@ export const commitEvtOp = z.object({
149
134
  ]),
150
135
  path: z.string(),
151
136
  cid: schema.cid.nullable(),
137
+ prev: schema.cid.optional(),
152
138
  })
153
139
  export type CommitEvtOp = z.infer<typeof commitEvtOp>
154
140
 
@@ -157,12 +143,12 @@ export const commitEvt = z.object({
157
143
  tooBig: z.boolean(),
158
144
  repo: z.string(),
159
145
  commit: schema.cid,
160
- prev: schema.cid.nullable(),
161
146
  rev: z.string(),
162
147
  since: z.string().nullable(),
163
148
  blocks: schema.bytes,
164
149
  ops: z.array(commitEvtOp),
165
150
  blobs: z.array(schema.cid),
151
+ prevData: schema.cid.optional(),
166
152
  })
167
153
  export type CommitEvt = z.infer<typeof commitEvt>
168
154
 
@@ -1,11 +1,10 @@
1
1
  import EventEmitter from 'node:events'
2
2
  import TypedEmitter from 'typed-emitter'
3
3
  import { SECOND, cborDecode, wait } from '@atproto/common'
4
- import { CommitData } from '@atproto/repo'
5
4
  import { AccountStatus } from '../account-manager/helpers/account'
6
5
  import { Crawlers } from '../crawlers'
7
6
  import { seqLogger as log } from '../logger'
8
- import { PreparedWrite } from '../repo'
7
+ import { CommitDataWithOps } from '../repo'
9
8
  import {
10
9
  RepoSeqEntry,
11
10
  RepoSeqInsert,
@@ -217,10 +216,9 @@ export class Sequencer extends (EventEmitter as new () => SequencerEmitter) {
217
216
 
218
217
  async sequenceCommit(
219
218
  did: string,
220
- commitData: CommitData,
221
- writes: PreparedWrite[],
219
+ commitData: CommitDataWithOps,
222
220
  ): Promise<number> {
223
- const evt = await formatSeqCommit(did, commitData, writes)
221
+ const evt = await formatSeqCommit(did, commitData)
224
222
  return await this.sequenceEvt(evt)
225
223
  }
226
224
 
@@ -1191,7 +1191,7 @@ describe('crud operations', () => {
1191
1191
  record: {
1192
1192
  text: 'x',
1193
1193
  createdAt: new Date().toISOString(),
1194
- deepObject: createDeepObject(3000),
1194
+ deepObject: createDeepObject(4000),
1195
1195
  },
1196
1196
  }),
1197
1197
  {
@@ -241,11 +241,7 @@ describe('sequencer', () => {
241
241
  (store) => store.repo.formatCommit(writes),
242
242
  )
243
243
 
244
- const repoSeqInsert = await formatSeqCommit(
245
- sc.dids.alice,
246
- writeCommit,
247
- writes,
248
- )
244
+ const repoSeqInsert = await formatSeqCommit(sc.dids.alice, writeCommit)
249
245
 
250
246
  const evt = cborDecode<sequencer.CommitEvt>(repoSeqInsert.event)
251
247
  expect(evt.tooBig).toBe(true)
@@ -0,0 +1,104 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { AtUri } from '@atproto/api'
3
+ import { SeedClient, TestNetworkNoAppView } from '@atproto/dev-env'
4
+ import * as repo from '@atproto/repo'
5
+ import { Subscription } from '@atproto/xrpc-server'
6
+ import {
7
+ OutputSchema as SubscribeReposOutput,
8
+ RepoOp,
9
+ isCommit,
10
+ } from '../../src/lexicon/types/com/atproto/sync/subscribeRepos'
11
+ import basicSeed from '../seeds/basic'
12
+
13
+ describe('invertible ops', () => {
14
+ let network: TestNetworkNoAppView
15
+ let sc: SeedClient
16
+
17
+ beforeAll(async () => {
18
+ network = await TestNetworkNoAppView.create({
19
+ dbPostgresSchema: 'repo_invertible_ops',
20
+ })
21
+ sc = network.getSeedClient()
22
+ await basicSeed(sc)
23
+
24
+ let posts: AtUri[] = []
25
+ for (let i = 0; i < 20; i++) {
26
+ const [aliceRef, bobRef, carolRef, danRef] = await Promise.all([
27
+ sc.post(sc.dids.alice, 'test'),
28
+ sc.post(sc.dids.bob, 'test'),
29
+ sc.post(sc.dids.carol, 'test'),
30
+ sc.post(sc.dids.dan, 'test'),
31
+ ])
32
+ posts = [
33
+ ...posts,
34
+ aliceRef.ref.uri,
35
+ bobRef.ref.uri,
36
+ carolRef.ref.uri,
37
+ danRef.ref.uri,
38
+ ]
39
+ }
40
+ for (const post of posts) {
41
+ await sc.deletePost(post.hostname, post)
42
+ }
43
+
44
+ await network.processAll()
45
+ })
46
+
47
+ afterAll(async () => {
48
+ await network.close()
49
+ })
50
+
51
+ it('works', async () => {
52
+ const currSeq = (await network.pds.ctx.sequencer.curr()) ?? 0
53
+
54
+ const sub = new Subscription({
55
+ service: network.pds.url.replace('http://', 'ws://'),
56
+ method: 'com.atproto.sync.subscribeRepos',
57
+ validate: (value: unknown): SubscribeReposOutput => {
58
+ return value as any
59
+ },
60
+ getParams: () => {
61
+ return { cursor: 0 }
62
+ },
63
+ })
64
+
65
+ for await (const evt of sub) {
66
+ if (!isCommit(evt)) {
67
+ continue
68
+ }
69
+ const prevData = evt.prevData as CID | undefined
70
+ if (!prevData) {
71
+ continue
72
+ }
73
+
74
+ const { blocks, root } = await repo.readCarWithRoot(
75
+ evt.blocks as Uint8Array,
76
+ )
77
+ const storage = new repo.MemoryBlockstore(blocks)
78
+ const slice = await repo.Repo.load(storage, root)
79
+
80
+ let data = slice.data
81
+ const ops = evt.ops as RepoOp[]
82
+ for (const op of ops) {
83
+ if (op.action === 'create') {
84
+ data = await data.delete(op.path)
85
+ } else if (op.action === 'update') {
86
+ if (!op.prev) throw new Error('missing prev')
87
+ data = await data.update(op.path, op.prev)
88
+ } else if (op.action === 'delete') {
89
+ if (!op.prev) throw new Error('missing prev')
90
+ data = await data.add(op.path, op.prev)
91
+ } else {
92
+ throw new Error('unknown action')
93
+ }
94
+ }
95
+
96
+ const invertedRoot = await data.getPointer()
97
+ expect(invertedRoot.equals(prevData)).toBe(true)
98
+
99
+ if (evt.seq >= currSeq) {
100
+ break
101
+ }
102
+ }
103
+ })
104
+ })