@atproto/bsky 0.0.81 → 0.0.82
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 +8 -0
- package/dist/data-plane/client.d.ts.map +1 -1
- package/dist/data-plane/client.js +2 -1
- package/dist/data-plane/client.js.map +1 -1
- package/dist/data-plane/server/db/migrations/20240829T211238293Z-simplify-actor-sync.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20240829T211238293Z-simplify-actor-sync.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20240829T211238293Z-simplify-actor-sync.js +26 -0
- package/dist/data-plane/server/db/migrations/20240829T211238293Z-simplify-actor-sync.js.map +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/index.js +2 -1
- package/dist/data-plane/server/db/migrations/index.js.map +1 -1
- package/dist/data-plane/server/db/tables/actor-sync.d.ts +0 -3
- package/dist/data-plane/server/db/tables/actor-sync.d.ts.map +1 -1
- package/dist/data-plane/server/db/tables/actor-sync.js.map +1 -1
- package/dist/data-plane/server/indexing/index.d.ts +2 -7
- package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
- package/dist/data-plane/server/indexing/index.js +4 -21
- package/dist/data-plane/server/indexing/index.js.map +1 -1
- package/dist/data-plane/server/subscription.d.ts +26 -0
- package/dist/data-plane/server/subscription.d.ts.map +1 -0
- package/dist/data-plane/server/subscription.js +115 -0
- package/dist/data-plane/server/subscription.js.map +1 -0
- package/package.json +4 -3
- package/src/data-plane/client.ts +4 -1
- package/src/data-plane/server/db/migrations/20240829T211238293Z-simplify-actor-sync.ts +23 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/tables/actor-sync.ts +0 -3
- package/src/data-plane/server/indexing/index.ts +4 -25
- package/src/data-plane/server/subscription.ts +104 -0
- package/tests/data-plane/indexing.test.ts +1 -1
- package/tests/data-plane/{subscription/repo.test.ts → subscription.test.ts} +4 -9
- package/tests/views/actor-search.test.ts +1 -1
- package/dist/data-plane/server/subscription/index.d.ts +0 -33
- package/dist/data-plane/server/subscription/index.d.ts.map +0 -1
- package/dist/data-plane/server/subscription/index.js +0 -341
- package/dist/data-plane/server/subscription/index.js.map +0 -1
- package/dist/data-plane/server/subscription/util.d.ts +0 -65
- package/dist/data-plane/server/subscription/util.d.ts.map +0 -1
- package/dist/data-plane/server/subscription/util.js +0 -215
- package/dist/data-plane/server/subscription/util.js.map +0 -1
- package/src/data-plane/server/subscription/index.ts +0 -352
- package/src/data-plane/server/subscription/util.ts +0 -156
- package/tests/data-plane/subscription/util.test.ts +0 -185
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.strToInt = exports.jitter = exports.loggableMessage = exports.PerfectMap = exports.ConsecutiveItem = exports.ConsecutiveList = exports.LatestQueue = exports.PartitionedQueue = void 0;
|
|
30
|
-
const node_assert_1 = __importDefault(require("node:assert"));
|
|
31
|
-
const p_queue_1 = __importDefault(require("p-queue"));
|
|
32
|
-
const message = __importStar(require("../../../lexicon/types/com/atproto/sync/subscribeRepos"));
|
|
33
|
-
// A queue with arbitrarily many partitions, each processing work sequentially.
|
|
34
|
-
// Partitions are created lazily and taken out of memory when they go idle.
|
|
35
|
-
class PartitionedQueue {
|
|
36
|
-
constructor(opts) {
|
|
37
|
-
Object.defineProperty(this, "main", {
|
|
38
|
-
enumerable: true,
|
|
39
|
-
configurable: true,
|
|
40
|
-
writable: true,
|
|
41
|
-
value: void 0
|
|
42
|
-
});
|
|
43
|
-
Object.defineProperty(this, "partitions", {
|
|
44
|
-
enumerable: true,
|
|
45
|
-
configurable: true,
|
|
46
|
-
writable: true,
|
|
47
|
-
value: new Map()
|
|
48
|
-
});
|
|
49
|
-
this.main = new p_queue_1.default({ concurrency: opts.concurrency });
|
|
50
|
-
}
|
|
51
|
-
async add(partitionId, task) {
|
|
52
|
-
if (this.main.isPaused)
|
|
53
|
-
return;
|
|
54
|
-
return this.main.add(() => {
|
|
55
|
-
return this.getPartition(partitionId).add(task);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
async destroy() {
|
|
59
|
-
this.main.pause();
|
|
60
|
-
this.main.clear();
|
|
61
|
-
this.partitions.forEach((p) => p.clear());
|
|
62
|
-
await this.main.onIdle(); // All in-flight work completes
|
|
63
|
-
}
|
|
64
|
-
getPartition(partitionId) {
|
|
65
|
-
let partition = this.partitions.get(partitionId);
|
|
66
|
-
if (!partition) {
|
|
67
|
-
partition = new p_queue_1.default({ concurrency: 1 });
|
|
68
|
-
partition.once('idle', () => this.partitions.delete(partitionId));
|
|
69
|
-
this.partitions.set(partitionId, partition);
|
|
70
|
-
}
|
|
71
|
-
return partition;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
exports.PartitionedQueue = PartitionedQueue;
|
|
75
|
-
class LatestQueue {
|
|
76
|
-
constructor() {
|
|
77
|
-
Object.defineProperty(this, "queue", {
|
|
78
|
-
enumerable: true,
|
|
79
|
-
configurable: true,
|
|
80
|
-
writable: true,
|
|
81
|
-
value: new p_queue_1.default({ concurrency: 1 })
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
async add(task) {
|
|
85
|
-
if (this.queue.isPaused)
|
|
86
|
-
return;
|
|
87
|
-
this.queue.clear(); // Only queue the latest task, invalidate any previous ones
|
|
88
|
-
return this.queue.add(task);
|
|
89
|
-
}
|
|
90
|
-
async destroy() {
|
|
91
|
-
this.queue.pause();
|
|
92
|
-
this.queue.clear();
|
|
93
|
-
await this.queue.onIdle(); // All in-flight work completes
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
exports.LatestQueue = LatestQueue;
|
|
97
|
-
/**
|
|
98
|
-
* Add items to a list, and mark those items as
|
|
99
|
-
* completed. Upon item completion, get list of consecutive
|
|
100
|
-
* items completed at the head of the list. Example:
|
|
101
|
-
*
|
|
102
|
-
* const consecutive = new ConsecutiveList<number>()
|
|
103
|
-
* const item1 = consecutive.push(1)
|
|
104
|
-
* const item2 = consecutive.push(2)
|
|
105
|
-
* const item3 = consecutive.push(3)
|
|
106
|
-
* item2.complete() // []
|
|
107
|
-
* item1.complete() // [1, 2]
|
|
108
|
-
* item3.complete() // [3]
|
|
109
|
-
*
|
|
110
|
-
*/
|
|
111
|
-
class ConsecutiveList {
|
|
112
|
-
constructor() {
|
|
113
|
-
Object.defineProperty(this, "list", {
|
|
114
|
-
enumerable: true,
|
|
115
|
-
configurable: true,
|
|
116
|
-
writable: true,
|
|
117
|
-
value: []
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
push(value) {
|
|
121
|
-
const item = new ConsecutiveItem(this, value);
|
|
122
|
-
this.list.push(item);
|
|
123
|
-
return item;
|
|
124
|
-
}
|
|
125
|
-
complete() {
|
|
126
|
-
let i = 0;
|
|
127
|
-
while (this.list[i]?.isComplete) {
|
|
128
|
-
i += 1;
|
|
129
|
-
}
|
|
130
|
-
return this.list.splice(0, i).map((item) => item.value);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
exports.ConsecutiveList = ConsecutiveList;
|
|
134
|
-
class ConsecutiveItem {
|
|
135
|
-
constructor(consecutive, value) {
|
|
136
|
-
Object.defineProperty(this, "consecutive", {
|
|
137
|
-
enumerable: true,
|
|
138
|
-
configurable: true,
|
|
139
|
-
writable: true,
|
|
140
|
-
value: consecutive
|
|
141
|
-
});
|
|
142
|
-
Object.defineProperty(this, "value", {
|
|
143
|
-
enumerable: true,
|
|
144
|
-
configurable: true,
|
|
145
|
-
writable: true,
|
|
146
|
-
value: value
|
|
147
|
-
});
|
|
148
|
-
Object.defineProperty(this, "isComplete", {
|
|
149
|
-
enumerable: true,
|
|
150
|
-
configurable: true,
|
|
151
|
-
writable: true,
|
|
152
|
-
value: false
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
complete() {
|
|
156
|
-
this.isComplete = true;
|
|
157
|
-
return this.consecutive.complete();
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
exports.ConsecutiveItem = ConsecutiveItem;
|
|
161
|
-
class PerfectMap extends Map {
|
|
162
|
-
get(key) {
|
|
163
|
-
const val = super.get(key);
|
|
164
|
-
(0, node_assert_1.default)(val !== undefined, `Key not found in PerfectMap: ${key}`);
|
|
165
|
-
return val;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
exports.PerfectMap = PerfectMap;
|
|
169
|
-
function loggableMessage(msg) {
|
|
170
|
-
if (message.isCommit(msg)) {
|
|
171
|
-
const { seq, rebase, prev, repo, commit, time, tooBig, blobs } = msg;
|
|
172
|
-
return {
|
|
173
|
-
$type: msg.$type,
|
|
174
|
-
seq,
|
|
175
|
-
rebase,
|
|
176
|
-
prev: prev?.toString(),
|
|
177
|
-
repo,
|
|
178
|
-
commit: commit.toString(),
|
|
179
|
-
time,
|
|
180
|
-
tooBig,
|
|
181
|
-
hasBlobs: blobs.length > 0,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
else if (message.isHandle(msg)) {
|
|
185
|
-
return msg;
|
|
186
|
-
}
|
|
187
|
-
else if (message.isIdentity(msg)) {
|
|
188
|
-
return msg;
|
|
189
|
-
}
|
|
190
|
-
else if (message.isAccount(msg)) {
|
|
191
|
-
return msg;
|
|
192
|
-
}
|
|
193
|
-
else if (message.isMigrate(msg)) {
|
|
194
|
-
return msg;
|
|
195
|
-
}
|
|
196
|
-
else if (message.isTombstone(msg)) {
|
|
197
|
-
return msg;
|
|
198
|
-
}
|
|
199
|
-
else if (message.isInfo(msg)) {
|
|
200
|
-
return msg;
|
|
201
|
-
}
|
|
202
|
-
return msg;
|
|
203
|
-
}
|
|
204
|
-
exports.loggableMessage = loggableMessage;
|
|
205
|
-
function jitter(maxMs) {
|
|
206
|
-
return Math.round((Math.random() - 0.5) * maxMs * 2);
|
|
207
|
-
}
|
|
208
|
-
exports.jitter = jitter;
|
|
209
|
-
function strToInt(str) {
|
|
210
|
-
const int = parseInt(str, 10);
|
|
211
|
-
(0, node_assert_1.default)(!isNaN(int), 'string could not be parsed to an integer');
|
|
212
|
-
return int;
|
|
213
|
-
}
|
|
214
|
-
exports.strToInt = strToInt;
|
|
215
|
-
//# sourceMappingURL=util.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../../../src/data-plane/server/subscription/util.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { CID } from 'multiformats/cid'
|
|
3
|
-
import { AtUri } from '@atproto/syntax'
|
|
4
|
-
import { Subscription } from '@atproto/xrpc-server'
|
|
5
|
-
import { cborDecode, handleAllSettledErrors } from '@atproto/common'
|
|
6
|
-
import { ValidationError } from '@atproto/lexicon'
|
|
7
|
-
import { IdResolver } from '@atproto/identity'
|
|
8
|
-
import {
|
|
9
|
-
WriteOpAction,
|
|
10
|
-
readCarWithRoot,
|
|
11
|
-
cborToLexRecord,
|
|
12
|
-
def,
|
|
13
|
-
Commit,
|
|
14
|
-
} from '@atproto/repo'
|
|
15
|
-
import { ids, lexicons } from '../../../lexicon/lexicons'
|
|
16
|
-
import { OutputSchema as Message } from '../../../lexicon/types/com/atproto/sync/subscribeRepos'
|
|
17
|
-
import * as message from '../../../lexicon/types/com/atproto/sync/subscribeRepos'
|
|
18
|
-
import { subLogger as log } from '../../../logger'
|
|
19
|
-
import { IndexingService } from '../indexing'
|
|
20
|
-
import { Database } from '../db'
|
|
21
|
-
import {
|
|
22
|
-
ConsecutiveItem,
|
|
23
|
-
ConsecutiveList,
|
|
24
|
-
PartitionedQueue,
|
|
25
|
-
ProcessableMessage,
|
|
26
|
-
loggableMessage,
|
|
27
|
-
} from './util'
|
|
28
|
-
import { BackgroundQueue } from '../background'
|
|
29
|
-
|
|
30
|
-
export class RepoSubscription {
|
|
31
|
-
ac = new AbortController()
|
|
32
|
-
running: Promise<void> | undefined
|
|
33
|
-
cursor = 0
|
|
34
|
-
seenSeq: number | null = null
|
|
35
|
-
repoQueue = new PartitionedQueue({ concurrency: Infinity })
|
|
36
|
-
consecutive = new ConsecutiveList<number>()
|
|
37
|
-
background: BackgroundQueue
|
|
38
|
-
indexingSvc: IndexingService
|
|
39
|
-
|
|
40
|
-
constructor(
|
|
41
|
-
private opts: {
|
|
42
|
-
service: string
|
|
43
|
-
db: Database
|
|
44
|
-
idResolver: IdResolver
|
|
45
|
-
background: BackgroundQueue
|
|
46
|
-
},
|
|
47
|
-
) {
|
|
48
|
-
this.background = new BackgroundQueue(this.opts.db)
|
|
49
|
-
this.indexingSvc = new IndexingService(
|
|
50
|
-
this.opts.db,
|
|
51
|
-
this.opts.idResolver,
|
|
52
|
-
this.background,
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
run() {
|
|
57
|
-
if (this.running) return
|
|
58
|
-
this.ac = new AbortController()
|
|
59
|
-
this.repoQueue = new PartitionedQueue({ concurrency: Infinity })
|
|
60
|
-
this.consecutive = new ConsecutiveList<number>()
|
|
61
|
-
this.running = this.process()
|
|
62
|
-
.catch((err) => {
|
|
63
|
-
if (err.name !== 'AbortError') {
|
|
64
|
-
// allow this to cause an unhandled rejection, let deployment handle the crash.
|
|
65
|
-
log.error({ err }, 'subscription crashed')
|
|
66
|
-
throw err
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
.finally(() => (this.running = undefined))
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private async process() {
|
|
73
|
-
const sub = this.getSubscription()
|
|
74
|
-
for await (const msg of sub) {
|
|
75
|
-
const details = getMessageDetails(msg)
|
|
76
|
-
if ('info' in details) {
|
|
77
|
-
// These messages are not sequenced, we just log them and carry on
|
|
78
|
-
log.warn(
|
|
79
|
-
{ provider: this.opts.service, message: loggableMessage(msg) },
|
|
80
|
-
`sub ${details.info ? 'info' : 'unknown'} message`,
|
|
81
|
-
)
|
|
82
|
-
continue
|
|
83
|
-
}
|
|
84
|
-
const item = this.consecutive.push(details.seq)
|
|
85
|
-
this.repoQueue.add(details.repo, async () => {
|
|
86
|
-
await this.handleMessage(item, details)
|
|
87
|
-
})
|
|
88
|
-
this.seenSeq = details.seq
|
|
89
|
-
await this.repoQueue.main.onEmpty() // backpressure
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private async handleMessage(
|
|
94
|
-
item: ConsecutiveItem<number>,
|
|
95
|
-
envelope: Envelope,
|
|
96
|
-
) {
|
|
97
|
-
const msg = envelope.message
|
|
98
|
-
try {
|
|
99
|
-
if (message.isCommit(msg)) {
|
|
100
|
-
await this.handleCommit(msg)
|
|
101
|
-
} else if (message.isHandle(msg)) {
|
|
102
|
-
await this.handleUpdateHandle(msg)
|
|
103
|
-
} else if (message.isIdentity(msg)) {
|
|
104
|
-
await this.handleIdentityEvt(msg)
|
|
105
|
-
} else if (message.isAccount(msg)) {
|
|
106
|
-
await this.handleAccountEvt(msg)
|
|
107
|
-
} else if (message.isTombstone(msg)) {
|
|
108
|
-
// Ignore tombstones
|
|
109
|
-
} else if (message.isMigrate(msg)) {
|
|
110
|
-
// Ignore migrations
|
|
111
|
-
} else {
|
|
112
|
-
const exhaustiveCheck: never = msg
|
|
113
|
-
throw new Error(`Unhandled message type: ${exhaustiveCheck['$type']}`)
|
|
114
|
-
}
|
|
115
|
-
} catch (err) {
|
|
116
|
-
// We log messages we can't process and move on:
|
|
117
|
-
// otherwise the cursor would get stuck on a poison message.
|
|
118
|
-
log.error(
|
|
119
|
-
{ err, message: loggableMessage(msg) },
|
|
120
|
-
'indexer message processing error',
|
|
121
|
-
)
|
|
122
|
-
} finally {
|
|
123
|
-
const latest = item.complete().at(-1)
|
|
124
|
-
if (latest !== undefined) {
|
|
125
|
-
this.cursor = latest
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private async handleCommit(msg: message.Commit) {
|
|
131
|
-
const indexRecords = async () => {
|
|
132
|
-
const { root, rootCid, ops } = await getOps(msg)
|
|
133
|
-
if (msg.tooBig) {
|
|
134
|
-
await this.indexingSvc.indexRepo(msg.repo, rootCid.toString())
|
|
135
|
-
await this.indexingSvc.setCommitLastSeen(root, msg)
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
if (msg.rebase) {
|
|
139
|
-
const needsReindex =
|
|
140
|
-
await this.indexingSvc.checkCommitNeedsIndexing(root)
|
|
141
|
-
if (needsReindex) {
|
|
142
|
-
await this.indexingSvc.indexRepo(msg.repo, rootCid.toString())
|
|
143
|
-
}
|
|
144
|
-
await this.indexingSvc.setCommitLastSeen(root, msg)
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
for (const op of ops) {
|
|
148
|
-
if (op.action === WriteOpAction.Delete) {
|
|
149
|
-
await this.indexingSvc.deleteRecord(op.uri)
|
|
150
|
-
} else {
|
|
151
|
-
try {
|
|
152
|
-
await this.indexingSvc.indexRecord(
|
|
153
|
-
op.uri,
|
|
154
|
-
op.cid,
|
|
155
|
-
op.record,
|
|
156
|
-
op.action, // create or update
|
|
157
|
-
msg.time,
|
|
158
|
-
)
|
|
159
|
-
} catch (err) {
|
|
160
|
-
if (err instanceof ValidationError) {
|
|
161
|
-
log.warn(
|
|
162
|
-
{
|
|
163
|
-
did: msg.repo,
|
|
164
|
-
commit: msg.commit.toString(),
|
|
165
|
-
uri: op.uri.toString(),
|
|
166
|
-
cid: op.cid.toString(),
|
|
167
|
-
},
|
|
168
|
-
'skipping indexing of invalid record',
|
|
169
|
-
)
|
|
170
|
-
} else {
|
|
171
|
-
log.error(
|
|
172
|
-
{
|
|
173
|
-
err,
|
|
174
|
-
did: msg.repo,
|
|
175
|
-
commit: msg.commit.toString(),
|
|
176
|
-
uri: op.uri.toString(),
|
|
177
|
-
cid: op.cid.toString(),
|
|
178
|
-
},
|
|
179
|
-
'skipping indexing due to error processing record',
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
await this.indexingSvc.setCommitLastSeen(root, msg)
|
|
186
|
-
}
|
|
187
|
-
const results = await Promise.allSettled([
|
|
188
|
-
indexRecords(),
|
|
189
|
-
this.indexingSvc.indexHandle(msg.repo, msg.time),
|
|
190
|
-
])
|
|
191
|
-
handleAllSettledErrors(results)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private async handleUpdateHandle(msg: message.Handle) {
|
|
195
|
-
await this.indexingSvc.indexHandle(msg.did, msg.time, true)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
private async handleIdentityEvt(msg: message.Identity) {
|
|
199
|
-
await this.indexingSvc.indexHandle(msg.did, msg.time, true)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private async handleAccountEvt(msg: message.Account) {
|
|
203
|
-
if (msg.active === false && msg.status === 'deleted') {
|
|
204
|
-
await this.indexingSvc.deleteActor(msg.did)
|
|
205
|
-
} else {
|
|
206
|
-
await this.indexingSvc.updateActorStatus(msg.did, msg.active, msg.status)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private getSubscription() {
|
|
211
|
-
return new Subscription({
|
|
212
|
-
service: this.opts.service,
|
|
213
|
-
method: ids.ComAtprotoSyncSubscribeRepos,
|
|
214
|
-
signal: this.ac.signal,
|
|
215
|
-
getParams: async () => {
|
|
216
|
-
return { cursor: this.cursor }
|
|
217
|
-
},
|
|
218
|
-
onReconnectError: (err, reconnects, initial) => {
|
|
219
|
-
log.warn({ err, reconnects, initial }, 'sub reconnect')
|
|
220
|
-
},
|
|
221
|
-
validate: (value) => {
|
|
222
|
-
try {
|
|
223
|
-
return lexicons.assertValidXrpcMessage<Message>(
|
|
224
|
-
ids.ComAtprotoSyncSubscribeRepos,
|
|
225
|
-
value,
|
|
226
|
-
)
|
|
227
|
-
} catch (err) {
|
|
228
|
-
log.warn(
|
|
229
|
-
{
|
|
230
|
-
err,
|
|
231
|
-
seq: ifNumber(value?.['seq']),
|
|
232
|
-
repo: ifString(value?.['repo']),
|
|
233
|
-
commit: ifString(value?.['commit']?.toString()),
|
|
234
|
-
time: ifString(value?.['time']),
|
|
235
|
-
provider: this.opts.service,
|
|
236
|
-
},
|
|
237
|
-
'ingester sub skipped invalid message',
|
|
238
|
-
)
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
|
-
})
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async destroy() {
|
|
245
|
-
this.ac.abort()
|
|
246
|
-
await this.running
|
|
247
|
-
await this.repoQueue.destroy()
|
|
248
|
-
await this.background.processAll()
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
type Envelope = {
|
|
253
|
-
repo: string
|
|
254
|
-
message: ProcessableMessage
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function ifString(val: unknown): string | undefined {
|
|
258
|
-
return typeof val === 'string' ? val : undefined
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function ifNumber(val: unknown): number | undefined {
|
|
262
|
-
return typeof val === 'number' ? val : undefined
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function getMessageDetails(msg: Message):
|
|
266
|
-
| { info: message.Info | null }
|
|
267
|
-
| {
|
|
268
|
-
seq: number
|
|
269
|
-
repo: string
|
|
270
|
-
message: ProcessableMessage
|
|
271
|
-
} {
|
|
272
|
-
if (message.isCommit(msg)) {
|
|
273
|
-
return { seq: msg.seq, repo: msg.repo, message: msg }
|
|
274
|
-
} else if (message.isHandle(msg)) {
|
|
275
|
-
return { seq: msg.seq, repo: msg.did, message: msg }
|
|
276
|
-
} else if (message.isIdentity(msg)) {
|
|
277
|
-
return { seq: msg.seq, repo: msg.did, message: msg }
|
|
278
|
-
} else if (message.isAccount(msg)) {
|
|
279
|
-
return { seq: msg.seq, repo: msg.did, message: msg }
|
|
280
|
-
} else if (message.isMigrate(msg)) {
|
|
281
|
-
return { seq: msg.seq, repo: msg.did, message: msg }
|
|
282
|
-
} else if (message.isTombstone(msg)) {
|
|
283
|
-
return { seq: msg.seq, repo: msg.did, message: msg }
|
|
284
|
-
} else if (message.isInfo(msg)) {
|
|
285
|
-
return { info: msg }
|
|
286
|
-
}
|
|
287
|
-
return { info: null }
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async function getOps(
|
|
291
|
-
msg: message.Commit,
|
|
292
|
-
): Promise<{ root: Commit; rootCid: CID; ops: PreparedWrite[] }> {
|
|
293
|
-
const car = await readCarWithRoot(msg.blocks as Uint8Array)
|
|
294
|
-
const rootBytes = car.blocks.get(car.root)
|
|
295
|
-
assert(rootBytes, 'Missing commit block in car slice')
|
|
296
|
-
|
|
297
|
-
const root = def.commit.schema.parse(cborDecode(rootBytes))
|
|
298
|
-
const ops: PreparedWrite[] = msg.ops.map((op) => {
|
|
299
|
-
const [collection, rkey] = op.path.split('/')
|
|
300
|
-
assert(collection && rkey)
|
|
301
|
-
if (
|
|
302
|
-
op.action === WriteOpAction.Create ||
|
|
303
|
-
op.action === WriteOpAction.Update
|
|
304
|
-
) {
|
|
305
|
-
assert(op.cid)
|
|
306
|
-
const record = car.blocks.get(op.cid)
|
|
307
|
-
assert(record)
|
|
308
|
-
return {
|
|
309
|
-
action:
|
|
310
|
-
op.action === WriteOpAction.Create
|
|
311
|
-
? WriteOpAction.Create
|
|
312
|
-
: WriteOpAction.Update,
|
|
313
|
-
cid: op.cid,
|
|
314
|
-
record: cborToLexRecord(record),
|
|
315
|
-
blobs: [],
|
|
316
|
-
uri: AtUri.make(msg.repo, collection, rkey),
|
|
317
|
-
}
|
|
318
|
-
} else if (op.action === WriteOpAction.Delete) {
|
|
319
|
-
return {
|
|
320
|
-
action: WriteOpAction.Delete,
|
|
321
|
-
uri: AtUri.make(msg.repo, collection, rkey),
|
|
322
|
-
}
|
|
323
|
-
} else {
|
|
324
|
-
throw new Error(`Unknown repo op action: ${op.action}`)
|
|
325
|
-
}
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
return { root, rootCid: car.root, ops }
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
type PreparedCreate = {
|
|
332
|
-
action: WriteOpAction.Create
|
|
333
|
-
uri: AtUri
|
|
334
|
-
cid: CID
|
|
335
|
-
record: Record<string, unknown>
|
|
336
|
-
blobs: CID[] // differs from similar type in pds
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
type PreparedUpdate = {
|
|
340
|
-
action: WriteOpAction.Update
|
|
341
|
-
uri: AtUri
|
|
342
|
-
cid: CID
|
|
343
|
-
record: Record<string, unknown>
|
|
344
|
-
blobs: CID[] // differs from similar type in pds
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
type PreparedDelete = {
|
|
348
|
-
action: WriteOpAction.Delete
|
|
349
|
-
uri: AtUri
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
type PreparedWrite = PreparedCreate | PreparedUpdate | PreparedDelete
|