@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 +1 -1
- package/dist/es/ipfsBlobStorageConnector.js +145 -18
- package/dist/es/ipfsBlobStorageConnector.js.map +1 -1
- package/dist/types/ipfsBlobStorageConnector.d.ts +11 -0
- package/docs/changelog.md +141 -0
- package/docs/reference/classes/IpfsBlobStorageConnector.md +44 -0
- package/locales/en.json +11 -3
- package/package.json +10 -8
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
134
|
-
if (!
|
|
135
|
-
|
|
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=${
|
|
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=${
|
|
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
|
-
|
|
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
|
+
"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
|
|
18
|
-
"@twin.org/
|
|
19
|
-
"@twin.org/
|
|
20
|
-
"@twin.org/
|
|
21
|
-
"@twin.org/
|
|
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
|
}
|