@twin.org/blob-storage-connector-ipfs 0.0.3-next.9 → 0.9.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/README.md CHANGED
@@ -13,7 +13,7 @@ npm install @twin.org/blob-storage-connector-ipfs
13
13
  To perform testing of this component it may be necessary to launch a local instance to communicate with.
14
14
 
15
15
  ```shell
16
- docker run -d --name twin-blob-storage-ipfs -p 4001:4001 -p 4001:4001/udp -p 8080:8080 -p 5001:5001 ipfs/kubo:latest
16
+ docker run -d --name twin-blob-storage-ipfs -p 24001:4001 -p 24001:4001/udp -p 28080:8080 -p 25001:5001 ipfs/kubo:latest
17
17
  ```
18
18
 
19
19
  ## Examples
@@ -1,4 +1,6 @@
1
- import { GeneralError, Guards, HealthStatus, Is, StringHelper, Urn } from "@twin.org/core";
1
+ import { ContextIdHelper, ContextIdStore } from "@twin.org/context";
2
+ import { BaseError, ComponentFactory, Converter, GeneralError, Guards, HealthStatus, Is, StringHelper, Urn } from "@twin.org/core";
3
+ import { Blake2b } from "@twin.org/crypto";
2
4
  import { HeaderHelper, HeaderTypes, HttpMethod, MimeTypes } from "@twin.org/web";
