@filoz/repair-cli 0.3.0 → 1.0.0
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/dist/package.json +3 -3
- package/dist/src/api.d.ts +35 -0
- package/dist/src/api.d.ts.map +1 -0
- package/dist/src/api.js +118 -0
- package/dist/src/api.js.map +1 -0
- package/dist/src/cli.js +2 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/datasets.d.ts +3 -5
- package/dist/src/commands/datasets.d.ts.map +1 -1
- package/dist/src/commands/datasets.js +24 -33
- package/dist/src/commands/datasets.js.map +1 -1
- package/dist/src/commands/providers.d.ts +3 -5
- package/dist/src/commands/providers.d.ts.map +1 -1
- package/dist/src/commands/providers.js +32 -33
- package/dist/src/commands/providers.js.map +1 -1
- package/dist/src/commands/repair.d.ts +3 -5
- package/dist/src/commands/repair.d.ts.map +1 -1
- package/dist/src/commands/repair.js +3 -3
- package/dist/src/commands/repair.js.map +1 -1
- package/dist/src/commands/replicate.d.ts +3 -5
- package/dist/src/commands/replicate.d.ts.map +1 -1
- package/dist/src/commands/replicate.js +3 -3
- package/dist/src/commands/replicate.js.map +1 -1
- package/dist/src/commands/second-copy.d.ts +23 -0
- package/dist/src/commands/second-copy.d.ts.map +1 -0
- package/dist/src/commands/second-copy.js +175 -0
- package/dist/src/commands/second-copy.js.map +1 -0
- package/dist/src/commands/session-key.d.ts +3 -5
- package/dist/src/commands/session-key.d.ts.map +1 -1
- package/dist/src/commands/setup.d.ts.map +1 -1
- package/dist/src/commands/setup.js +11 -32
- package/dist/src/commands/setup.js.map +1 -1
- package/dist/src/commands/wallet.d.ts +3 -5
- package/dist/src/commands/wallet.d.ts.map +1 -1
- package/dist/src/db/dedupe-cids.d.ts +6 -7
- package/dist/src/db/dedupe-cids.d.ts.map +1 -1
- package/dist/src/db/dedupe-cids.js +2 -14
- package/dist/src/db/dedupe-cids.js.map +1 -1
- package/dist/src/db/find-repair-dataset.d.ts +3 -3
- package/dist/src/db/find-repair-dataset.d.ts.map +1 -1
- package/dist/src/db/find-repair-dataset.js +16 -14
- package/dist/src/db/find-repair-dataset.js.map +1 -1
- package/dist/src/db/get-active-data-set-ids.d.ts +9 -0
- package/dist/src/db/get-active-data-set-ids.d.ts.map +1 -0
- package/dist/src/db/get-active-data-set-ids.js +9 -0
- package/dist/src/db/get-active-data-set-ids.js.map +1 -0
- package/dist/src/db/get-alternate-provider-urls.d.ts +6 -0
- package/dist/src/db/get-alternate-provider-urls.d.ts.map +1 -0
- package/dist/src/db/get-alternate-provider-urls.js +24 -0
- package/dist/src/db/get-alternate-provider-urls.js.map +1 -0
- package/dist/src/db/get-pieces.d.ts +8 -27
- package/dist/src/db/get-pieces.d.ts.map +1 -1
- package/dist/src/db/get-pieces.js +44 -97
- package/dist/src/db/get-pieces.js.map +1 -1
- package/dist/src/db/get-repair-provider.d.ts +2 -2
- package/dist/src/db/get-repair-provider.d.ts.map +1 -1
- package/dist/src/db/get-repair-provider.js +8 -20
- package/dist/src/db/get-repair-provider.js.map +1 -1
- package/dist/src/db/repair-create.d.ts.map +1 -1
- package/dist/src/db/repair-create.js +5 -3
- package/dist/src/db/repair-create.js.map +1 -1
- package/dist/src/db/replicate-create.d.ts.map +1 -1
- package/dist/src/db/replicate-create.js +15 -21
- package/dist/src/db/replicate-create.js.map +1 -1
- package/dist/src/db/second-copy-create.d.ts +10 -0
- package/dist/src/db/second-copy-create.d.ts.map +1 -0
- package/dist/src/db/second-copy-create.js +104 -0
- package/dist/src/db/second-copy-create.js.map +1 -0
- package/dist/src/local-schema.d.ts +41 -1
- package/dist/src/local-schema.d.ts.map +1 -1
- package/dist/src/local-schema.js +2 -0
- package/dist/src/local-schema.js.map +1 -1
- package/dist/src/middleware.d.ts +7 -11
- package/dist/src/middleware.d.ts.map +1 -1
- package/dist/src/middleware.js +6 -10
- package/dist/src/middleware.js.map +1 -1
- package/dist/src/pipeline/add-pieces.d.ts +3 -3
- package/dist/src/pipeline/add-pieces.d.ts.map +1 -1
- package/dist/src/pipeline/add-pieces.js +15 -7
- package/dist/src/pipeline/add-pieces.js.map +1 -1
- package/dist/src/pipeline/create-datasets.d.ts +4 -4
- package/dist/src/pipeline/create-datasets.d.ts.map +1 -1
- package/dist/src/pipeline/create-datasets.js +7 -14
- package/dist/src/pipeline/create-datasets.js.map +1 -1
- package/dist/src/types.d.ts +6 -7
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +7 -10
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +21 -2
- package/dist/src/utils.js.map +1 -1
- package/package.json +3 -3
- package/readme.md +98 -13
- package/src/api.ts +220 -0
- package/src/cli.ts +2 -0
- package/src/commands/datasets.ts +24 -36
- package/src/commands/providers.ts +34 -42
- package/src/commands/repair.ts +3 -3
- package/src/commands/replicate.ts +3 -3
- package/src/commands/second-copy.ts +187 -0
- package/src/commands/setup.ts +12 -35
- package/src/db/dedupe-cids.ts +7 -24
- package/src/db/find-repair-dataset.ts +17 -28
- package/src/db/get-active-data-set-ids.ts +29 -0
- package/src/db/get-alternate-provider-urls.ts +43 -0
- package/src/db/get-pieces.ts +82 -214
- package/src/db/get-repair-provider.ts +10 -30
- package/src/db/repair-create.ts +5 -3
- package/src/db/replicate-create.ts +15 -21
- package/src/db/second-copy-create.ts +147 -0
- package/src/local-schema.ts +3 -0
- package/src/middleware.ts +7 -11
- package/src/pipeline/add-pieces.ts +21 -11
- package/src/pipeline/create-datasets.ts +9 -29
- package/src/types.ts +9 -7
- package/src/utils.ts +22 -4
- package/dist/src/db/find-providers-by-cid.d.ts +0 -9
- package/dist/src/db/find-providers-by-cid.d.ts.map +0 -1
- package/dist/src/db/find-providers-by-cid.js +0 -43
- package/dist/src/db/find-providers-by-cid.js.map +0 -1
- package/dist/src/indexer-schema.d.ts +0 -549
- package/dist/src/indexer-schema.d.ts.map +0 -1
- package/dist/src/indexer-schema.js +0 -56
- package/dist/src/indexer-schema.js.map +0 -1
- package/src/db/find-providers-by-cid.ts +0 -70
- package/src/indexer-schema.ts +0 -77
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { listProviders, type Provider } from '../api.ts'
|
|
2
|
+
import type { IndexerApi } from '../types.ts'
|
|
3
|
+
|
|
4
|
+
/** Rank endorsed providers first, then approved, then by ascending ID. */
|
|
5
|
+
function compareProviders(a: Provider, b: Provider): number {
|
|
6
|
+
if (a.endorsed !== b.endorsed) return a.endorsed ? -1 : 1
|
|
7
|
+
if (a.approved !== b.approved) return a.approved ? -1 : 1
|
|
8
|
+
const x = BigInt(a.providerId)
|
|
9
|
+
const y = BigInt(b.providerId)
|
|
10
|
+
return x < y ? -1 : x > y ? 1 : 0
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Service URLs of the providers to probe for a copy of a repaired piece: approved or endorsed
|
|
15
|
+
* providers with a service URL, then the provider being repaired as a last resort, so pieces only
|
|
16
|
+
* it stores can still be pulled while it serves them.
|
|
17
|
+
*
|
|
18
|
+
* Callers probe these with `findPieceOnProviders`, which keeps this order, so endorsed providers
|
|
19
|
+
* are preferred as pull sources and the repaired provider is used only when no other one serves
|
|
20
|
+
* the piece.
|
|
21
|
+
*/
|
|
22
|
+
export async function getAlternateProviderUrls({
|
|
23
|
+
api,
|
|
24
|
+
repairProviderId,
|
|
25
|
+
}: {
|
|
26
|
+
api: IndexerApi
|
|
27
|
+
repairProviderId: bigint
|
|
28
|
+
}): Promise<string[]> {
|
|
29
|
+
const providers = await listProviders(api)
|
|
30
|
+
const repairProviderUrl = providers.find((provider) => BigInt(provider.providerId) === repairProviderId)?.serviceUrl
|
|
31
|
+
const urls = providers
|
|
32
|
+
.filter(
|
|
33
|
+
(provider) =>
|
|
34
|
+
(provider.approved || provider.endorsed) &&
|
|
35
|
+
provider.serviceUrl &&
|
|
36
|
+
BigInt(provider.providerId) !== repairProviderId
|
|
37
|
+
)
|
|
38
|
+
.sort(compareProviders)
|
|
39
|
+
.map((provider) => provider.serviceUrl as string)
|
|
40
|
+
|
|
41
|
+
if (repairProviderUrl) urls.push(repairProviderUrl)
|
|
42
|
+
return urls
|
|
43
|
+
}
|
package/src/db/get-pieces.ts
CHANGED
|
@@ -1,157 +1,70 @@
|
|
|
1
1
|
import * as Piece from '@filoz/synapse-core/piece'
|
|
2
|
-
import { and, asc, eq, isNull, lte, or } from 'drizzle-orm'
|
|
3
2
|
import pMap from 'p-map'
|
|
3
|
+
import { listDataSetPieces, MAX_PAGE_SIZE, providerPiecePages, toStringMetadata } from '../api.ts'
|
|
4
4
|
import type { OperationInsert } from '../local-schema.ts'
|
|
5
|
-
import type {
|
|
6
|
-
import {
|
|
5
|
+
import type { IndexerApi } from '../types.ts'
|
|
6
|
+
import { getActiveDataSetIds } from './get-active-data-set-ids.ts'
|
|
7
|
+
import { getAlternateProviderUrls } from './get-alternate-provider-urls.ts'
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
/** Options for fetching one page of `add_piece` operations for a repair. */
|
|
12
|
-
export type GetPiecesPageOptions = {
|
|
13
|
-
indexerDb: IndexerDatabase
|
|
9
|
+
/** Options for walking `add_piece` operations for a repair. */
|
|
10
|
+
export type ForEachPiecesPageOptions = {
|
|
11
|
+
api: IndexerApi
|
|
14
12
|
/** Source provider whose pieces are being repaired. */
|
|
15
13
|
providerId: bigint
|
|
16
14
|
/** Local repair row to attach operations to. */
|
|
17
15
|
repairId: number
|
|
18
16
|
/** Chain block number captured when the repair was created. */
|
|
19
17
|
blockNumber: bigint
|
|
20
|
-
/** Max indexer rows per page. Defaults to {@link DEFAULT_PIECES_PAGE_SIZE}. */
|
|
21
|
-
limit?: number
|
|
22
|
-
/** SQL offset for the indexer query. */
|
|
23
|
-
offset?: number
|
|
24
|
-
/**
|
|
25
|
-
* CIDs already emitted across prior pages. Pass the value returned from the previous call
|
|
26
|
-
* so the same CID is not queued twice when it appears in multiple source datasets.
|
|
27
|
-
*/
|
|
28
|
-
seenCids?: Set<string>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Result of a single {@link getPiecesPage} call. */
|
|
32
|
-
export type GetPiecesPageResult = {
|
|
33
|
-
/** `add_piece` operations ready to insert for this page (may include `skipped` rows). */
|
|
34
|
-
operations: OperationInsert[]
|
|
35
|
-
/** Whether another indexer page may exist after this one. */
|
|
36
|
-
hasMore: boolean
|
|
37
|
-
/** Offset to pass as `offset` on the next page. */
|
|
38
|
-
nextOffset: number
|
|
39
|
-
/** Updated dedupe set; pass into the next {@link getPiecesPage} call. */
|
|
40
|
-
seenCids: Set<string>
|
|
41
18
|
}
|
|
42
19
|
|
|
43
|
-
/** Options for
|
|
44
|
-
export type
|
|
45
|
-
|
|
46
|
-
/** Options for fetching one page of source-dataset pieces for replication. */
|
|
47
|
-
export type GetDataSetPiecesPageOptions = {
|
|
48
|
-
indexerDb: IndexerDatabase
|
|
20
|
+
/** Options for walking source-dataset pieces for replication. */
|
|
21
|
+
export type ForEachDataSetPiecesPageOptions = {
|
|
22
|
+
api: IndexerApi
|
|
49
23
|
/** Source dataset whose pieces are being replicated. */
|
|
50
24
|
dataSetId: bigint
|
|
51
25
|
/** Local job row to attach operations to. */
|
|
52
26
|
repairId: number
|
|
53
27
|
/** Provider URL that serves the source dataset pieces. */
|
|
54
28
|
sourceProviderUrl: string
|
|
55
|
-
/** Max indexer rows per page. Defaults to {@link DEFAULT_PIECES_PAGE_SIZE}. */
|
|
56
|
-
limit?: number
|
|
57
|
-
/** SQL offset for the indexer query. */
|
|
58
|
-
offset?: number
|
|
59
29
|
}
|
|
60
30
|
|
|
61
|
-
/**
|
|
62
|
-
export type
|
|
63
|
-
/** `add_piece` operations
|
|
31
|
+
/** One page of operations ready to insert into the local database. */
|
|
32
|
+
export type PiecesPage = {
|
|
33
|
+
/** `add_piece` operations for this page (repairs may include `skipped` rows). */
|
|
64
34
|
operations: OperationInsert[]
|
|
65
|
-
/** Whether another indexer page may exist after this one. */
|
|
66
|
-
hasMore: boolean
|
|
67
|
-
/** Offset to pass as `offset` on the next page. */
|
|
68
|
-
nextOffset: number
|
|
69
35
|
}
|
|
70
36
|
|
|
71
|
-
/** Options for {@link forEachDataSetPiecesPage}; pagination state is managed internally. */
|
|
72
|
-
export type ForEachDataSetPiecesPageOptions = Omit<GetDataSetPiecesPageOptions, 'offset'>
|
|
73
|
-
|
|
74
37
|
type PieceForOperation = {
|
|
75
38
|
cid: string
|
|
76
|
-
metadata: Record<string, string>
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Empty CID set for starting a paginated piece walk. */
|
|
80
|
-
export function emptySeenCids(): Set<string> {
|
|
81
|
-
return new Set()
|
|
39
|
+
metadata: Record<string, string>
|
|
82
40
|
}
|
|
83
41
|
|
|
84
42
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* Pieces are read from the indexer in stable dataset/piece order. CIDs are deduped globally so a
|
|
88
|
-
* piece stored under multiple source datasets is queued once into the single IPFS repair dataset.
|
|
89
|
-
* Alternate providers are resolved in one batch per page; operations without alternates are
|
|
90
|
-
* inserted as `skipped` with a descriptive error.
|
|
43
|
+
* Map pieces to repair `add_piece` operations, probing the alternate providers for a copy of each.
|
|
91
44
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* @param options - Indexer connection, repair context, and optional pagination state.
|
|
95
|
-
* @returns Operations for this page plus pagination cursors.
|
|
45
|
+
* Operations without a reachable alternate are returned as `skipped` with a descriptive error.
|
|
96
46
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
providerId,
|
|
47
|
+
async function piecesToRepairOperations({
|
|
48
|
+
pieces,
|
|
100
49
|
repairId,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const rows = await indexerDb
|
|
108
|
-
.select({
|
|
109
|
-
cid: schema.pieces.cid,
|
|
110
|
-
metadata: schema.pieces.metadata,
|
|
111
|
-
})
|
|
112
|
-
.from(schema.pieces)
|
|
113
|
-
.innerJoin(schema.dataSets, eq(schema.pieces.dataSetId, schema.dataSets.dataSetId))
|
|
114
|
-
.where(
|
|
115
|
-
and(
|
|
116
|
-
eq(schema.dataSets.providerId, providerId),
|
|
117
|
-
eq(schema.dataSets.deleted, false),
|
|
118
|
-
or(isNull(schema.dataSets.pdpEndEpoch), lte(schema.dataSets.pdpEndEpoch, blockNumber)),
|
|
119
|
-
eq(schema.pieces.removed, false)
|
|
120
|
-
)
|
|
121
|
-
)
|
|
122
|
-
.orderBy(asc(schema.pieces.dataSetId), asc(schema.pieces.pieceId))
|
|
123
|
-
.limit(limit)
|
|
124
|
-
.offset(offset)
|
|
125
|
-
|
|
50
|
+
alternateProviderUrls,
|
|
51
|
+
}: {
|
|
52
|
+
pieces: PieceForOperation[]
|
|
53
|
+
repairId: number
|
|
54
|
+
alternateProviderUrls: string[]
|
|
55
|
+
}): Promise<OperationInsert[]> {
|
|
126
56
|
const now = Date.now()
|
|
127
|
-
const pieces: PieceForOperation[] = []
|
|
128
|
-
|
|
129
|
-
for (const { cid, metadata } of rows) {
|
|
130
|
-
// Same CID can appear on multiple source datasets; only repair it once.
|
|
131
|
-
if (seenCids.has(cid)) continue
|
|
132
|
-
seenCids.add(cid)
|
|
133
|
-
|
|
134
|
-
pieces.push({ cid, metadata })
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Resolve pull sources in one query per page; exclude the provider being repaired from alternates.
|
|
138
|
-
const providersByCid = await findProvidersByCid({
|
|
139
|
-
indexerDb,
|
|
140
|
-
cids: pieces.map((piece) => piece.cid),
|
|
141
|
-
blockNumber,
|
|
142
|
-
})
|
|
143
57
|
|
|
144
|
-
|
|
58
|
+
return pMap(
|
|
145
59
|
pieces,
|
|
146
60
|
async ({ cid, metadata }) => {
|
|
147
|
-
const alternateProviders = providersByCid[cid]?.map((provider) => provider.serviceUrl) ?? []
|
|
148
61
|
let skippedMessage = ''
|
|
149
62
|
let validProvider: string | undefined
|
|
150
|
-
if (
|
|
151
|
-
validProvider = await Piece.findPieceOnProviders(
|
|
63
|
+
if (alternateProviderUrls.length > 0) {
|
|
64
|
+
validProvider = await Piece.findPieceOnProviders(alternateProviderUrls, Piece.from(cid))
|
|
152
65
|
|
|
153
66
|
if (!validProvider) {
|
|
154
|
-
skippedMessage = `
|
|
67
|
+
skippedMessage = `Piece not found on any of ${alternateProviderUrls.length} probed providers`
|
|
155
68
|
}
|
|
156
69
|
} else {
|
|
157
70
|
skippedMessage = `No alternate providers found`
|
|
@@ -163,129 +76,84 @@ export async function getPiecesPage({
|
|
|
163
76
|
// Cannot pull without another replica; mark skipped up front so run skips these ops.
|
|
164
77
|
status: validProvider ? 'pending' : 'skipped',
|
|
165
78
|
cid,
|
|
166
|
-
metadata
|
|
79
|
+
metadata,
|
|
167
80
|
alternateProvider: validProvider ?? '',
|
|
168
81
|
error: validProvider ? undefined : skippedMessage,
|
|
169
82
|
createdAt: now,
|
|
170
83
|
updatedAt: now,
|
|
171
|
-
}
|
|
84
|
+
} satisfies OperationInsert
|
|
172
85
|
},
|
|
173
86
|
{ concurrency: 20 }
|
|
174
87
|
)
|
|
175
|
-
|
|
176
|
-
// )
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
operations,
|
|
180
|
-
// A full page means there may be more rows; a short page ends pagination.
|
|
181
|
-
hasMore: rows.length === limit,
|
|
182
|
-
nextOffset: offset + rows.length,
|
|
183
|
-
seenCids,
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Fetch one page of pieces for a specific dataset and map them to replication operations.
|
|
189
|
-
*
|
|
190
|
-
* Unlike repairs, replication preserves source dataset ordering and does not dedupe repeated CIDs.
|
|
191
|
-
*
|
|
192
|
-
* @param options - Indexer connection, dataset context, and optional pagination state.
|
|
193
|
-
* @returns Operations for this page plus pagination cursors.
|
|
194
|
-
*/
|
|
195
|
-
export async function getDataSetPiecesPage({
|
|
196
|
-
indexerDb,
|
|
197
|
-
dataSetId,
|
|
198
|
-
repairId,
|
|
199
|
-
sourceProviderUrl,
|
|
200
|
-
limit = DEFAULT_PIECES_PAGE_SIZE,
|
|
201
|
-
offset = 0,
|
|
202
|
-
}: GetDataSetPiecesPageOptions): Promise<GetDataSetPiecesPageResult> {
|
|
203
|
-
const schema = indexerDb._.fullSchema
|
|
204
|
-
const rows = await indexerDb
|
|
205
|
-
.select({
|
|
206
|
-
cid: schema.pieces.cid,
|
|
207
|
-
metadata: schema.pieces.metadata,
|
|
208
|
-
})
|
|
209
|
-
.from(schema.pieces)
|
|
210
|
-
.where(and(eq(schema.pieces.dataSetId, dataSetId), eq(schema.pieces.removed, false)))
|
|
211
|
-
.orderBy(asc(schema.pieces.pieceId))
|
|
212
|
-
.limit(limit)
|
|
213
|
-
.offset(offset)
|
|
214
|
-
|
|
215
|
-
const now = Date.now()
|
|
216
|
-
const operations: OperationInsert[] = rows.map(({ cid, metadata }) => ({
|
|
217
|
-
repairId,
|
|
218
|
-
type: 'add_piece',
|
|
219
|
-
status: 'pending',
|
|
220
|
-
cid,
|
|
221
|
-
metadata: metadata ?? {},
|
|
222
|
-
alternateProvider: sourceProviderUrl,
|
|
223
|
-
createdAt: now,
|
|
224
|
-
updatedAt: now,
|
|
225
|
-
}))
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
operations,
|
|
229
|
-
hasMore: rows.length === limit,
|
|
230
|
-
nextOffset: offset + rows.length,
|
|
231
|
-
}
|
|
232
88
|
}
|
|
233
89
|
|
|
234
90
|
/**
|
|
235
|
-
* Walk every
|
|
91
|
+
* Walk every live piece of a provider at the repair block and map them to `add_piece` operations,
|
|
92
|
+
* invoking `onPage` per API page.
|
|
236
93
|
*
|
|
237
|
-
*
|
|
94
|
+
* Pieces are walked newest dataset and piece first. CIDs are deduped globally so a piece stored
|
|
95
|
+
* under multiple source datasets is queued once into the single IPFS repair dataset. Each piece is
|
|
96
|
+
* looked up directly on the approved or endorsed providers, then the provider being repaired
|
|
97
|
+
* (`HEAD /piece/{cid}`), rather than in the API, so finding a pull source costs no API requests.
|
|
238
98
|
*
|
|
239
|
-
* @param options -
|
|
240
|
-
* @param onPage - Async handler for each page
|
|
99
|
+
* @param options - API client and repair context.
|
|
100
|
+
* @param onPage - Async handler for each page (e.g. batch insert into local DB).
|
|
241
101
|
*/
|
|
242
102
|
export async function forEachPiecesPage(
|
|
243
|
-
|
|
244
|
-
onPage: (page:
|
|
103
|
+
{ api, providerId, repairId, blockNumber }: ForEachPiecesPageOptions,
|
|
104
|
+
onPage: (page: PiecesPage) => Promise<void>
|
|
245
105
|
): Promise<void> {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
hasMore = page.hasMore
|
|
106
|
+
const activeDataSetIds = await getActiveDataSetIds({ api, providerId, blockNumber })
|
|
107
|
+
const alternateProviderUrls = await getAlternateProviderUrls({ api, repairProviderId: providerId })
|
|
108
|
+
const seenCids = new Set<string>()
|
|
109
|
+
|
|
110
|
+
for await (const rows of providerPiecePages(api, providerId, { removed: false })) {
|
|
111
|
+
const pieces: PieceForOperation[] = []
|
|
112
|
+
for (const { cid, metadata, dataSetId } of rows) {
|
|
113
|
+
if (!activeDataSetIds.has(dataSetId)) continue
|
|
114
|
+
// Same CID can appear on multiple source datasets; only repair it once.
|
|
115
|
+
if (cid == null || seenCids.has(cid)) continue
|
|
116
|
+
seenCids.add(cid)
|
|
117
|
+
pieces.push({ cid, metadata: toStringMetadata(metadata) })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await onPage({ operations: await piecesToRepairOperations({ pieces, repairId, alternateProviderUrls }) })
|
|
262
121
|
}
|
|
263
122
|
}
|
|
264
123
|
|
|
265
124
|
/**
|
|
266
|
-
* Walk every
|
|
125
|
+
* Walk every live piece of a source dataset and map them to replication operations, invoking
|
|
126
|
+
* `onPage` per batch.
|
|
267
127
|
*
|
|
268
|
-
*
|
|
128
|
+
* Unlike repairs, replication preserves source dataset ordering (ascending piece ID) and does not
|
|
129
|
+
* dedupe repeated CIDs.
|
|
269
130
|
*
|
|
270
|
-
* @param options -
|
|
271
|
-
* @param onPage - Async handler for each page
|
|
131
|
+
* @param options - API client and dataset context.
|
|
132
|
+
* @param onPage - Async handler for each page (e.g. batch insert into local DB).
|
|
272
133
|
*/
|
|
273
134
|
export async function forEachDataSetPiecesPage(
|
|
274
|
-
|
|
275
|
-
onPage: (page:
|
|
135
|
+
{ api, dataSetId, repairId, sourceProviderUrl }: ForEachDataSetPiecesPageOptions,
|
|
136
|
+
onPage: (page: PiecesPage) => Promise<void>
|
|
276
137
|
): Promise<void> {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
while (hasMore) {
|
|
281
|
-
const page = await getDataSetPiecesPage({
|
|
282
|
-
...options,
|
|
283
|
-
offset,
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
await onPage(page)
|
|
138
|
+
// The API lists newest pieces first; load the whole dataset and reverse to keep source order.
|
|
139
|
+
const pieces = (await listDataSetPieces(api, dataSetId, { removed: false })).reverse()
|
|
140
|
+
const now = Date.now()
|
|
287
141
|
|
|
288
|
-
|
|
289
|
-
|
|
142
|
+
for (let offset = 0; offset < pieces.length; offset += MAX_PAGE_SIZE) {
|
|
143
|
+
const operations: OperationInsert[] = []
|
|
144
|
+
for (const { cid, metadata } of pieces.slice(offset, offset + MAX_PAGE_SIZE)) {
|
|
145
|
+
if (cid == null) continue
|
|
146
|
+
operations.push({
|
|
147
|
+
repairId,
|
|
148
|
+
type: 'add_piece',
|
|
149
|
+
status: 'pending',
|
|
150
|
+
cid,
|
|
151
|
+
metadata: toStringMetadata(metadata),
|
|
152
|
+
alternateProvider: sourceProviderUrl,
|
|
153
|
+
createdAt: now,
|
|
154
|
+
updatedAt: now,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
await onPage({ operations })
|
|
290
158
|
}
|
|
291
159
|
}
|
|
@@ -1,47 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Address } from 'viem'
|
|
2
|
+
import { getProvider } from '../api.ts'
|
|
2
3
|
import type { Context, RepairProvider } from '../types.ts'
|
|
3
4
|
|
|
4
|
-
export interface GetRepairProviderOptions extends Pick<Context, '
|
|
5
|
+
export interface GetRepairProviderOptions extends Pick<Context, 'api'> {
|
|
5
6
|
providerId: bigint
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Load an active provider by ID for use as a repair target.
|
|
10
11
|
*/
|
|
11
|
-
export async function getRepairProvider({
|
|
12
|
-
|
|
13
|
-
providerId,
|
|
14
|
-
}: GetRepairProviderOptions): Promise<RepairProvider | null> {
|
|
15
|
-
const schema = indexerDb._.fullSchema
|
|
16
|
-
const [provider] = await indexerDb
|
|
17
|
-
.select({
|
|
18
|
-
providerId: schema.providers.providerId,
|
|
19
|
-
providerAddress: schema.providers.providerAddress,
|
|
20
|
-
name: schema.providers.name,
|
|
21
|
-
serviceUrl: schema.providers.serviceUrl,
|
|
22
|
-
approved: schema.providers.approved,
|
|
23
|
-
endorsed: schema.providers.endorsed,
|
|
24
|
-
})
|
|
25
|
-
.from(schema.providers)
|
|
26
|
-
.where(
|
|
27
|
-
and(
|
|
28
|
-
eq(schema.providers.providerId, providerId),
|
|
29
|
-
eq(schema.providers.providerActive, true),
|
|
30
|
-
eq(schema.providers.pdpProductActive, true)
|
|
31
|
-
)
|
|
32
|
-
)
|
|
33
|
-
.limit(1)
|
|
12
|
+
export async function getRepairProvider({ api, providerId }: GetRepairProviderOptions): Promise<RepairProvider | null> {
|
|
13
|
+
const provider = await getProvider(api, providerId)
|
|
34
14
|
|
|
35
|
-
if (!provider?.
|
|
15
|
+
if (!provider?.active || !provider.pdpProductActive || !provider.serviceUrl || !provider.name) {
|
|
36
16
|
return null
|
|
37
17
|
}
|
|
38
18
|
|
|
39
19
|
return {
|
|
40
|
-
providerId: provider.providerId,
|
|
41
|
-
providerAddress: provider.
|
|
20
|
+
providerId: BigInt(provider.providerId),
|
|
21
|
+
providerAddress: provider.address as Address,
|
|
42
22
|
name: provider.name,
|
|
43
23
|
serviceUrl: provider.serviceUrl,
|
|
44
|
-
approved: provider.approved,
|
|
45
|
-
endorsed: provider.endorsed,
|
|
24
|
+
approved: provider.approved ?? false,
|
|
25
|
+
endorsed: provider.endorsed ?? false,
|
|
46
26
|
}
|
|
47
27
|
}
|
package/src/db/repair-create.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface RepairCreateOptions extends Context {
|
|
|
18
18
|
* @returns {Promise<number>} The ID of the created repair.
|
|
19
19
|
*/
|
|
20
20
|
export async function repairCreate(options: RepairCreateOptions): Promise<number> {
|
|
21
|
-
const {
|
|
21
|
+
const { api, localDb, repairProviderId, targetProviderId, client } = options
|
|
22
22
|
const localSchema = localDb._.fullSchema
|
|
23
23
|
const now = Date.now()
|
|
24
24
|
const blockNumber = await getBlockNumber(client)
|
|
@@ -34,7 +34,7 @@ export async function repairCreate(options: RepairCreateOptions): Promise<number
|
|
|
34
34
|
throw new RepairCreationError('Target provider must differ from the provider being repaired')
|
|
35
35
|
}
|
|
36
36
|
const targetProvider = await getRepairProvider({
|
|
37
|
-
|
|
37
|
+
api,
|
|
38
38
|
providerId: targetProviderId,
|
|
39
39
|
})
|
|
40
40
|
|
|
@@ -46,8 +46,10 @@ export async function repairCreate(options: RepairCreateOptions): Promise<number
|
|
|
46
46
|
const [repair] = await localDb
|
|
47
47
|
.insert(localSchema.repairs)
|
|
48
48
|
.values({
|
|
49
|
+
kind: 'repair',
|
|
49
50
|
repairProviderId,
|
|
50
51
|
repairDataSetId: null,
|
|
52
|
+
sourceProviderUrl: null,
|
|
51
53
|
targetProviderId: targetProvider.providerId,
|
|
52
54
|
targetProviderUrl: targetProvider.serviceUrl,
|
|
53
55
|
targetDataSetId: null,
|
|
@@ -66,7 +68,7 @@ export async function repairCreate(options: RepairCreateOptions): Promise<number
|
|
|
66
68
|
const seenCids = new Set<string>()
|
|
67
69
|
await forEachPiecesPage(
|
|
68
70
|
{
|
|
69
|
-
|
|
71
|
+
api,
|
|
70
72
|
providerId: repairProviderId,
|
|
71
73
|
repairId: repair.id,
|
|
72
74
|
blockNumber,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { taskLog } from '@clack/prompts'
|
|
2
|
-
import { eq } from 'drizzle-orm'
|
|
3
2
|
import { getBlockNumber } from 'viem/actions'
|
|
3
|
+
import { getDataSet } from '../api.ts'
|
|
4
4
|
import { NoAlternateProviderError, RepairCreationError } from '../error.ts'
|
|
5
5
|
import type { Context } from '../types.ts'
|
|
6
6
|
import { forEachDataSetPiecesPage } from './get-pieces.ts'
|
|
@@ -18,8 +18,7 @@ export interface ReplicateCreateOptions extends Context {
|
|
|
18
18
|
* @returns The ID of the created local job.
|
|
19
19
|
*/
|
|
20
20
|
export async function replicateCreate(options: ReplicateCreateOptions): Promise<number> {
|
|
21
|
-
const {
|
|
22
|
-
const indexerSchema = indexerDb._.fullSchema
|
|
21
|
+
const { api, localDb, dataSetId, targetProviderId, client } = options
|
|
23
22
|
const localSchema = localDb._.fullSchema
|
|
24
23
|
const now = Date.now()
|
|
25
24
|
const blockNumber = await getBlockNumber(client)
|
|
@@ -30,35 +29,28 @@ export async function replicateCreate(options: ReplicateCreateOptions): Promise<
|
|
|
30
29
|
retainLog: true,
|
|
31
30
|
})
|
|
32
31
|
|
|
33
|
-
const
|
|
34
|
-
.select({
|
|
35
|
-
dataSetId: indexerSchema.dataSets.dataSetId,
|
|
36
|
-
providerId: indexerSchema.dataSets.providerId,
|
|
37
|
-
deleted: indexerSchema.dataSets.deleted,
|
|
38
|
-
})
|
|
39
|
-
.from(indexerSchema.dataSets)
|
|
40
|
-
.where(eq(indexerSchema.dataSets.dataSetId, dataSetId))
|
|
41
|
-
.limit(1)
|
|
32
|
+
const sourceDataSet = await getDataSet(api, dataSetId)
|
|
42
33
|
|
|
43
|
-
if (!sourceDataSet || sourceDataSet.deleted) {
|
|
34
|
+
if (!sourceDataSet || sourceDataSet.deleted || sourceDataSet.providerId == null) {
|
|
44
35
|
throw new RepairCreationError(`Source dataset ${dataSetId} not found or deleted`)
|
|
45
36
|
}
|
|
37
|
+
const sourceProviderId = BigInt(sourceDataSet.providerId)
|
|
46
38
|
|
|
47
|
-
if (targetProviderId ===
|
|
39
|
+
if (targetProviderId === sourceProviderId) {
|
|
48
40
|
throw new RepairCreationError('Target provider must differ from the source dataset provider')
|
|
49
41
|
}
|
|
50
42
|
|
|
51
43
|
const sourceProvider = await getRepairProvider({
|
|
52
|
-
|
|
53
|
-
providerId:
|
|
44
|
+
api,
|
|
45
|
+
providerId: sourceProviderId,
|
|
54
46
|
})
|
|
55
47
|
|
|
56
48
|
if (!sourceProvider) {
|
|
57
|
-
throw new RepairCreationError(`Source provider ${
|
|
49
|
+
throw new RepairCreationError(`Source provider ${sourceProviderId} not found or inactive`)
|
|
58
50
|
}
|
|
59
51
|
|
|
60
52
|
const targetProvider = await getRepairProvider({
|
|
61
|
-
|
|
53
|
+
api,
|
|
62
54
|
providerId: targetProviderId,
|
|
63
55
|
})
|
|
64
56
|
|
|
@@ -69,8 +61,10 @@ export async function replicateCreate(options: ReplicateCreateOptions): Promise<
|
|
|
69
61
|
const [repair] = await localDb
|
|
70
62
|
.insert(localSchema.repairs)
|
|
71
63
|
.values({
|
|
64
|
+
kind: 'replicate',
|
|
72
65
|
repairProviderId: sourceProvider.providerId,
|
|
73
|
-
repairDataSetId:
|
|
66
|
+
repairDataSetId: dataSetId,
|
|
67
|
+
sourceProviderUrl: sourceProvider.serviceUrl,
|
|
74
68
|
targetProviderId: targetProvider.providerId,
|
|
75
69
|
targetProviderUrl: targetProvider.serviceUrl,
|
|
76
70
|
targetDataSetId: null,
|
|
@@ -85,8 +79,8 @@ export async function replicateCreate(options: ReplicateCreateOptions): Promise<
|
|
|
85
79
|
let totalOperations = 0
|
|
86
80
|
await forEachDataSetPiecesPage(
|
|
87
81
|
{
|
|
88
|
-
|
|
89
|
-
dataSetId
|
|
82
|
+
api,
|
|
83
|
+
dataSetId,
|
|
90
84
|
repairId: repair.id,
|
|
91
85
|
sourceProviderUrl: sourceProvider.serviceUrl,
|
|
92
86
|
},
|