Skip to content

Commit 56cd853

Browse files
authored
feat!: improve UploadResult ergonomics (#664)
1 parent f1bfc44 commit 56cd853

10 files changed

Lines changed: 142 additions & 91 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Client ──store──> Primary SP (endorsed)
123123

124124
### Failure Handling
125125

126-
`UploadResult` returns `copies[]` (successful) and `failures[]` (failed). Primary failure is fatal; secondary failures are reported but non-fatal. `StoreError` and `CommitError` (in `errors/storage.ts`) carry providerId and cause chain.
126+
`UploadResult` returns `complete` (boolean), `copies[]` (successful), and `failedAttempts[]` (intermediate failures). Check `complete` to determine overall success -- do not use `failedAttempts.length` as a failure signal. Primary store failure throws `StoreError`; all commits failing throws `CommitError`. Both carry providerId and cause chain.
127127

128128
## Session Keys
129129

@@ -194,14 +194,15 @@ Ref: FRC-0069
194194
// Simple: auto-managed multi-copy upload
195195
const synapse = await Synapse.create({ privateKey, rpcUrl })
196196
const result = await synapse.storage.upload(data)
197+
// result.complete === true means all requested copies succeeded
197198
// result.copies = [{ providerId, dataSetId, pieceId, role: 'primary'|'secondary' }]
198-
// result.failures = [{ providerId, error, role }]
199+
// result.failedAttempts = [{ providerId, error, role }] (non-empty does NOT mean failure)
199200

200201
// Download (SP-agnostic, tries all known providers)
201202
const bytes = await synapse.storage.download({ pieceCid })
202203

203204
// Explicit contexts for fine-grained control
204-
const [primary, secondary] = await synapse.storage.createContexts({ count: 2 })
205+
const [primary, secondary] = await synapse.storage.createContexts({ copies: 2 })
205206
const stored = await primary.store(data) // Upload, get PieceCID
206207
const extraData = await secondary.presignForCommit([{ pieceCid: stored.pieceCid }])
207208
await secondary.pull({ // SP-to-SP transfer

docs/src/content/docs/developer-guides/storage/storage-context.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ await synapse.storage.createContext({
6969

7070
// Multiple contexts for multi-copy
7171
const contexts = await synapse.storage.createContexts({
72-
count: 3, // number of contexts (default: 2)
72+
copies: 3, // number of contexts (default: 2)
7373
providerIds: [1n, 2n, 3n], // specific providers (mutually exclusive with dataSetIds)
7474
dataSetIds: [10n, 20n, 30n], // specific data sets (mutually exclusive with providerIds)
7575
})
@@ -132,7 +132,7 @@ const abortController = new AbortController()
132132
const preCalculatedCid = null as unknown as PieceCID;
133133
// ---cut---
134134
const contexts = await synapse.storage.createContexts({
135-
count: 2,
135+
copies: 2,
136136
})
137137
const [primary, secondary] = contexts
138138

@@ -166,7 +166,7 @@ import { privateKeyToAccount } from "viem/accounts"
166166

167167
const synapse = Synapse.create({ account: privateKeyToAccount("0x..."), source: "my-app" })
168168
const [primary, secondary] = await synapse.storage.createContexts({
169-
count: 2,
169+
copies: 2,
170170
metadata: { source: "my-app" },
171171
})
172172
const pieceCid = null as unknown as PieceCID;
@@ -209,7 +209,7 @@ import { privateKeyToAccount } from "viem/accounts"
209209
import type { Hex } from "viem";
210210
const synapse = Synapse.create({ account: privateKeyToAccount("0x..."), source: "my-app" })
211211
const contexts = await synapse.storage.createContexts({
212-
count: 2,
212+
copies: 2,
213213
metadata: { source: "my-app" },
214214
})
215215
const [primary, secondary] = contexts
@@ -270,7 +270,7 @@ const files = [
270270

271271
// Create contexts for 2 providers
272272
const [primary, secondary] = await synapse.storage.createContexts({
273-
count: 2,
273+
copies: 2,
274274
metadata: { source: "batch-upload" },
275275
})
276276

@@ -575,7 +575,7 @@ const client = synapse.client;
575575
const walletAddress = client.account.address;
576576

577577
const [primary, secondary] = await synapse.storage.createContexts({
578-
count: 2,
578+
copies: 2,
579579
metadata: { source: "my-app" },
580580
})
581581
const pieceCid = null as unknown as PieceCID;

docs/src/content/docs/developer-guides/storage/storage-operations.mdx

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source:
4646

4747
const data = new Uint8Array([1, 2, 3, 4, 5])
4848

49-
const { pieceCid, size, copies, failures } = await synapse.storage.upload(data)
49+
const { pieceCid, size, complete, copies, failedAttempts } = await synapse.storage.upload(data)
5050

5151
console.log("PieceCID:", pieceCid.toString())
5252
console.log("Size:", size, "bytes")
@@ -56,21 +56,23 @@ for (const copy of copies) {
5656
console.log(` Provider ${copy.providerId}: role=${copy.role}, dataSet=${copy.dataSetId}`)
5757
}
5858

59-
if (failures.length > 0) {
60-
console.warn("Some copies failed:", failures)
59+
if (!complete) {
60+
console.warn("Some copies failed:", failedAttempts)
6161
}
6262
```
6363

6464
:::caution[Always check the result]
65-
`upload()` returns a result as long as **at least one** copy commits on-chain. It only throws when zero copies succeed. You **must** inspect `copies` and `failures` to know whether all requested copies were stored — a successful return does not guarantee all copies succeeded.
65+
`upload()` returns a result as long as **at least one** copy commits on-chain. It only throws when zero copies succeed. You **must** check `complete` to know whether all requested copies were stored — a successful return does not guarantee all copies succeeded.
6666
:::
6767

6868
The result contains:
6969

70+
- **`complete`**`true` when all requested copies were stored and committed on-chain. This is the primary field to check.
71+
- **`requestedCopies`** — the number of copies that were requested (default: 2)
7072
- **`pieceCid`** — content address of your data, used for downloads
7173
- **`size`** — size of the uploaded data in bytes
7274
- **`copies`** — array of successful copies, each with `providerId`, `dataSetId`, `pieceId`, `role` (`'primary'` or `'secondary'`), `retrievalUrl`, and `isNewDataSet`
73-
- **`failures`**array of failed copy attempts (partial failures are returned, not thrown), each with `providerId`, `role`, `error`, and `explicit`
75+
- **`failedAttempts`**providers that were tried but did not produce a copy. The SDK retries failed secondaries with alternate providers, so a non-empty array often just means a provider was swapped out. These are diagnostic, check `complete` for the actual outcome.
7476

7577
#### Upload with Metadata
7678

@@ -183,11 +185,11 @@ const synapse = Synapse.create({ account: privateKeyToAccount("0x..."), source:
183185
const data = new Uint8Array(256)
184186

185187
// Store 3 copies for higher redundancy
186-
const result3 = await synapse.storage.upload(data, { count: 3 })
188+
const result3 = await synapse.storage.upload(data, { copies: 3 })
187189
console.log("3 copies:", result3.copies.length)
188190

189191
// Store a single copy when redundancy isn't needed
190-
const result1 = await synapse.storage.upload(data, { count: 1 })
192+
const result1 = await synapse.storage.upload(data, { copies: 1 })
191193
console.log("1 copy:", result1.copies.length)
192194
```
193195

@@ -220,23 +222,16 @@ const synapse = Synapse.create({ account: privateKeyToAccount("0x..."), source:
220222

221223
const data = new Uint8Array(256)
222224

223-
const result = await synapse.storage.upload(data, { count: 2 })
225+
const result = await synapse.storage.upload(data, { copies: 2 })
224226

225-
// Check: did we get all requested copies?
226-
if (result.copies.length < 2) {
227-
console.warn(`Only ${result.copies.length}/2 copies succeeded`)
228-
for (const failure of result.failures) {
229-
console.warn(` Provider ${failure.providerId} (${failure.role}): ${failure.error}`)
227+
// Check overall success: complete === true means all requested copies succeeded
228+
if (!result.complete) {
229+
console.warn(`Only ${result.copies.length}/${result.requestedCopies} copies succeeded`)
230+
for (const attempt of result.failedAttempts) {
231+
console.warn(` Provider ${attempt.providerId} (${attempt.role}): ${attempt.error}`)
230232
}
231233
}
232234

233-
// Check: did the endorsed primary succeed?
234-
const primaryFailed = result.failures.find(f => f.role === "primary")
235-
if (primaryFailed) {
236-
console.warn(`Endorsed provider failed: ${primaryFailed.error}`)
237-
// Data is only on non-endorsed secondaries
238-
}
239-
240235
// Every copy is committed and being paid for
241236
for (const copy of result.copies) {
242237
console.log(`Provider ${copy.providerId}, dataset ${copy.dataSetId}, piece ${copy.pieceId}`)

docs/src/content/docs/getting-started/index.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,12 @@ async function main() {
104104
}
105105

106106
// 3) Upload — stores 2 copies across independent providers for durability
107-
const { pieceCid, size, copies, failures } = await synapse.storage.upload(file)
107+
const { pieceCid, size, complete, copies, failedAttempts } = await synapse.storage.upload(file)
108108
console.log(`✅ Upload complete!`);
109109
console.log(`PieceCID: ${pieceCid}`);
110110
console.log(`Size: ${size} bytes`);
111111
console.log(`Stored on ${copies.length} providers`);
112-
if (failures.length > 0) console.warn(`${failures.length} copy attempt(s) failed`);
112+
if (!complete) console.warn(`${failedAttempts.length} copy attempt(s) failed`);
113113

114114
// 4) Download
115115
const bytes = await synapse.storage.download({ pieceCid })
@@ -205,9 +205,9 @@ const file = new TextEncoder().encode(
205205
🌍 You need to make sure to meet the minimum size
206206
requirement of 127 bytes per upload.`
207207
);
208-
const { pieceCid, copies, failures } = await synapse.storage.upload(file);
208+
const { pieceCid, complete, copies, failedAttempts } = await synapse.storage.upload(file);
209209
console.log(`Stored on ${copies.length} providers`);
210-
if (failures.length > 0) console.warn(`${failures.length} copy attempt(s) failed`);
210+
if (!complete) console.warn(`${failedAttempts.length} copy attempt(s) failed`);
211211

212212
// Download data from any provider that has it
213213
const downloadedData = await synapse.storage.download({ pieceCid });

packages/synapse-sdk/src/storage/context.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export class StorageContext {
244244

245245
if (resolutions.length > 0) {
246246
// Explicit path — validate count matches deduped results
247-
const count = options.count ?? resolutions.length
247+
const count = options.copies ?? resolutions.length
248248
if (resolutions.length !== count) {
249249
throw createError(
250250
'StorageContext',
@@ -266,7 +266,7 @@ export class StorageContext {
266266
}
267267
} else {
268268
// Smart selection path (neither dataSetIds nor providerIds provided)
269-
const count = options.count ?? 2
269+
const count = options.copies ?? 2
270270
resolutions = await StorageContext.smartSelect({
271271
synapse: options.synapse,
272272
metadata: options.metadata ?? {},
@@ -944,6 +944,8 @@ export class StorageContext {
944944
return {
945945
pieceCid: storeResult.pieceCid,
946946
size: storeResult.size,
947+
requestedCopies: 1,
948+
complete: true,
947949
copies: [
948950
{
949951
providerId: this._provider.id,
@@ -954,7 +956,7 @@ export class StorageContext {
954956
isNewDataSet: commitResult.isNewDataSet,
955957
},
956958
],
957-
failures: [],
959+
failedAttempts: [],
958960
}
959961
}
960962

0 commit comments

Comments
 (0)