@storacha/clawracha 0.3.14 → 0.4.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/LICENSE.md +204 -0
- package/README.md +117 -110
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +3 -3
- package/dist/handlers/apply.d.ts +3 -11
- package/dist/handlers/apply.d.ts.map +1 -1
- package/dist/handlers/apply.js +5 -32
- package/dist/handlers/process.d.ts +3 -6
- package/dist/handlers/process.d.ts.map +1 -1
- package/dist/handlers/process.js +30 -60
- package/dist/handlers/remote.d.ts +1 -3
- package/dist/handlers/remote.d.ts.map +1 -1
- package/dist/handlers/remote.js +4 -34
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/mdsync/index.d.ts +2 -2
- package/dist/mdsync/index.d.ts.map +1 -1
- package/dist/mdsync/index.js +14 -17
- package/dist/plugin.js +2 -2
- package/dist/sync.d.ts +20 -10
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +76 -105
- package/dist/types/index.d.ts +11 -5
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/config.d.ts +26 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +89 -0
- package/dist/utils/contentfetcher.d.ts +6 -0
- package/dist/utils/contentfetcher.d.ts.map +1 -0
- package/dist/utils/contentfetcher.js +58 -0
- package/dist/utils/crypto.d.ts +0 -6
- package/dist/utils/crypto.d.ts.map +1 -1
- package/dist/utils/crypto.js +0 -45
- package/dist/utils/differ.d.ts.map +1 -1
- package/dist/utils/differ.js +6 -0
- package/dist/utils/encoder.d.ts +2 -15
- package/dist/utils/encoder.d.ts.map +1 -1
- package/dist/utils/encoder.js +8 -49
- package/dist/utils/tempcar.d.ts +1 -1
- package/dist/utils/tempcar.d.ts.map +1 -1
- package/dist/utils/tempcar.js +4 -2
- package/package.json +3 -2
package/dist/sync.js
CHANGED
|
@@ -9,80 +9,48 @@
|
|
|
9
9
|
* 6. Apply remote changes to local filesystem
|
|
10
10
|
*/
|
|
11
11
|
// UCN Pail imports
|
|
12
|
-
import {
|
|
12
|
+
import { NoValueError, Revision } from "@storacha/ucn/pail";
|
|
13
13
|
import { createWorkspaceBlockstore, } from "./blockstore/index.js";
|
|
14
14
|
import { applyPendingOps } from "./handlers/apply.js";
|
|
15
15
|
import { applyRemoteChanges } from "./handlers/remote.js";
|
|
16
16
|
import { processChanges } from "./handlers/process.js";
|
|
17
17
|
import { diffRemoteChanges } from "./utils/differ.js";
|
|
18
18
|
import { makeTempCar } from "./utils/tempcar.js";
|
|
19
|
-
import { createStorachaClient } from "./utils/
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
19
|
+
import { createStorachaClient, resolveNameFromConfig, resolveCryptoConfig, } from "./utils/config.js";
|
|
20
|
+
import { encodeDelegation } from "./utils/delegation.js";
|
|
21
|
+
import { publish } from "@storacha/ucn/pail/revision";
|
|
22
|
+
import { makeEncoder } from "./utils/encoder.js";
|
|
23
|
+
import { makeContentFetcher } from "./utils/contentfetcher.js";
|
|
24
|
+
import { Agent } from "@storacha/ucn/pail";
|
|
23
25
|
export class SyncEngine {
|
|
24
26
|
workspace;
|
|
25
27
|
blocks;
|
|
26
|
-
state = {
|
|
28
|
+
state = { running: false };
|
|
27
29
|
current = null;
|
|
28
30
|
pendingOps = [];
|
|
29
31
|
carFile = null;
|
|
30
32
|
lastSync = null;
|
|
31
33
|
syncLock = Promise.resolve();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
client;
|
|
35
|
+
name;
|
|
36
|
+
encoder;
|
|
37
|
+
contentFetcher;
|
|
38
|
+
remotes;
|
|
39
|
+
constructor(workspace, deps) {
|
|
36
40
|
this.workspace = workspace;
|
|
37
|
-
this.blocks =
|
|
41
|
+
this.blocks = deps.blocks;
|
|
42
|
+
this.client = deps.client;
|
|
43
|
+
this.name = deps.name;
|
|
44
|
+
this.encoder = deps.encoder;
|
|
45
|
+
this.contentFetcher = deps.contentFetcher;
|
|
46
|
+
this.remotes = deps.remotes;
|
|
38
47
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* Creates the Storacha client and resolves the UCN name.
|
|
42
|
-
*/
|
|
43
|
-
async init(config) {
|
|
44
|
-
const storachaClient = await createStorachaClient(config);
|
|
45
|
-
const agent = Agent.parse(config.agentKey);
|
|
46
|
-
let name;
|
|
47
|
-
if (config.nameArchive) {
|
|
48
|
-
// Restore from previously saved archive (has full state)
|
|
49
|
-
const archiveBytes = decodeDelegation(config.nameArchive);
|
|
50
|
-
name = await Name.extract(agent, archiveBytes);
|
|
51
|
-
}
|
|
52
|
-
else if (config.nameDelegation) {
|
|
53
|
-
// Reconstruct from delegation (granted by another device)
|
|
54
|
-
const nameBytes = decodeDelegation(config.nameDelegation);
|
|
55
|
-
const { ok: delegation } = await extract(nameBytes);
|
|
56
|
-
if (!delegation)
|
|
57
|
-
throw new Error("Failed to extract name delegation");
|
|
58
|
-
name = Name.from(agent, [delegation]);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
// First device — create a new name
|
|
62
|
-
name = await Name.create(agent);
|
|
63
|
-
}
|
|
64
|
-
this.state = { initialized: true, running: true, name, storachaClient };
|
|
65
|
-
// Set up encryption for private spaces
|
|
66
|
-
if (config.access?.type === "private") {
|
|
67
|
-
if (!config.planDelegation) {
|
|
68
|
-
throw new Error("Private space requires a plan delegation for KMS access");
|
|
69
|
-
}
|
|
70
|
-
const planBytes = decodeDelegation(config.planDelegation);
|
|
71
|
-
const { ok: planDel } = await extract(planBytes);
|
|
72
|
-
if (!planDel)
|
|
73
|
-
throw new Error("Failed to extract plan delegation");
|
|
74
|
-
const planDelForKMS = await delegatePlanningDelegationToKMS(agent, planDel);
|
|
75
|
-
const uploadBytes = decodeDelegation(config.uploadDelegation);
|
|
76
|
-
const { ok: uploadDel } = await extract(uploadBytes);
|
|
77
|
-
if (!uploadDel)
|
|
78
|
-
throw new Error("Failed to extract upload delegation");
|
|
79
|
-
this.encryptionConfig = makeEncryptionConfig(agent, config.spaceDID, [planDelForKMS, uploadDel]);
|
|
80
|
-
// For decrypt, uploadDelegation covers space/content/decrypt
|
|
81
|
-
this.decryptionConfig = makeDecryptionConfig(config.spaceDID, uploadDel, [planDelForKMS, uploadDel]);
|
|
82
|
-
this.encryptedClient = await getEncryptedClient(storachaClient);
|
|
83
|
-
}
|
|
48
|
+
async start() {
|
|
49
|
+
this.state = { running: true };
|
|
84
50
|
try {
|
|
85
|
-
const result = await Revision.resolve(this.blocks, name
|
|
51
|
+
const result = await Revision.resolve(this.blocks, this.name, {
|
|
52
|
+
remotes: this.remotes,
|
|
53
|
+
});
|
|
86
54
|
this.current = result.value;
|
|
87
55
|
await this.storeBlocks(result.additions);
|
|
88
56
|
}
|
|
@@ -95,6 +63,27 @@ export class SyncEngine {
|
|
|
95
63
|
}
|
|
96
64
|
}
|
|
97
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Initialize sync engine with device config.
|
|
68
|
+
* Creates the Storacha client and resolves the UCN name.
|
|
69
|
+
*/
|
|
70
|
+
static async fromConfig(workspace, config) {
|
|
71
|
+
const blocks = createWorkspaceBlockstore(workspace);
|
|
72
|
+
const client = await createStorachaClient(config);
|
|
73
|
+
const name = await resolveNameFromConfig(config);
|
|
74
|
+
// Set up encryption for private spaces
|
|
75
|
+
const crypto = await resolveCryptoConfig(config);
|
|
76
|
+
const encoder = makeEncoder(crypto);
|
|
77
|
+
const agent = Agent.parse(config.agentKey);
|
|
78
|
+
const contentFetcher = makeContentFetcher(crypto, blocks, client, agent);
|
|
79
|
+
return new SyncEngine(workspace, {
|
|
80
|
+
blocks,
|
|
81
|
+
name,
|
|
82
|
+
client,
|
|
83
|
+
encoder,
|
|
84
|
+
contentFetcher,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
98
87
|
/**
|
|
99
88
|
* Require running state or throw.
|
|
100
89
|
*/
|
|
@@ -102,16 +91,7 @@ export class SyncEngine {
|
|
|
102
91
|
if (!this.state.running) {
|
|
103
92
|
throw new Error("Sync engine not running");
|
|
104
93
|
}
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Require state is initialized or throw.
|
|
109
|
-
*/
|
|
110
|
-
requireInitialized() {
|
|
111
|
-
if (!this.state.initialized) {
|
|
112
|
-
throw new Error("Sync engine not initialized");
|
|
113
|
-
}
|
|
114
|
-
return this.state;
|
|
94
|
+
return;
|
|
115
95
|
}
|
|
116
96
|
/**
|
|
117
97
|
* Process a batch of file changes.
|
|
@@ -121,7 +101,9 @@ export class SyncEngine {
|
|
|
121
101
|
if (!this.carFile) {
|
|
122
102
|
this.carFile = await makeTempCar();
|
|
123
103
|
}
|
|
124
|
-
const pendingOps = await processChanges(changes, this.workspace, this.current, this.blocks,
|
|
104
|
+
const pendingOps = await processChanges(changes, this.workspace, this.current, this.blocks, async (block) => {
|
|
105
|
+
await this.storeForUpload([block]);
|
|
106
|
+
}, this.encoder, this.contentFetcher);
|
|
125
107
|
this.pendingOps.push(...pendingOps);
|
|
126
108
|
}
|
|
127
109
|
/**
|
|
@@ -133,13 +115,14 @@ export class SyncEngine {
|
|
|
133
115
|
return op;
|
|
134
116
|
}
|
|
135
117
|
async _syncInner() {
|
|
136
|
-
|
|
118
|
+
this.requireRunning();
|
|
137
119
|
const beforeEntries = await this.getPailEntries();
|
|
138
120
|
if (this.pendingOps.length === 0) {
|
|
139
121
|
// No pending ops — just pull remote
|
|
140
122
|
try {
|
|
141
|
-
const result = await Revision.resolve(this.blocks, name, {
|
|
123
|
+
const result = await Revision.resolve(this.blocks, this.name, {
|
|
142
124
|
base: this.current ?? undefined,
|
|
125
|
+
remotes: this.remotes,
|
|
143
126
|
});
|
|
144
127
|
await this.storeBlocks(result.additions);
|
|
145
128
|
this.current = result.value;
|
|
@@ -155,22 +138,19 @@ export class SyncEngine {
|
|
|
155
138
|
try {
|
|
156
139
|
while (pendingOps.length > 0) {
|
|
157
140
|
if (!this.carFile) {
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
const { current, revisionBlocks, event, remainingOps } = await applyPendingOps(this.blocks, name, this.current, pendingOps);
|
|
161
|
-
this.current = current;
|
|
162
|
-
for (const block of revisionBlocks) {
|
|
163
|
-
await this.carFile.put(block);
|
|
164
|
-
}
|
|
165
|
-
await this.storeBlocks(revisionBlocks);
|
|
166
|
-
if (event) {
|
|
167
|
-
await this.carFile.put(event);
|
|
168
|
-
await this.storeBlocks([event]);
|
|
141
|
+
this.carFile = await makeTempCar();
|
|
169
142
|
}
|
|
170
|
-
|
|
143
|
+
const { revision, additions, remainingOps } = await applyPendingOps(this.blocks, this.current, pendingOps);
|
|
144
|
+
await this.storeForUpload(additions);
|
|
145
|
+
await this.storeForUpload([revision.event]);
|
|
171
146
|
await this.possiblyUploadCAR();
|
|
172
|
-
|
|
147
|
+
const { value, additions: publishAdditions } = await publish(this.blocks, this.name, revision, { remotes: this.remotes });
|
|
148
|
+
pendingOps = remainingOps;
|
|
149
|
+
this.current = value;
|
|
150
|
+
// save the next round of additions to CAR so they're ready to go if there are more pending ops, otherwise we would have to wait until the next sync call to upload them
|
|
151
|
+
if (publishAdditions.length > 0) {
|
|
173
152
|
this.carFile = await makeTempCar();
|
|
153
|
+
await this.storeForUpload(publishAdditions);
|
|
174
154
|
}
|
|
175
155
|
}
|
|
176
156
|
}
|
|
@@ -183,12 +163,9 @@ export class SyncEngine {
|
|
|
183
163
|
const afterEntries = await this.getPailEntries();
|
|
184
164
|
const remoteChanges = diffRemoteChanges(beforeEntries, afterEntries);
|
|
185
165
|
if (remoteChanges.length > 0) {
|
|
186
|
-
await applyRemoteChanges(remoteChanges, afterEntries, this.workspace, {
|
|
166
|
+
await applyRemoteChanges(remoteChanges, afterEntries, this.workspace, this.contentFetcher, {
|
|
187
167
|
blocks: this.blocks,
|
|
188
168
|
current: this.current ?? undefined,
|
|
189
|
-
decrypt: this.decryptionConfig && this.encryptedClient
|
|
190
|
-
? makeDecryptFn(this.encryptedClient, this.decryptionConfig, name.agent)
|
|
191
|
-
: undefined,
|
|
192
169
|
});
|
|
193
170
|
}
|
|
194
171
|
this.lastSync = Date.now();
|
|
@@ -198,12 +175,12 @@ export class SyncEngine {
|
|
|
198
175
|
*/
|
|
199
176
|
async possiblyUploadCAR() {
|
|
200
177
|
if (this.carFile) {
|
|
201
|
-
|
|
178
|
+
this.requireRunning();
|
|
202
179
|
const readableCar = await this.carFile.switchToReadable();
|
|
203
180
|
this.carFile = null;
|
|
204
181
|
if (readableCar) {
|
|
205
182
|
try {
|
|
206
|
-
await
|
|
183
|
+
await this.client.uploadCAR(readableCar.readable);
|
|
207
184
|
}
|
|
208
185
|
finally {
|
|
209
186
|
await readableCar.cleanup();
|
|
@@ -230,14 +207,7 @@ export class SyncEngine {
|
|
|
230
207
|
*/
|
|
231
208
|
async stop() {
|
|
232
209
|
this.syncLock = this.syncLock.then(() => {
|
|
233
|
-
this.state =
|
|
234
|
-
? {
|
|
235
|
-
initialized: true,
|
|
236
|
-
running: false,
|
|
237
|
-
name: this.state.name,
|
|
238
|
-
storachaClient: this.state.storachaClient,
|
|
239
|
-
}
|
|
240
|
-
: { initialized: false, running: false };
|
|
210
|
+
this.state = { running: false };
|
|
241
211
|
});
|
|
242
212
|
return this.syncLock;
|
|
243
213
|
}
|
|
@@ -251,10 +221,11 @@ export class SyncEngine {
|
|
|
251
221
|
return op;
|
|
252
222
|
}
|
|
253
223
|
async _pullRemoteInner() {
|
|
254
|
-
|
|
224
|
+
this.requireRunning();
|
|
255
225
|
try {
|
|
256
|
-
const result = await Revision.resolve(this.blocks, name, {
|
|
226
|
+
const result = await Revision.resolve(this.blocks, this.name, {
|
|
257
227
|
base: this.current ?? undefined,
|
|
228
|
+
remotes: this.remotes,
|
|
258
229
|
});
|
|
259
230
|
await this.storeBlocks(result.additions);
|
|
260
231
|
this.current = result.value;
|
|
@@ -266,12 +237,9 @@ export class SyncEngine {
|
|
|
266
237
|
const entries = await this.getPailEntries();
|
|
267
238
|
if (entries.size > 0) {
|
|
268
239
|
const allPaths = [...entries.keys()];
|
|
269
|
-
await applyRemoteChanges(allPaths, entries, this.workspace, {
|
|
240
|
+
await applyRemoteChanges(allPaths, entries, this.workspace, this.contentFetcher, {
|
|
270
241
|
blocks: this.blocks,
|
|
271
242
|
current: this.current ?? undefined,
|
|
272
|
-
decrypt: this.decryptionConfig && this.encryptedClient
|
|
273
|
-
? makeDecryptFn(this.encryptedClient, this.decryptionConfig, name.agent)
|
|
274
|
-
: undefined,
|
|
275
243
|
});
|
|
276
244
|
}
|
|
277
245
|
this.lastSync = Date.now();
|
|
@@ -307,8 +275,7 @@ export class SyncEngine {
|
|
|
307
275
|
};
|
|
308
276
|
}
|
|
309
277
|
async exportNameArchive() {
|
|
310
|
-
const
|
|
311
|
-
const bytes = await name.archive();
|
|
278
|
+
const bytes = await this.name.archive();
|
|
312
279
|
return encodeDelegation(bytes);
|
|
313
280
|
}
|
|
314
281
|
async storeBlocks(blocks) {
|
|
@@ -316,4 +283,8 @@ export class SyncEngine {
|
|
|
316
283
|
await this.blocks.put(block);
|
|
317
284
|
}
|
|
318
285
|
}
|
|
286
|
+
async storeForUpload(blocks) {
|
|
287
|
+
await this.carFile.put(blocks);
|
|
288
|
+
await this.storeBlocks(blocks);
|
|
289
|
+
}
|
|
319
290
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { UnknownLink } from "multiformats";
|
|
5
5
|
import type { Block } from "multiformats";
|
|
6
6
|
import type { CID } from "multiformats/cid";
|
|
7
|
+
import type { DecryptionConfig, EncryptionConfig } from "@storacha/encrypt-upload-client/types";
|
|
7
8
|
/** Plugin configuration (from openclaw.plugin.json schema) */
|
|
8
9
|
export interface SyncPluginConfig {
|
|
9
10
|
enabled: boolean;
|
|
@@ -58,12 +59,17 @@ export interface FileChange {
|
|
|
58
59
|
type: "add" | "change" | "unlink";
|
|
59
60
|
path: string;
|
|
60
61
|
}
|
|
61
|
-
/**
|
|
62
|
-
export interface
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
/** Bundled encryption + decryption config for private spaces. */
|
|
63
|
+
export interface CryptoConfig {
|
|
64
|
+
encryptionConfig: EncryptionConfig;
|
|
65
|
+
decryptionConfig: DecryptionConfig;
|
|
66
|
+
}
|
|
67
|
+
/** Minimal storage client interface (subset of @storacha/client used by SyncEngine). */
|
|
68
|
+
export interface StorageClient {
|
|
69
|
+
uploadCAR(car: import("@storacha/upload-client/types").BlobLike): Promise<unknown>;
|
|
66
70
|
}
|
|
71
|
+
export type Encoder = (file: Blob) => Promise<ReadableStream<Block>>;
|
|
72
|
+
export type ContentFetcher = (cid: CID) => Promise<Uint8Array>;
|
|
67
73
|
/** Diff operation for pail */
|
|
68
74
|
export interface PailOp {
|
|
69
75
|
type: "put" | "del";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAE/C,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE;QACV,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,CAAC;AAEvD,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,yBAAyB;AACzB,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4BAA4B;IAC5B,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,GAAG,EAAE,OAAO,+BAA+B,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AAErE,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAE/D,8BAA8B;AAC9B,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,GAAG,CAAC;CACb"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for building runtime objects from DeviceConfig.
|
|
3
|
+
*
|
|
4
|
+
* - createStorachaClient: Storacha upload client
|
|
5
|
+
* - resolveNameFromConfig: UCN Pail NameView
|
|
6
|
+
* - resolveCryptoConfig: encryption / decryption config (private spaces)
|
|
7
|
+
*/
|
|
8
|
+
import type { NameView } from "@storacha/ucn/pail/api";
|
|
9
|
+
import type { DeviceConfig, CryptoConfig } from "../types/index.js";
|
|
10
|
+
import type { Client } from "@storacha/client";
|
|
11
|
+
/**
|
|
12
|
+
* Build a Storacha Client from device config.
|
|
13
|
+
* Requires agentKey and uploadDelegation to be present.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createStorachaClient(config: DeviceConfig): Promise<Client>;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a UCN Pail NameView from device config.
|
|
18
|
+
* Picks the right path: archive → delegation → create new.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveNameFromConfig(config: DeviceConfig): Promise<NameView>;
|
|
21
|
+
/**
|
|
22
|
+
* Build encryption/decryption config from device config.
|
|
23
|
+
* Returns null for public spaces.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveCryptoConfig(config: DeviceConfig): Promise<CryptoConfig | null>;
|
|
26
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAUvD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,QAAQ,CAAC,CAcnB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmC9B"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for building runtime objects from DeviceConfig.
|
|
3
|
+
*
|
|
4
|
+
* - createStorachaClient: Storacha upload client
|
|
5
|
+
* - resolveNameFromConfig: UCN Pail NameView
|
|
6
|
+
* - resolveCryptoConfig: encryption / decryption config (private spaces)
|
|
7
|
+
*/
|
|
8
|
+
import { Agent as PailAgent, Name } from "@storacha/ucn/pail";
|
|
9
|
+
import { create as createClient } from "@storacha/client";
|
|
10
|
+
import { StoreMemory } from "@storacha/client/stores/memory";
|
|
11
|
+
import { extract } from "@storacha/client/delegation";
|
|
12
|
+
import { decodeDelegation } from "./delegation.js";
|
|
13
|
+
import { makeEncryptionConfig, makeDecryptionConfig, delegatePlanningDelegationToKMS, } from "./crypto.js";
|
|
14
|
+
/**
|
|
15
|
+
* Build a Storacha Client from device config.
|
|
16
|
+
* Requires agentKey and uploadDelegation to be present.
|
|
17
|
+
*/
|
|
18
|
+
export async function createStorachaClient(config) {
|
|
19
|
+
if (!config.uploadDelegation) {
|
|
20
|
+
throw new Error("No upload delegation in device config");
|
|
21
|
+
}
|
|
22
|
+
const agent = PailAgent.parse(config.agentKey);
|
|
23
|
+
const client = await createClient({
|
|
24
|
+
principal: agent,
|
|
25
|
+
store: new StoreMemory(),
|
|
26
|
+
});
|
|
27
|
+
// Import the upload delegation (space → agent)
|
|
28
|
+
const uploadBytes = decodeDelegation(config.uploadDelegation);
|
|
29
|
+
const { ok: uploadDelegation, error: uploadErr } = await extract(uploadBytes);
|
|
30
|
+
if (!uploadDelegation) {
|
|
31
|
+
throw new Error(`Failed to extract upload delegation: ${uploadErr}`);
|
|
32
|
+
}
|
|
33
|
+
// addSpace registers the space and adds the delegation as a proof
|
|
34
|
+
await client.addSpace(uploadDelegation);
|
|
35
|
+
// Set the space as current so uploads target it
|
|
36
|
+
const spaceDID = uploadDelegation.capabilities[0]?.with;
|
|
37
|
+
if (spaceDID) {
|
|
38
|
+
await client.setCurrentSpace(spaceDID);
|
|
39
|
+
}
|
|
40
|
+
return client;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a UCN Pail NameView from device config.
|
|
44
|
+
* Picks the right path: archive → delegation → create new.
|
|
45
|
+
*/
|
|
46
|
+
export async function resolveNameFromConfig(config) {
|
|
47
|
+
const agent = PailAgent.parse(config.agentKey);
|
|
48
|
+
if (config.nameArchive) {
|
|
49
|
+
const archiveBytes = decodeDelegation(config.nameArchive);
|
|
50
|
+
return Name.extract(agent, archiveBytes);
|
|
51
|
+
}
|
|
52
|
+
else if (config.nameDelegation) {
|
|
53
|
+
const nameBytes = decodeDelegation(config.nameDelegation);
|
|
54
|
+
const { ok: delegation } = await extract(nameBytes);
|
|
55
|
+
if (!delegation)
|
|
56
|
+
throw new Error("Failed to extract name delegation");
|
|
57
|
+
return Name.from(agent, [delegation]);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return Name.create(agent);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build encryption/decryption config from device config.
|
|
65
|
+
* Returns null for public spaces.
|
|
66
|
+
*/
|
|
67
|
+
export async function resolveCryptoConfig(config) {
|
|
68
|
+
if (config.access?.type !== "private")
|
|
69
|
+
return null;
|
|
70
|
+
if (!config.planDelegation) {
|
|
71
|
+
throw new Error("Private space requires a plan delegation for KMS access");
|
|
72
|
+
}
|
|
73
|
+
const agent = PailAgent.parse(config.agentKey);
|
|
74
|
+
const planBytes = decodeDelegation(config.planDelegation);
|
|
75
|
+
const { ok: planDel } = await extract(planBytes);
|
|
76
|
+
if (!planDel)
|
|
77
|
+
throw new Error("Failed to extract plan delegation");
|
|
78
|
+
const planDelForKMS = await delegatePlanningDelegationToKMS(agent, planDel);
|
|
79
|
+
const uploadBytes = decodeDelegation(config.uploadDelegation);
|
|
80
|
+
const { ok: uploadDel } = await extract(uploadBytes);
|
|
81
|
+
if (!uploadDel)
|
|
82
|
+
throw new Error("Failed to extract upload delegation");
|
|
83
|
+
const encryptionConfig = makeEncryptionConfig(agent, config.spaceDID, [planDelForKMS, uploadDel]);
|
|
84
|
+
const decryptionConfig = makeDecryptionConfig(config.spaceDID, uploadDel, [planDelForKMS, uploadDel]);
|
|
85
|
+
return {
|
|
86
|
+
encryptionConfig,
|
|
87
|
+
decryptionConfig,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { BlockFetcher } from "@web3-storage/pail/api";
|
|
2
|
+
import { ContentFetcher, CryptoConfig } from "../types/index.js";
|
|
3
|
+
import { Signer } from "@ucanto/interface";
|
|
4
|
+
import { Client } from "@storacha/client";
|
|
5
|
+
export declare const makeContentFetcher: (cryptoConfig: CryptoConfig | null, blocks: BlockFetcher, client: Client, agent: Signer) => ContentFetcher;
|
|
6
|
+
//# sourceMappingURL=contentfetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentfetcher.d.ts","sourceRoot":"","sources":["../../src/utils/contentfetcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK1C,eAAO,MAAM,kBAAkB,GAC7B,cAAc,YAAY,GAAG,IAAI,EACjC,QAAQ,YAAY,EACpB,QAAQ,MAAM,EACd,OAAO,MAAM,KACZ,cAoCF,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { decrypt } from "@storacha/capabilities/space";
|
|
2
|
+
import { decryptFile } from "@storacha/encrypt-upload-client/utils/decrypt";
|
|
3
|
+
import { getKMSCryptoAdapter } from "./crypto.js";
|
|
4
|
+
import { exporter } from "ipfs-unixfs-exporter";
|
|
5
|
+
export const makeContentFetcher = (cryptoConfig, blocks, client, agent) => {
|
|
6
|
+
if (cryptoConfig) {
|
|
7
|
+
const { decryptionConfig } = cryptoConfig;
|
|
8
|
+
return async (cid) => {
|
|
9
|
+
const decryptDelegation = await decrypt.delegate({
|
|
10
|
+
issuer: agent,
|
|
11
|
+
audience: agent,
|
|
12
|
+
with: decryptionConfig.spaceDID,
|
|
13
|
+
nb: {
|
|
14
|
+
resource: cid,
|
|
15
|
+
},
|
|
16
|
+
expiration: Math.floor(Date.now() / 1000) + 60 * 15, // 15 minutes
|
|
17
|
+
proofs: [decryptionConfig.decryptDelegation],
|
|
18
|
+
});
|
|
19
|
+
const { stream } = await decryptFile(getKMSCryptoAdapter(), client, readableStorageFromFetcher(blocks), cid, {
|
|
20
|
+
...decryptionConfig,
|
|
21
|
+
decryptDelegation,
|
|
22
|
+
});
|
|
23
|
+
return drainStream(stream);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return async (cid) => {
|
|
27
|
+
const entry = await exporter(cid, readableStorageFromFetcher(blocks));
|
|
28
|
+
if (entry.type !== "file" && entry.type !== "raw") {
|
|
29
|
+
throw new Error(`Expected file or raw entry for CID ${cid}, got ${entry.type}`);
|
|
30
|
+
}
|
|
31
|
+
return drainStream(entry.content());
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
const readableStorageFromFetcher = (blocks) => ({
|
|
35
|
+
get: async (cid) => {
|
|
36
|
+
const block = await blocks.get(cid);
|
|
37
|
+
if (!block)
|
|
38
|
+
throw new Error(`Block not found for CID ${cid}`);
|
|
39
|
+
return block.bytes;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* Drain an AsyncIterable (or ReadableStream) into a single Uint8Array.
|
|
44
|
+
*/
|
|
45
|
+
async function drainStream(iter) {
|
|
46
|
+
const chunks = [];
|
|
47
|
+
for await (const chunk of iter) {
|
|
48
|
+
chunks.push(chunk);
|
|
49
|
+
}
|
|
50
|
+
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
51
|
+
const result = new Uint8Array(totalLength);
|
|
52
|
+
let offset = 0;
|
|
53
|
+
for (const chunk of chunks) {
|
|
54
|
+
result.set(chunk, offset);
|
|
55
|
+
offset += chunk.length;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
package/dist/utils/crypto.d.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Uses @storacha/encrypt-upload-client with KMS-based key management.
|
|
4
4
|
*/
|
|
5
5
|
import type { Block } from "multiformats";
|
|
6
|
-
import type { CID } from "multiformats/cid";
|
|
7
6
|
import type { Client } from "@storacha/client";
|
|
8
7
|
import type { Proof, Signer } from "@ucanto/interface";
|
|
9
8
|
type SpaceDID = `did:key:${string}`;
|
|
@@ -21,10 +20,5 @@ export declare function makeDecryptionConfig(spaceDID: SpaceDID, decryptDelegati
|
|
|
21
20
|
* (UnixFS-encoded encrypted content + metadata block appended).
|
|
22
21
|
*/
|
|
23
22
|
export declare function encryptToBlockStream(file: BlobLike, encryptionConfig: EncryptionConfig): Promise<ReadableStream<Block>>;
|
|
24
|
-
/**
|
|
25
|
-
* Create a decrypt function for mdsync resolveValue.
|
|
26
|
-
* Fetches encrypted content by CID via EncryptedClient and returns decrypted bytes.
|
|
27
|
-
*/
|
|
28
|
-
export declare function makeDecryptFn(encryptedClient: EncryptedClient, decryptionConfig: DecryptionConfig, agent: Signer): (cid: CID) => Promise<Uint8Array>;
|
|
29
23
|
export {};
|
|
30
24
|
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACvD,KAAK,QAAQ,GAAG,WAAW,MAAM,EAAE,CAAC;AACpC,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,QAAQ,EACR,eAAe,EAChB,MAAM,uCAAuC,CAAC;AAO/C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAS/C,wBAAgB,mBAAmB,IAAI,aAAa,CAInD;AAED,wBAAsB,kBAAkB,CACtC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,eAAe,CAAC,CAM1B;AAED,wBAAsB,+BAA+B,CACnD,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,UAAU,GACzB,OAAO,CAAC,KAAK,CAAC,CAUhB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,WAAW,MAAM,EAAE,CAAA;CAAE,EAC1C,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,KAAK,EAAE,GACd,gBAAgB,CAMlB;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,iBAAiB,EAAE,KAAK,EACxB,MAAM,CAAC,EAAE,KAAK,EAAE,GACf,gBAAgB,CAMlB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,QAAQ,EACd,gBAAgB,EAAE,gBAAgB,GACjC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAIhC"}
|
package/dist/utils/crypto.js
CHANGED
|
@@ -6,7 +6,6 @@ import { createGenericKMSAdapter } from "@storacha/encrypt-upload-client/factori
|
|
|
6
6
|
import { create as createEncryptedClient } from "@storacha/encrypt-upload-client";
|
|
7
7
|
import { encryptFile, encryptedBlockStream, } from "@storacha/encrypt-upload-client/utils/encrypt";
|
|
8
8
|
import { delegate } from "@ucanto/core";
|
|
9
|
-
import { decrypt } from "@storacha/capabilities/space";
|
|
10
9
|
const KMS_SERVICE_URL = "https://ucan-kms-production.protocol-labs.workers.dev";
|
|
11
10
|
const KMS_SERVICE_DID = "did:key:z6MksQJobJmBfPhjHWgFXVppqM6Fcjc1k7xu4z6xvusVrtKv";
|
|
12
11
|
let cachedAdapter = null;
|
|
@@ -57,47 +56,3 @@ export async function encryptToBlockStream(file, encryptionConfig) {
|
|
|
57
56
|
const payload = await encryptFile(adapter, file, encryptionConfig);
|
|
58
57
|
return encryptedBlockStream(payload, adapter);
|
|
59
58
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Drain a ReadableStream into a single Uint8Array.
|
|
62
|
-
*/
|
|
63
|
-
async function drainStream(stream) {
|
|
64
|
-
const reader = stream.getReader();
|
|
65
|
-
const chunks = [];
|
|
66
|
-
while (true) {
|
|
67
|
-
const { done, value } = await reader.read();
|
|
68
|
-
if (done)
|
|
69
|
-
break;
|
|
70
|
-
chunks.push(value);
|
|
71
|
-
}
|
|
72
|
-
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
73
|
-
const result = new Uint8Array(totalLength);
|
|
74
|
-
let offset = 0;
|
|
75
|
-
for (const chunk of chunks) {
|
|
76
|
-
result.set(chunk, offset);
|
|
77
|
-
offset += chunk.length;
|
|
78
|
-
}
|
|
79
|
-
return result;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Create a decrypt function for mdsync resolveValue.
|
|
83
|
-
* Fetches encrypted content by CID via EncryptedClient and returns decrypted bytes.
|
|
84
|
-
*/
|
|
85
|
-
export function makeDecryptFn(encryptedClient, decryptionConfig, agent) {
|
|
86
|
-
return async (cid) => {
|
|
87
|
-
const decryptDelegation = await decrypt.delegate({
|
|
88
|
-
issuer: agent,
|
|
89
|
-
audience: agent,
|
|
90
|
-
with: decryptionConfig.spaceDID,
|
|
91
|
-
nb: {
|
|
92
|
-
resource: cid,
|
|
93
|
-
},
|
|
94
|
-
expiration: Math.floor(Date.now() / 1000) + 60 * 15, // 15 minutes
|
|
95
|
-
proofs: [decryptionConfig.decryptDelegation],
|
|
96
|
-
});
|
|
97
|
-
const { stream } = await encryptedClient.retrieveAndDecryptFile(cid, {
|
|
98
|
-
...decryptionConfig,
|
|
99
|
-
decryptDelegation,
|
|
100
|
-
});
|
|
101
|
-
return drainStream(stream);
|
|
102
|
-
};
|
|
103
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"differ.d.ts","sourceRoot":"","sources":["../../src/utils/differ.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,0CAA0C;AAC1C,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE3C,iDAAiD;AACjD,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE5C;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CAmB5E;AAGD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,WAAW,GACjB,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"differ.d.ts","sourceRoot":"","sources":["../../src/utils/differ.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,0CAA0C;AAC1C,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE3C,iDAAiD;AACjD,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE5C;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CAmB5E;AAGD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,WAAW,GACjB,MAAM,EAAE,CAkBV"}
|
package/dist/utils/differ.js
CHANGED
|
@@ -43,5 +43,11 @@ export function diffRemoteChanges(before, after) {
|
|
|
43
43
|
changed.push(path);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
// Detect deletions: paths in before but not in after
|
|
47
|
+
for (const path of before.keys()) {
|
|
48
|
+
if (!after.has(path)) {
|
|
49
|
+
changed.push(path);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
46
52
|
return changed;
|
|
47
53
|
}
|
package/dist/utils/encoder.d.ts
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Uses @storacha/upload-client's UnixFS encoding to generate
|
|
5
|
-
* content-addressed blocks for each file.
|
|
6
|
-
*/
|
|
7
|
-
import type { EncodedFile } from "../types/index.js";
|
|
8
|
-
/**
|
|
9
|
-
* Encode a single file to UnixFS blocks
|
|
10
|
-
*/
|
|
11
|
-
export declare function encodeWorkspaceFile(workspacePath: string, relativePath: string): Promise<EncodedFile>;
|
|
12
|
-
/**
|
|
13
|
-
* Encode multiple files, returning all encoded results
|
|
14
|
-
*/
|
|
15
|
-
export declare function encodeFiles(workspacePath: string, relativePaths: string[]): Promise<EncodedFile[]>;
|
|
1
|
+
import { CryptoConfig, Encoder } from "../types/index.js";
|
|
2
|
+
export declare const makeEncoder: (cryptoConfig: CryptoConfig | null) => Encoder;
|
|
16
3
|
//# sourceMappingURL=encoder.d.ts.map
|