@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.
Files changed (43) hide show
  1. package/LICENSE.md +204 -0
  2. package/README.md +117 -110
  3. package/dist/commands.d.ts.map +1 -1
  4. package/dist/commands.js +3 -3
  5. package/dist/handlers/apply.d.ts +3 -11
  6. package/dist/handlers/apply.d.ts.map +1 -1
  7. package/dist/handlers/apply.js +5 -32
  8. package/dist/handlers/process.d.ts +3 -6
  9. package/dist/handlers/process.d.ts.map +1 -1
  10. package/dist/handlers/process.js +30 -60
  11. package/dist/handlers/remote.d.ts +1 -3
  12. package/dist/handlers/remote.d.ts.map +1 -1
  13. package/dist/handlers/remote.js +4 -34
  14. package/dist/index.d.ts +0 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +0 -1
  17. package/dist/mdsync/index.d.ts +2 -2
  18. package/dist/mdsync/index.d.ts.map +1 -1
  19. package/dist/mdsync/index.js +14 -17
  20. package/dist/plugin.js +2 -2
  21. package/dist/sync.d.ts +20 -10
  22. package/dist/sync.d.ts.map +1 -1
  23. package/dist/sync.js +76 -105
  24. package/dist/types/index.d.ts +11 -5
  25. package/dist/types/index.d.ts.map +1 -1
  26. package/dist/utils/config.d.ts +26 -0
  27. package/dist/utils/config.d.ts.map +1 -0
  28. package/dist/utils/config.js +89 -0
  29. package/dist/utils/contentfetcher.d.ts +6 -0
  30. package/dist/utils/contentfetcher.d.ts.map +1 -0
  31. package/dist/utils/contentfetcher.js +58 -0
  32. package/dist/utils/crypto.d.ts +0 -6
  33. package/dist/utils/crypto.d.ts.map +1 -1
  34. package/dist/utils/crypto.js +0 -45
  35. package/dist/utils/differ.d.ts.map +1 -1
  36. package/dist/utils/differ.js +6 -0
  37. package/dist/utils/encoder.d.ts +2 -15
  38. package/dist/utils/encoder.d.ts.map +1 -1
  39. package/dist/utils/encoder.js +8 -49
  40. package/dist/utils/tempcar.d.ts +1 -1
  41. package/dist/utils/tempcar.d.ts.map +1 -1
  42. package/dist/utils/tempcar.js +4 -2
  43. 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 { Agent, Name, NoValueError, Revision } from "@storacha/ucn/pail";
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/client.js";
20
- import { decodeDelegation, encodeDelegation } from "./utils/delegation.js";
21
- import { extract } from "@storacha/client/delegation";
22
- import { makeEncryptionConfig, makeDecryptionConfig, getEncryptedClient, delegatePlanningDelegationToKMS, makeDecryptFn, } from "./utils/crypto.js";
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 = { initialized: false, running: false };
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
- encryptionConfig;
33
- decryptionConfig;
34
- encryptedClient;
35
- constructor(workspace) {
34
+ client;
35
+ name;
36
+ encoder;
37
+ contentFetcher;
38
+ remotes;
39
+ constructor(workspace, deps) {
36
40
  this.workspace = workspace;
37
- this.blocks = createWorkspaceBlockstore(workspace);
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
- * Initialize sync engine with device config.
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 this.state;
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, (block) => this.carFile.put(block), (block) => this.blocks.put(block), this.encryptionConfig);
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
- const { name } = this.requireRunning();
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
- throw new Error("CAR file not initialized");
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
- pendingOps = remainingOps;
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
- if (pendingOps.length > 0) {
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
- const { storachaClient } = this.requireRunning();
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 storachaClient.uploadCAR(readableCar.readable);
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 = this.state.initialized
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
- const { name } = this.requireRunning();
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 { name } = this.requireInitialized();
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
  }
@@ -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
- /** Encoded file result */
62
- export interface EncodedFile {
63
- path: string;
64
- blocks: ReadableStream<Block>;
65
- size: number;
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;AAE5C,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,0BAA0B;AAC1B,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8BAA8B;AAC9B,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,GAAG,CAAC;CACb"}
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
+ }
@@ -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;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAC5C,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;AAU/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;AAuBD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,eAAe,EAChC,gBAAgB,EAAE,gBAAgB,EAClC,KAAK,EAAE,MAAM,GACZ,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,UAAU,CAAC,CAkBnC"}
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"}
@@ -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,CAWV"}
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"}
@@ -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
  }
@@ -1,16 +1,3 @@
1
- /**
2
- * File encoder - converts files to UnixFS DAG with root CID
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