3
5
  /**
4
6
  * Class for performing blob storage operations on IPFS.
@@ -61,7 +63,10 @@ export class IpfsBlobStorageConnector {
61
63
  {
62
64
  source: IpfsBlobStorageConnector.CLASS_NAME,
63
65
  status: HealthStatus.Ok,
64
- description: "healthDescription"
66
+ description: "healthDescription",
67
+ data: {
68
+ apiUrl: this._config.apiUrl
69
+ }
65
70
  }
66
71
  ];
67
72
  }
@@ -72,7 +77,10 @@ export class IpfsBlobStorageConnector {
72
77
  source: IpfsBlobStorageConnector.CLASS_NAME,
73
78
  status: HealthStatus.Error,
74
79
  description: "healthDescription",
75
- message: "healthCheckFailed"
80
+ message: "healthCheckFailed",
81
+ data: {
82
+ apiUrl: this._config.apiUrl
83
+ }
76
84
  }
77
85
  ];
78
86
  }
@@ -99,7 +107,11 @@ export class IpfsBlobStorageConnector {
99
107
  const response = await fetch(`${this._config.apiUrl}/add?pin=true`, fetchOptions);
100
108
  if (response.ok) {
101
109
  const result = (await response.json());
102
- return `blob:${new Urn(IpfsBlobStorageConnector.NAMESPACE, result.Hash).toString()}`;
110
+ const partitionSegment = await this.buildPartitionSegment();
111
+ const urnParts = Is.stringValue(partitionSegment)
112
+ ? [result.Hash, partitionSegment]
113
+ : [result.Hash];
114
+ return `blob:${new Urn(IpfsBlobStorageConnector.NAMESPACE, urnParts).toString()}`;
103
115
  }
104
116
  const error = await response.json();
105
117
  throw new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, "fetchFail", error);
@@ -122,6 +134,14 @@ export class IpfsBlobStorageConnector {
122
134
  id
123
135
  });
124
136
  }
137
+ const partitionSegment = await this.buildPartitionSegment();
138
+ const storedPartitionSegment = Is.stringValue(urnParsed.namespaceSpecific(2))
139
+ ? urnParsed.namespaceSpecific(2)
140
+ : undefined;
141
+ if (partitionSegment !== storedPartitionSegment) {
142
+ return undefined;
143
+ }
144
+ const ipfsHash = urnParsed.namespaceSpecific(1).split(":")[0];
125
145
  try {
126
146
  const fetchOptions = {
127
147
  method: HttpMethod.POST,
@@ -130,16 +150,11 @@ export class IpfsBlobStorageConnector {
130
150
  }
131
151
  };
132
152
  this.addSecurity(fetchOptions);
133
- const responseStat = await fetch(`${this._config.apiUrl}/block/stat?arg=${urnParsed.namespaceSpecific(1)}&local=true`, fetchOptions);
134
- if (!responseStat.ok) {
135
- const resp = await responseStat.json();
136
- if (Is.object(resp) &&
137
- Is.stringValue(resp.Message) &&
138
- resp.Message.includes("not found")) {
139
- return;
140
- }
153
+ const pinResponse = await fetch(`${this._config.apiUrl}/pin/ls?arg=${ipfsHash}`, fetchOptions);
154
+ if (!pinResponse.ok) {
155
+ return undefined;
141
156
  }
142
- const response = await fetch(`${this._config.apiUrl}/cat?arg=${urnParsed.namespaceSpecific(1)}`, fetchOptions);
157
+ const response = await fetch(`${this._config.apiUrl}/cat?arg=${ipfsHash}`, fetchOptions);
143
158
  if (response.ok) {
144
159
  const result = await response.arrayBuffer();
145
160
  return new Uint8Array(result);
@@ -151,6 +166,98 @@ export class IpfsBlobStorageConnector {
151
166
  throw new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, "getBlobFailed", undefined, err);
152
167
  }
153
168
  }
169
+ /**
170
+ * Teardown the component and remove any resources it created.
171
+ * @param nodeLoggingComponentType The node logging component type.
172
+ * @returns True if the teardown process was successful.
173
+ */
174
+ async teardown(nodeLoggingComponentType) {
175
+ const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
176
+ await nodeLogging?.log({
177
+ level: "info",
178
+ source: IpfsBlobStorageConnector.CLASS_NAME,
179
+ message: "storeTearingDown"
180
+ });
181
+ try {
182
+ const fetchOptions = {
183
+ method: HttpMethod.POST,
184
+ headers: {
185
+ accept: MimeTypes.Json
186
+ }
187
+ };
188
+ this.addSecurity(fetchOptions);
189
+ const response = await fetch(`${this._config.apiUrl}/pin/ls`, fetchOptions);
190
+ if (response.ok) {
191
+ const result = (await response.json());
192
+ for (const cid of Object.keys(result.Keys)) {
193
+ const unpinFetchOptions = {
194
+ method: HttpMethod.POST,
195
+ headers: {
196
+ accept: MimeTypes.Json
197
+ }
198
+ };
199
+ this.addSecurity(unpinFetchOptions);
200
+ await fetch(`${this._config.apiUrl}/pin/rm?arg=${cid}`, unpinFetchOptions);
201
+ }
202
+ }
203
+ await nodeLogging?.log({
204
+ level: "info",
205
+ source: IpfsBlobStorageConnector.CLASS_NAME,
206
+ message: "storeTornDown"
207
+ });
208
+ return true;
209
+ }
210
+ catch (err) {
211
+ await nodeLogging?.log({
212
+ level: "error",
213
+ source: IpfsBlobStorageConnector.CLASS_NAME,
214
+ message: "teardownFailed",
215
+ error: BaseError.fromError(err)
216
+ });
217
+ return false;
218
+ }
219
+ }
220
+ /**
221
+ * Remove all blobs from the storage.
222
+ * @returns A promise that resolves when all pinned blobs have been unpinned and garbage collected.
223
+ */
224
+ async empty() {
225
+ try {
226
+ const fetchOptions = {
227
+ method: HttpMethod.POST,
228
+ headers: {
229
+ accept: MimeTypes.Json
230
+ }
231
+ };
232
+ this.addSecurity(fetchOptions);
233
+ const response = await fetch(`${this._config.apiUrl}/pin/ls`, fetchOptions);
234
+ if (response.ok) {
235
+ const result = (await response.json());
236
+ for (const cid of Object.keys(result.Keys)) {
237
+ const unpinFetchOptions = {
238
+ method: HttpMethod.POST,
239
+ headers: {
240
+ accept: MimeTypes.Json
241
+ }
242
+ };
243
+ this.addSecurity(unpinFetchOptions);
244
+ await fetch(`${this._config.apiUrl}/pin/rm?arg=${cid}`, unpinFetchOptions);
245
+ }
246
+ }
247
+ const gcFetchOptions = {
248
+ method: HttpMethod.POST,
249
+ headers: {
250
+ accept: MimeTypes.Json
251
+ }
252
+ };
253
+ this.addSecurity(gcFetchOptions);
254
+ const gcResponse = await fetch(`${this._config.apiUrl}/repo/gc`, gcFetchOptions);
255
+ await gcResponse.text();
256
+ }
257
+ catch (err) {
258
+ throw new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, "emptyFailed", undefined, err);
259
+ }
260
+ }
154
261
  /**
155
262
  * Remove the blob.
156
263
  * @param id The id of the blob to remove in urn format.
@@ -165,6 +272,14 @@ export class IpfsBlobStorageConnector {
165
272
  id
166
273
  });
167
274
  }
275
+ const partitionSegment = await this.buildPartitionSegment();
276
+ const storedPartitionSegment = Is.stringValue(urnParsed.namespaceSpecific(2))
277
+ ? urnParsed.namespaceSpecific(2)
278
+ : undefined;
279
+ if (partitionSegment !== storedPartitionSegment) {
280
+ return false;
281
+ }
282
+ const ipfsHash = urnParsed.namespaceSpecific(1).split(":")[0];
168
283
  try {
169
284
  const fetchOptions = {
170
285
  method: HttpMethod.POST,
@@ -173,20 +288,19 @@ export class IpfsBlobStorageConnector {
173
288
  }
174
289
  };
175
290
  this.addSecurity(fetchOptions);
176
- const response = await fetch(`${this._config.apiUrl}/pin/rm?arg=${urnParsed.namespaceSpecific(1)}`, fetchOptions);
291
+ const response = await fetch(`${this._config.apiUrl}/pin/rm?arg=${ipfsHash}`, fetchOptions);
177
292
  if (response.ok) {
178
293
  return true;
179
294
  }
180
- const error = await response.json();
181
- throw new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, "fetchFail", error);
295
+ return false;
182
296
  }
183
297
  catch (err) {
184
298
  throw new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, "removeBlobFailed", undefined, err);
185
299
  }
186
300
  }
187
301
  /**
188
- * Add the security to the request.
189
- * @param requestInit The request options.
302
+ * Add the security headers to the request if a bearer token is configured.
303
+ * @param requestInit The request options to augment with security headers.
190
304
  * @internal
191
305
  */
192
306
  addSecurity(requestInit) {
@@ -197,5 +311,18 @@ export class IpfsBlobStorageConnector {
197
311
  };
198
312
  }
199
313
  }
314
+ /**
315
+ * Build the partition segment from the current context.
316
+ * @returns The hex-encoded Blake2b hash of the partition key, or undefined if no partition applies.
317
+ * @internal
318
+ */
319
+ async buildPartitionSegment() {
320
+ const contextIds = await ContextIdStore.getContextIds();
321
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
322
+ if (Is.stringValue(partitionKey)) {
323
+ return Converter.bytesToHex(Blake2b.sum256(Converter.utf8ToBytes(partitionKey)));
324
+ }
325
+ return undefined;
326
+ }
200
327
  }
