@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.
- package/CHANGELOG.md +22 -0
- package/dist/actor-store/repo/transactor.d.ts +5 -5
- package/dist/actor-store/repo/transactor.d.ts.map +1 -1
- package/dist/actor-store/repo/transactor.js +38 -18
- package/dist/actor-store/repo/transactor.js.map +1 -1
- package/dist/actor-store/repo/util.d.ts +5 -0
- package/dist/actor-store/repo/util.d.ts.map +1 -0
- package/dist/actor-store/repo/util.js +25 -0
- package/dist/actor-store/repo/util.js.map +1 -0
- package/dist/api/com/atproto/repo/applyWrites.js +1 -1
- package/dist/api/com/atproto/repo/applyWrites.js.map +1 -1
- package/dist/api/com/atproto/repo/createRecord.d.ts.map +1 -1
- package/dist/api/com/atproto/repo/createRecord.js +3 -3
- package/dist/api/com/atproto/repo/createRecord.js.map +1 -1
- package/dist/api/com/atproto/repo/deleteRecord.js +1 -1
- package/dist/api/com/atproto/repo/deleteRecord.js.map +1 -1
- package/dist/api/com/atproto/repo/putRecord.d.ts.map +1 -1
- package/dist/api/com/atproto/repo/putRecord.js +1 -1
- package/dist/api/com/atproto/repo/putRecord.js.map +1 -1
- package/dist/api/com/atproto/server/activateAccount.d.ts.map +1 -1
- package/dist/api/com/atproto/server/activateAccount.js +4 -1
- package/dist/api/com/atproto/server/activateAccount.js.map +1 -1
- package/dist/api/com/atproto/server/createAccount.js +1 -1
- package/dist/api/com/atproto/server/createAccount.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon/lexicons.d.ts +100 -34
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +71 -15
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/getRepoStatus.d.ts +1 -1
- package/dist/lexicon/types/com/atproto/sync/getRepoStatus.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts +1 -1
- package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/listRepos.js.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts +25 -7
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts.map +1 -1
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js +9 -0
- package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -2
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
- package/dist/repo/types.d.ts +12 -1
- package/dist/repo/types.d.ts.map +1 -1
- package/dist/repo/types.js.map +1 -1
- package/dist/scripts/rebuild-repo.d.ts.map +1 -1
- package/dist/scripts/rebuild-repo.js +4 -1
- package/dist/scripts/rebuild-repo.js.map +1 -1
- package/dist/sequencer/events.d.ts +22 -16
- package/dist/sequencer/events.d.ts.map +1 -1
- package/dist/sequencer/events.js +31 -39
- package/dist/sequencer/events.js.map +1 -1
- package/dist/sequencer/sequencer.d.ts +2 -3
- package/dist/sequencer/sequencer.d.ts.map +1 -1
- package/dist/sequencer/sequencer.js +2 -2
- package/dist/sequencer/sequencer.js.map +1 -1
- package/package.json +6 -6
- package/src/actor-store/repo/transactor.ts +47 -21
- package/src/actor-store/repo/util.ts +22 -0
- package/src/api/com/atproto/repo/applyWrites.ts +1 -1
- package/src/api/com/atproto/repo/createRecord.ts +28 -31
- package/src/api/com/atproto/repo/deleteRecord.ts +1 -1
- package/src/api/com/atproto/repo/putRecord.ts +3 -3
- package/src/api/com/atproto/server/activateAccount.ts +4 -1
- package/src/api/com/atproto/server/createAccount.ts +1 -1
- package/src/index.ts +1 -1
- package/src/lexicon/lexicons.ts +80 -16
- package/src/lexicon/types/com/atproto/sync/getRepoStatus.ts +8 -1
- package/src/lexicon/types/com/atproto/sync/listRepos.ts +8 -1
- package/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +41 -6
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -2
- package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +1 -0
- package/src/repo/types.ts +14 -1
- package/src/scripts/rebuild-repo.ts +4 -1
- package/src/sequencer/events.ts +35 -49
- package/src/sequencer/sequencer.ts +3 -5
- package/tests/crud.test.ts +1 -1
- package/tests/sequencer.test.ts +1 -5
- package/tests/sync/invertible-ops.test.ts +104 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- 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?:
|
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?:
|
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?:
|
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
|
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> => {
|
package/src/sequencer/events.ts
CHANGED
@@ -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 {
|
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:
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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 {
|
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:
|
221
|
-
writes: PreparedWrite[],
|
219
|
+
commitData: CommitDataWithOps,
|
222
220
|
): Promise<number> {
|
223
|
-
const evt = await formatSeqCommit(did, commitData
|
221
|
+
const evt = await formatSeqCommit(did, commitData)
|
224
222
|
return await this.sequenceEvt(evt)
|
225
223
|
}
|
226
224
|
|
package/tests/crud.test.ts
CHANGED
package/tests/sequencer.test.ts
CHANGED
@@ -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
|
+
})
|