201
328
  //# sourceMappingURL=ipfsBlobStorageConnector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ipfsBlobStorageConnector.js","sourceRoot":"","sources":["../../src/ipfsBlobStorageConnector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IBlobStorageConnector } from \"@twin.org/blob-storage-models\";\nimport {\n\tGeneralError,\n\tGuards,\n\tHealthStatus,\n\tIs,\n\tStringHelper,\n\tUrn,\n\ttype IHealth\n} from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderHelper, HeaderTypes, HttpMethod, MimeTypes } from \"@twin.org/web\";\nimport type { IIpfsBlobStorageConnectorConfig } from \"./models/IIpfsBlobStorageConnectorConfig.js\";\nimport type { IIpfsBlobStorageConnectorConstructorOptions } from \"./models/IIpfsBlobStorageConnectorConstructorOptions.js\";\n\n/**\n * Class for performing blob storage operations on IPFS.\n * See https://docs.ipfs.tech/reference/kubo/rpc/ for more information.\n */\nexport class IpfsBlobStorageConnector implements IBlobStorageConnector {\n\t/**\n\t * The namespace for the items.\n\t */\n\tpublic static readonly NAMESPACE: string = \"ipfs\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<IpfsBlobStorageConnector>();\n\n\t/**\n\t * The configuration for the connector.\n\t * @internal\n\t */\n\tprivate readonly _config: IIpfsBlobStorageConnectorConfig;\n\n\t/**\n\t * The keys to use from the context ids to create partitions.\n\t * @internal\n\t */\n\tprivate readonly _partitionContextIds?: string[];\n\n\t/**\n\t * Create a new instance of IpfsBlobStorageConnector.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options: IIpfsBlobStorageConnectorConstructorOptions) {\n\t\tGuards.object(IpfsBlobStorageConnector.CLASS_NAME, nameof(options), options);\n\t\tGuards.object<IIpfsBlobStorageConnectorConfig>(\n\t\t\tIpfsBlobStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config),\n\t\t\toptions.config\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tIpfsBlobStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config.apiUrl),\n\t\t\toptions.config.apiUrl\n\t\t);\n\n\t\tthis._config = options.config;\n\t\tthis._partitionContextIds = options.partitionContextIds;\n\t\tthis._config.apiUrl = StringHelper.trimTrailingSlashes(this._config.apiUrl);\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn IpfsBlobStorageConnector.CLASS_NAME;\n\t}\n\n\t/**\n\t * Returns the health status of the component.\n\t * @returns The health status of the component.\n\t */\n\tpublic async health(): Promise<IHealth[]> {\n\t\ttry {\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\t\t\tthis.addSecurity(fetchOptions);\n\t\t\tconst response = await fetch(`${this._config.apiUrl}/version`, fetchOptions);\n\t\t\tif (response.ok) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\tsource: IpfsBlobStorageConnector.CLASS_NAME,\n\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\tdescription: \"healthDescription\"\n\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t}\n\t\t} catch {}\n\t\treturn [\n\t\t\t{\n\t\t\t\tsource: IpfsBlobStorageConnector.CLASS_NAME,\n\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\tmessage: \"healthCheckFailed\"\n\t\t\t}\n\t\t];\n\t}\n\n\t/**\n\t * Set the blob.\n\t * @param blob The data for the blob.\n\t * @returns The id of the stored blob in urn format.\n\t */\n\tpublic async set(blob: Uint8Array): Promise<string> {\n\t\tGuards.uint8Array(IpfsBlobStorageConnector.CLASS_NAME, nameof(blob), blob);\n\n\t\ttry {\n\t\t\tconst formBlob = new Blob([new Uint8Array(blob)], { type: MimeTypes.OctetStream });\n\t\t\tconst formData = new FormData();\n\t\t\tformData.append(\"file\", formBlob);\n\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\tbody: formData,\n\t\t\t\theaders: {\n\t\t\t\t\t[HeaderTypes.Accept]: MimeTypes.Json,\n\t\t\t\t\t[HeaderTypes.ContentDisposition]: \"form-data\"\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.addSecurity(fetchOptions);\n\n\t\t\tconst response = await fetch(`${this._config.apiUrl}/add?pin=true`, fetchOptions);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = (await response.json()) as {\n\t\t\t\t\tName: string;\n\t\t\t\t\tHash: string;\n\t\t\t\t\tSize: string;\n\t\t\t\t};\n\n\t\t\t\treturn `blob:${new Urn(IpfsBlobStorageConnector.NAMESPACE, result.Hash).toString()}`;\n\t\t\t}\n\n\t\t\tconst error = await response.json();\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"fetchFail\", error);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"setBlobFailed\", undefined, err);\n\t\t}\n\t}\n\n\t/**\n\t * Get the blob.\n\t * @param id The id of the blob to get in urn format.\n\t * @returns The data for the blob if it can be found or undefined.\n\t */\n\tpublic async get(id: string): Promise<Uint8Array | undefined> {\n\t\tUrn.guard(IpfsBlobStorageConnector.CLASS_NAME, nameof(id), id);\n\t\tconst urnParsed = Urn.fromValidString(id);\n\n\t\tif (urnParsed.namespaceMethod() !== IpfsBlobStorageConnector.NAMESPACE) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"namespaceMismatch\", {\n\t\t\t\tnamespace: IpfsBlobStorageConnector.NAMESPACE,\n\t\t\t\tid\n\t\t\t});\n\t\t}\n\n\t\ttry {\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.addSecurity(fetchOptions);\n\n\t\t\tconst responseStat = await fetch(\n\t\t\t\t`${this._config.apiUrl}/block/stat?arg=${urnParsed.namespaceSpecific(1)}&local=true`,\n\t\t\t\tfetchOptions\n\t\t\t);\n\n\t\t\tif (!responseStat.ok) {\n\t\t\t\tconst resp = await responseStat.json();\n\t\t\t\tif (\n\t\t\t\t\tIs.object<{ Message: string }>(resp) &&\n\t\t\t\t\tIs.stringValue(resp.Message) &&\n\t\t\t\t\tresp.Message.includes(\"not found\")\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${this._config.apiUrl}/cat?arg=${urnParsed.namespaceSpecific(1)}`,\n\t\t\t\tfetchOptions\n\t\t\t);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = await response.arrayBuffer();\n\t\t\t\treturn new Uint8Array(result);\n\t\t\t}\n\n\t\t\tconst error = await response.json();\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"fetchFail\", error);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"getBlobFailed\", undefined, err);\n\t\t}\n\t}\n\n\t/**\n\t * Remove the blob.\n\t * @param id The id of the blob to remove in urn format.\n\t * @returns True if the blob was found.\n\t */\n\tpublic async remove(id: string): Promise<boolean> {\n\t\tUrn.guard(IpfsBlobStorageConnector.CLASS_NAME, nameof(id), id);\n\t\tconst urnParsed = Urn.fromValidString(id);\n\n\t\tif (urnParsed.namespaceMethod() !== IpfsBlobStorageConnector.NAMESPACE) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"namespaceMismatch\", {\n\t\t\t\tnamespace: IpfsBlobStorageConnector.NAMESPACE,\n\t\t\t\tid\n\t\t\t});\n\t\t}\n\n\t\ttry {\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.addSecurity(fetchOptions);\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${this._config.apiUrl}/pin/rm?arg=${urnParsed.namespaceSpecific(1)}`,\n\t\t\t\tfetchOptions\n\t\t\t);\n\n\t\t\tif (response.ok) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst error = await response.json();\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"fetchFail\", error);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tIpfsBlobStorageConnector.CLASS_NAME,\n\t\t\t\t\"removeBlobFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Add the security to the request.\n\t * @param requestInit The request options.\n\t * @internal\n\t */\n\tprivate addSecurity(requestInit: RequestInit): void {\n\t\tif (Is.stringValue(this._config.bearerToken)) {\n\t\t\trequestInit.headers = {\n\t\t\t\t...requestInit.headers,\n\t\t\t\t[HeaderTypes.Authorization]: HeaderHelper.createBearer(this._config.bearerToken)\n\t\t\t};\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"ipfsBlobStorageConnector.js","sourceRoot":"","sources":["../../src/ipfsBlobStorageConnector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IBlobStorageConnector } from \"@twin.org/blob-storage-models\";\nimport { ContextIdHelper, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tHealthStatus,\n\tIs,\n\tStringHelper,\n\tUrn,\n\ttype IHealth\n} from \"@twin.org/core\";\nimport { Blake2b } from \"@twin.org/crypto\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderHelper, HeaderTypes, HttpMethod, MimeTypes } from \"@twin.org/web\";\nimport type { IIpfsBlobStorageConnectorConfig } from \"./models/IIpfsBlobStorageConnectorConfig.js\";\nimport type { IIpfsBlobStorageConnectorConstructorOptions } from \"./models/IIpfsBlobStorageConnectorConstructorOptions.js\";\n\n/**\n * Class for performing blob storage operations on IPFS.\n * See https://docs.ipfs.tech/reference/kubo/rpc/ for more information.\n */\nexport class IpfsBlobStorageConnector implements IBlobStorageConnector {\n\t/**\n\t * The namespace for the items.\n\t */\n\tpublic static readonly NAMESPACE: string = \"ipfs\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<IpfsBlobStorageConnector>();\n\n\t/**\n\t * The configuration for the connector.\n\t * @internal\n\t */\n\tprivate readonly _config: IIpfsBlobStorageConnectorConfig;\n\n\t/**\n\t * The keys to use from the context ids to create partitions.\n\t * @internal\n\t */\n\tprivate readonly _partitionContextIds?: string[];\n\n\t/**\n\t * Create a new instance of IpfsBlobStorageConnector.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options: IIpfsBlobStorageConnectorConstructorOptions) {\n\t\tGuards.object(IpfsBlobStorageConnector.CLASS_NAME, nameof(options), options);\n\t\tGuards.object<IIpfsBlobStorageConnectorConfig>(\n\t\t\tIpfsBlobStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config),\n\t\t\toptions.config\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tIpfsBlobStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config.apiUrl),\n\t\t\toptions.config.apiUrl\n\t\t);\n\n\t\tthis._config = options.config;\n\t\tthis._partitionContextIds = options.partitionContextIds;\n\t\tthis._config.apiUrl = StringHelper.trimTrailingSlashes(this._config.apiUrl);\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn IpfsBlobStorageConnector.CLASS_NAME;\n\t}\n\n\t/**\n\t * Returns the health status of the component.\n\t * @returns The health status of the component.\n\t */\n\tpublic async health(): Promise<IHealth[]> {\n\t\ttry {\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\t\t\tthis.addSecurity(fetchOptions);\n\t\t\tconst response = await fetch(`${this._config.apiUrl}/version`, fetchOptions);\n\t\t\tif (response.ok) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\tsource: IpfsBlobStorageConnector.CLASS_NAME,\n\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tapiUrl: this._config.apiUrl\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t}\n\t\t} catch {}\n\t\treturn [\n\t\t\t{\n\t\t\t\tsource: IpfsBlobStorageConnector.CLASS_NAME,\n\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\tmessage: \"healthCheckFailed\",\n\t\t\t\tdata: {\n\t\t\t\t\tapiUrl: this._config.apiUrl\n\t\t\t\t}\n\t\t\t}\n\t\t];\n\t}\n\n\t/**\n\t * Set the blob.\n\t * @param blob The data for the blob.\n\t * @returns The id of the stored blob in urn format.\n\t */\n\tpublic async set(blob: Uint8Array): Promise<string> {\n\t\tGuards.uint8Array(IpfsBlobStorageConnector.CLASS_NAME, nameof(blob), blob);\n\n\t\ttry {\n\t\t\tconst formBlob = new Blob([new Uint8Array(blob)], { type: MimeTypes.OctetStream });\n\t\t\tconst formData = new FormData();\n\t\t\tformData.append(\"file\", formBlob);\n\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\tbody: formData,\n\t\t\t\theaders: {\n\t\t\t\t\t[HeaderTypes.Accept]: MimeTypes.Json,\n\t\t\t\t\t[HeaderTypes.ContentDisposition]: \"form-data\"\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.addSecurity(fetchOptions);\n\n\t\t\tconst response = await fetch(`${this._config.apiUrl}/add?pin=true`, fetchOptions);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = (await response.json()) as {\n\t\t\t\t\tName: string;\n\t\t\t\t\tHash: string;\n\t\t\t\t\tSize: string;\n\t\t\t\t};\n\n\t\t\t\tconst partitionSegment = await this.buildPartitionSegment();\n\t\t\t\tconst urnParts = Is.stringValue(partitionSegment)\n\t\t\t\t\t? [result.Hash, partitionSegment]\n\t\t\t\t\t: [result.Hash];\n\n\t\t\t\treturn `blob:${new Urn(IpfsBlobStorageConnector.NAMESPACE, urnParts).toString()}`;\n\t\t\t}\n\n\t\t\tconst error = await response.json();\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"fetchFail\", error);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"setBlobFailed\", undefined, err);\n\t\t}\n\t}\n\n\t/**\n\t * Get the blob.\n\t * @param id The id of the blob to get in urn format.\n\t * @returns The data for the blob if it can be found or undefined.\n\t */\n\tpublic async get(id: string): Promise<Uint8Array | undefined> {\n\t\tUrn.guard(IpfsBlobStorageConnector.CLASS_NAME, nameof(id), id);\n\t\tconst urnParsed = Urn.fromValidString(id);\n\n\t\tif (urnParsed.namespaceMethod() !== IpfsBlobStorageConnector.NAMESPACE) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"namespaceMismatch\", {\n\t\t\t\tnamespace: IpfsBlobStorageConnector.NAMESPACE,\n\t\t\t\tid\n\t\t\t});\n\t\t}\n\n\t\tconst partitionSegment = await this.buildPartitionSegment();\n\t\tconst storedPartitionSegment = Is.stringValue(urnParsed.namespaceSpecific(2))\n\t\t\t? urnParsed.namespaceSpecific(2)\n\t\t\t: undefined;\n\n\t\tif (partitionSegment !== storedPartitionSegment) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst ipfsHash = urnParsed.namespaceSpecific(1).split(\":\")[0];\n\n\t\ttry {\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.addSecurity(fetchOptions);\n\n\t\t\tconst pinResponse = await fetch(\n\t\t\t\t`${this._config.apiUrl}/pin/ls?arg=${ipfsHash}`,\n\t\t\t\tfetchOptions\n\t\t\t);\n\n\t\t\tif (!pinResponse.ok) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst response = await fetch(`${this._config.apiUrl}/cat?arg=${ipfsHash}`, fetchOptions);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = await response.arrayBuffer();\n\t\t\t\treturn new Uint8Array(result);\n\t\t\t}\n\n\t\t\tconst error = await response.json();\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"fetchFail\", error);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"getBlobFailed\", undefined, err);\n\t\t}\n\t}\n\n\t/**\n\t * Teardown the component and remove any resources it created.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns True if the teardown process was successful.\n\t */\n\tpublic async teardown(nodeLoggingComponentType?: string): Promise<boolean> {\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\n\t\tawait nodeLogging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: IpfsBlobStorageConnector.CLASS_NAME,\n\t\t\tmessage: \"storeTearingDown\"\n\t\t});\n\n\t\ttry {\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\t\t\tthis.addSecurity(fetchOptions);\n\n\t\t\tconst response = await fetch(`${this._config.apiUrl}/pin/ls`, fetchOptions);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = (await response.json()) as { Keys: { [cid: string]: unknown } };\n\n\t\t\t\tfor (const cid of Object.keys(result.Keys)) {\n\t\t\t\t\tconst unpinFetchOptions: RequestInit = {\n\t\t\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\tthis.addSecurity(unpinFetchOptions);\n\t\t\t\t\tawait fetch(`${this._config.apiUrl}/pin/rm?arg=${cid}`, unpinFetchOptions);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: IpfsBlobStorageConnector.CLASS_NAME,\n\t\t\t\tmessage: \"storeTornDown\"\n\t\t\t});\n\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: IpfsBlobStorageConnector.CLASS_NAME,\n\t\t\t\tmessage: \"teardownFailed\",\n\t\t\t\terror: BaseError.fromError(err)\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Remove all blobs from the storage.\n\t * @returns A promise that resolves when all pinned blobs have been unpinned and garbage collected.\n\t */\n\tpublic async empty(): Promise<void> {\n\t\ttry {\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\t\t\tthis.addSecurity(fetchOptions);\n\n\t\t\tconst response = await fetch(`${this._config.apiUrl}/pin/ls`, fetchOptions);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = (await response.json()) as { Keys: { [cid: string]: unknown } };\n\n\t\t\t\tfor (const cid of Object.keys(result.Keys)) {\n\t\t\t\t\tconst unpinFetchOptions: RequestInit = {\n\t\t\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\tthis.addSecurity(unpinFetchOptions);\n\t\t\t\t\tawait fetch(`${this._config.apiUrl}/pin/rm?arg=${cid}`, unpinFetchOptions);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst gcFetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\t\t\tthis.addSecurity(gcFetchOptions);\n\t\t\tconst gcResponse = await fetch(`${this._config.apiUrl}/repo/gc`, gcFetchOptions);\n\t\t\tawait gcResponse.text();\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"emptyFailed\", undefined, err);\n\t\t}\n\t}\n\n\t/**\n\t * Remove the blob.\n\t * @param id The id of the blob to remove in urn format.\n\t * @returns True if the blob was found.\n\t */\n\tpublic async remove(id: string): Promise<boolean> {\n\t\tUrn.guard(IpfsBlobStorageConnector.CLASS_NAME, nameof(id), id);\n\t\tconst urnParsed = Urn.fromValidString(id);\n\n\t\tif (urnParsed.namespaceMethod() !== IpfsBlobStorageConnector.NAMESPACE) {\n\t\t\tthrow new GeneralError(IpfsBlobStorageConnector.CLASS_NAME, \"namespaceMismatch\", {\n\t\t\t\tnamespace: IpfsBlobStorageConnector.NAMESPACE,\n\t\t\t\tid\n\t\t\t});\n\t\t}\n\n\t\tconst partitionSegment = await this.buildPartitionSegment();\n\t\tconst storedPartitionSegment = Is.stringValue(urnParsed.namespaceSpecific(2))\n\t\t\t? urnParsed.namespaceSpecific(2)\n\t\t\t: undefined;\n\n\t\tif (partitionSegment !== storedPartitionSegment) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst ipfsHash = urnParsed.namespaceSpecific(1).split(\":\")[0];\n\n\t\ttry {\n\t\t\tconst fetchOptions: RequestInit = {\n\t\t\t\tmethod: HttpMethod.POST,\n\t\t\t\theaders: {\n\t\t\t\t\taccept: MimeTypes.Json\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.addSecurity(fetchOptions);\n\n\t\t\tconst response = await fetch(`${this._config.apiUrl}/pin/rm?arg=${ipfsHash}`, fetchOptions);\n\n\t\t\tif (response.ok) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tIpfsBlobStorageConnector.CLASS_NAME,\n\t\t\t\t\"removeBlobFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Add the security headers to the request if a bearer token is configured.\n\t * @param requestInit The request options to augment with security headers.\n\t * @internal\n\t */\n\tprivate addSecurity(requestInit: RequestInit): void {\n\t\tif (Is.stringValue(this._config.bearerToken)) {\n\t\t\trequestInit.headers = {\n\t\t\t\t...requestInit.headers,\n\t\t\t\t[HeaderTypes.Authorization]: HeaderHelper.createBearer(this._config.bearerToken)\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Build the partition segment from the current context.\n\t * @returns The hex-encoded Blake2b hash of the partition key, or undefined if no partition applies.\n\t * @internal\n\t */\n\tprivate async buildPartitionSegment(): Promise<string | undefined> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\treturn Converter.bytesToHex(Blake2b.sum256(Converter.utf8ToBytes(partitionKey)));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
@@ -41,6 +41,17 @@ export declare class IpfsBlobStorageConnector implements IBlobStorageConnector {
41
41
  * @returns The data for the blob if it can be found or undefined.
42
42
  */
43
43
  get(id: string): Promise<Uint8Array | undefined>;
44
+ /**
45
+ * Teardown the component and remove any resources it created.
46
+ * @param nodeLoggingComponentType The node logging component type.
47
+ * @returns True if the teardown process was successful.
48
+ */
49
+ teardown(nodeLoggingComponentType?: string): Promise<boolean>;
50
+ /**
51
+ * Remove all blobs from the storage.
52
+ * @returns A promise that resolves when all pinned blobs have been unpinned and garbage collected.
53
+ */
54
+ empty(): Promise<void>;
44
55
  /**
45
56
  * Remove the blob.
46
57
  * @param id The id of the blob to remove in urn format.
package/docs/changelog.md CHANGED
@@ -1,5 +1,146 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.0](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.9.0...blob-storage-connector-ipfs-v0.9.0) (2026-06-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * release to production ([eacfe75](https://github.com/iotaledger/twin-blob-storage/commit/eacfe754a0dcd9243d9e13d86422327d0a605164))
9
+ * release to production ([#70](https://github.com/iotaledger/twin-blob-storage/issues/70)) ([6a38fe5](https://github.com/iotaledger/twin-blob-storage/commit/6a38fe583076baf1fc53d1d891d294b75ebbefd1))
10
+
11
+ ## [0.9.0-next.1](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.9.0-next.0...blob-storage-connector-ipfs-v0.9.0-next.1) (2026-06-23)
12
+
13
+
14
+ ### Features
15
+
16
+ * add context id features ([#30](https://github.com/iotaledger/twin-blob-storage/issues/30)) ([fbf1c92](https://github.com/iotaledger/twin-blob-storage/commit/fbf1c9276424c841ef5ef3f4de8469ab3fba7e9c))
17
+ * add empty and teardown methods ([#49](https://github.com/iotaledger/twin-blob-storage/issues/49)) ([cec6248](https://github.com/iotaledger/twin-blob-storage/commit/cec624809ffd2f2baa4b7b8cbf72a7247b8703ed))
18
+ * add validate-locales ([f20fcec](https://github.com/iotaledger/twin-blob-storage/commit/f20fceced91e39a0c9edb770b2e43ce944c92f3c))
19
+ * additional information in health ([1ef83be](https://github.com/iotaledger/twin-blob-storage/commit/1ef83bef81148489b7950d5131a2af5121910e99))
20
+ * async getStore ([#59](https://github.com/iotaledger/twin-blob-storage/issues/59)) ([2de1ae5](https://github.com/iotaledger/twin-blob-storage/commit/2de1ae5c274b84a2320f75ac4628b81e9459c540))
21
+ * eslint migration to flat config ([e4239dd](https://github.com/iotaledger/twin-blob-storage/commit/e4239dd1c721955cff7f0357255d2bba15319972))
22
+ * health checks ([#44](https://github.com/iotaledger/twin-blob-storage/issues/44)) ([4a4041c](https://github.com/iotaledger/twin-blob-storage/commit/4a4041c19b68c40ed1aba6d1cdb4318ac4208b7d))
23
+ * skip ci for tests ([02b8095](https://github.com/iotaledger/twin-blob-storage/commit/02b8095dfabf8d9b3c6248a054a7657bfab1a374))
24
+ * typescript 6 update ([4eed54f](https://github.com/iotaledger/twin-blob-storage/commit/4eed54f5ce2dc697c06597269c97ad4cad108be5))
25
+ * update dependencies ([ca3c571](https://github.com/iotaledger/twin-blob-storage/commit/ca3c571573c771b8d25594f729651c8214e28263))
26
+ * update dependencies ([56f0094](https://github.com/iotaledger/twin-blob-storage/commit/56f0094b68d8bd22864cd899ac1b61d95540f719))
27
+ * update framework core ([ff339fe](https://github.com/iotaledger/twin-blob-storage/commit/ff339fe7e3f09ddff429907834bdf43617e9c05e))
28
+ * update health fetch ([6c86b51](https://github.com/iotaledger/twin-blob-storage/commit/6c86b51c2a034f680ccf290514dcb47e2ef91057))
29
+ * use new createBearer method ([f87c550](https://github.com/iotaledger/twin-blob-storage/commit/f87c5502fe3b5fee81257d7500f5d4500ea5ed28))
30
+ * use new createBearer method ([a965801](https://github.com/iotaledger/twin-blob-storage/commit/a96580160315c363fc0f06a1615cc92d4339a5e4))
31
+ * use shared store mechanism ([#12](https://github.com/iotaledger/twin-blob-storage/issues/12)) ([cae8110](https://github.com/iotaledger/twin-blob-storage/commit/cae8110681847a1ac4fcac968b8196694e49c320))
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * missing dependency ([868717b](https://github.com/iotaledger/twin-blob-storage/commit/868717b9d352af916c32df4d4f942d3bd8769c95))
37
+ * missing dependency ([9b7ec26](https://github.com/iotaledger/twin-blob-storage/commit/9b7ec2694468e75b4d3357b623e5031def82c920))
38
+
39
+
40
+ ### Dependencies
41
+
42
+ * The following workspace dependencies were updated
43
+ * dependencies
44
+ * @twin.org/blob-storage-models bumped from 0.9.0-next.0 to 0.9.0-next.1
45
+
46
+ ## [0.0.3-next.16](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.0.3-next.15...blob-storage-connector-ipfs-v0.0.3-next.16) (2026-06-18)
47
+
48
+
49
+ ### Miscellaneous Chores
50
+
51
+ * **blob-storage-connector-ipfs:** Synchronize repo versions
52
+
53
+
54
+ ### Dependencies
55
+
56
+ * The following workspace dependencies were updated
57
+ * dependencies
58
+ * @twin.org/blob-storage-models bumped from 0.0.3-next.15 to 0.0.3-next.16
59
+
60
+ ## [0.0.3-next.15](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.0.3-next.14...blob-storage-connector-ipfs-v0.0.3-next.15) (2026-06-15)
61
+
62
+
63
+ ### Features
64
+
65
+ * async getStore ([#59](https://github.com/iotaledger/twin-blob-storage/issues/59)) ([2de1ae5](https://github.com/iotaledger/twin-blob-storage/commit/2de1ae5c274b84a2320f75ac4628b81e9459c540))
66
+
67
+
68
+ ### Dependencies
69
+
70
+ * The following workspace dependencies were updated
71
+ * dependencies
72
+ * @twin.org/blob-storage-models bumped from 0.0.3-next.14 to 0.0.3-next.15
73
+
74
+ ## [0.0.3-next.14](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.0.3-next.13...blob-storage-connector-ipfs-v0.0.3-next.14) (2026-06-11)
75
+
76
+
77
+ ### Miscellaneous Chores
78
+
79
+ * **blob-storage-connector-ipfs:** Synchronize repo versions
80
+
81
+
82
+ ### Dependencies
83
+
84
+ * The following workspace dependencies were updated
85
+ * dependencies
86
+ * @twin.org/blob-storage-models bumped from 0.0.3-next.13 to 0.0.3-next.14
87
+
88
+ ## [0.0.3-next.13](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.0.3-next.12...blob-storage-connector-ipfs-v0.0.3-next.13) (2026-05-20)
89
+
90
+
91
+ ### Features
92
+
93
+ * update dependencies ([ca3c571](https://github.com/iotaledger/twin-blob-storage/commit/ca3c571573c771b8d25594f729651c8214e28263))
94
+
95
+
96
+ ### Dependencies
97
+
98
+ * The following workspace dependencies were updated
99
+ * dependencies
100
+ * @twin.org/blob-storage-models bumped from 0.0.3-next.12 to 0.0.3-next.13
101
+
102
+ ## [0.0.3-next.12](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.0.3-next.11...blob-storage-connector-ipfs-v0.0.3-next.12) (2026-05-11)
103
+
104
+
105
+ ### Features
106
+
107
+ * typescript 6 update ([4eed54f](https://github.com/iotaledger/twin-blob-storage/commit/4eed54f5ce2dc697c06597269c97ad4cad108be5))
108
+
109
+
110
+ ### Dependencies
111
+
112
+ * The following workspace dependencies were updated
113
+ * dependencies
114
+ * @twin.org/blob-storage-models bumped from 0.0.3-next.11 to 0.0.3-next.12
115
+
116
+ ## [0.0.3-next.11](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.0.3-next.10...blob-storage-connector-ipfs-v0.0.3-next.11) (2026-05-08)
117
+
118
+
119
+ ### Features
120
+
121
+ * add empty and teardown methods ([#49](https://github.com/iotaledger/twin-blob-storage/issues/49)) ([cec6248](https://github.com/iotaledger/twin-blob-storage/commit/cec624809ffd2f2baa4b7b8cbf72a7247b8703ed))
122
+
123
+
124
+ ### Dependencies
125
+
126
+ * The following workspace dependencies were updated
127
+ * dependencies
128
+ * @twin.org/blob-storage-models bumped from 0.0.3-next.10 to 0.0.3-next.11
129
+
130
+ ## [0.0.3-next.10](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.0.3-next.9...blob-storage-connector-ipfs-v0.0.3-next.10) (2026-05-07)
131
+
132
+
133
+ ### Features
134
+
135
+ * additional information in health ([1ef83be](https://github.com/iotaledger/twin-blob-storage/commit/1ef83bef81148489b7950d5131a2af5121910e99))
136
+
137
+
138
+ ### Dependencies
139
+
140
+ * The following workspace dependencies were updated
141
+ * dependencies
142
+ * @twin.org/blob-storage-models bumped from 0.0.3-next.9 to 0.0.3-next.10
143
+
3
144
  ## [0.0.3-next.9](https://github.com/iotaledger/twin-blob-storage/compare/blob-storage-connector-ipfs-v0.0.3-next.8...blob-storage-connector-ipfs-v0.0.3-next.9) (2026-05-07)
4
145
 
5
146
 
@@ -133,6 +133,50 @@ The data for the blob if it can be found or undefined.
133
133
 
134
134
  ***
135
135
 
136
+ ### teardown() {#teardown}
137
+
138
+ > **teardown**(`nodeLoggingComponentType?`): `Promise`\<`boolean`\>
139
+
140
+ Teardown the component and remove any resources it created.
141
+
142
+ #### Parameters
143
+
144
+ ##### nodeLoggingComponentType?
145
+
146
+ `string`
147
+
148
+ The node logging component type.
149
+
150
+ #### Returns
151
+
152
+ `Promise`\<`boolean`\>
153
+
154
+ True if the teardown process was successful.
155
+
156
+ #### Implementation of
157
+
158
+ `IBlobStorageConnector.teardown`
159
+
160
+ ***
161
+
162
+ ### empty() {#empty}
163
+
164
+ > **empty**(): `Promise`\<`void`\>
165
+
166
+ Remove all blobs from the storage.
167
+
168
+ #### Returns
169
+
170
+ `Promise`\<`void`\>
171
+
172
+ A promise that resolves when all pinned blobs have been unpinned and garbage collected.
173
+
174
+ #### Implementation of
175
+
176
+ `IBlobStorageConnector.empty`
177
+
178
+ ***
179
+
136
180
  ### remove() {#remove}
137
181
 
138
182
  > **remove**(`id`): `Promise`\<`boolean`\>
package/locales/en.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
+ "info": {
3
+ "ipfsBlobStorageConnector": {
4
+ "storeTearingDown": "Tearing down IPFS blob storage",
5
+ "storeTornDown": "IPFS blob storage torn down"
6
+ }
7
+ },
2
8
  "health": {
3
9
  "ipfsBlobStorageConnector": {
4
- "healthDescription": "IPFS blob storage connector is healthy",
5
- "healthCheckFailed": "Failed to connect to IPFS node"
10
+ "healthDescription": "IPFS blob storage connector is healthy, connected to \"{apiUrl}\"",
11
+ "healthCheckFailed": "Failed to connect to IPFS node at \"{apiUrl}\""
6
12
  }
7
13
  },
8
14
  "error": {
@@ -11,7 +17,9 @@
11
17
  "fetchFail": "Failure during IPFS request",
12
18
  "setBlobFailed": "Failed to store blob in IPFS",
13
19
  "getBlobFailed": "Failed to get blob from IPFS",
14
- "removeBlobFailed": "Failed to remove blob from IPFS"
20
+ "removeBlobFailed": "Failed to remove blob from IPFS",
21
+ "emptyFailed": "Failed to empty IPFS blob storage",
22
+ "teardownFailed": "Failed to teardown IPFS blob storage"
15
23
  }
16
24
  }
17
25
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@twin.org/blob-storage-connector-ipfs",
3
- "version": "0.0.3-next.9",
3
+ "version": "0.9.0",
4
4
  "description": "Stores and retrieves blobs through IPFS for content-addressed and distributed workflows.",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/iotaledger/blob-storage.git",
7
+ "url": "git+https://github.com/iotaledger/twin-blob-storage.git",
8
8
  "directory": "packages/blob-storage-connector-ipfs"
9
9
  },
10
10
  "author": "martyn.janes@iota.org",
@@ -14,11 +14,13 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/blob-storage-models": "0.0.3-next.9",
18
- "@twin.org/core": "next",
19
- "@twin.org/crypto": "next",
20
- "@twin.org/nameof": "next",
21
- "@twin.org/web": "next"
17
+ "@twin.org/blob-storage-models": "^0.9.0",
18
+ "@twin.org/context": "^0.9.0",
19
+ "@twin.org/core": "^0.9.0",
20
+ "@twin.org/crypto": "^0.9.0",
21
+ "@twin.org/logging-models": "^0.9.0",
22
+ "@twin.org/nameof": "^0.9.0",
23
+ "@twin.org/web": "^0.9.0"
22
24
  },
23
25
  "main": "./dist/es/index.js",
24
26
  "types": "./dist/types/index.d.ts",
@@ -52,7 +54,7 @@
52
54
  "integration"
53
55
  ],
54
56
  "bugs": {
55
- "url": "git+https://github.com/iotaledger/blob-storage/issues"
57
+ "url": "git+https://github.com/iotaledger/twin-blob-storage/issues"
56
58
  },
57
59
  "homepage": "https://twindev.org"
58
60
  }