@twin.org/blob-storage-service 0.0.2-next.5 → 0.0.3-next.2

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.
@@ -1,1072 +0,0 @@
1
- import { HttpParameterHelper } from '@twin.org/api-models';
2
- import { BlobStorageTypes, BlobStorageContexts, BlobStorageCompressionType, BlobStorageConnectorFactory } from '@twin.org/blob-storage-models';
3
- import { StringHelper, Guards, ComponentFactory, Coerce, Is, Converter, GeneralError, Validation, Compression, ObjectHelper, Urn, NotFoundError } from '@twin.org/core';
4
- import { SchemaOrgContexts, SchemaOrgTypes, SchemaOrgDataTypes } from '@twin.org/standards-schema-org';
5
- import { HttpStatusCode, HeaderTypes, MimeTypes, MimeTypeHelper } from '@twin.org/web';
6
- import { Sha256 } from '@twin.org/crypto';
7
- import { JsonLdHelper, JsonLdProcessor } from '@twin.org/data-json-ld';
8
- import { ComparisonOperator, LogicalOperator, SortDirection, EntitySchemaHelper, property, entity, EntitySchemaFactory } from '@twin.org/entity';
9
- import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
10
- import { VaultConnectorFactory, VaultEncryptionType } from '@twin.org/vault-models';
11
-
12
- // Copyright 2024 IOTA Stiftung.
13
- // SPDX-License-Identifier: Apache-2.0.
14
- /**
15
- * The source used when communicating about these routes.
16
- */
17
- const ROUTES_SOURCE = "blobStorageRoutes";
18
- /**
19
- * The tag to associate with the routes.
20
- */
21
- const tagsBlobStorage = [
22
- {
23
- name: "Blob Storage",
24
- description: "Endpoints which are modelled to access a blob storage contract."
25
- }
26
- ];
27
- /**
28
- * The REST routes for blob storage.
29
- * @param baseRouteName Prefix to prepend to the paths.
30
- * @param componentName The name of the component to use in the routes stored in the ComponentFactory.
31
- * @param options Additional options for the routes.
32
- * @param options.typeName Optional type name to use in the routes, defaults to Blob Storage.
33
- * @param options.tagName Optional name to use in OpenAPI spec for tag.
34
- * @returns The generated routes.
35
- */
36
- function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
37
- const typeName = options?.typeName ?? "Blob Storage";
38
- const lowerName = typeName.toLowerCase();
39
- const camelTypeName = StringHelper.camelCase(typeName);
40
- const blobStorageCreateRoute = {
41
- operationId: `${camelTypeName}Create`,
42
- summary: `Create an entry in ${lowerName}`,
43
- tag: options?.tagName ?? tagsBlobStorage[0].name,
44
- method: "POST",
45
- path: `${baseRouteName}/`,
46
- handler: async (httpRequestContext, request) => blobStorageCreate(httpRequestContext, componentName, request),
47
- requestType: {
48
- type: "IBlobStorageCreateRequest",
49
- examples: [
50
- {
51
- id: `${camelTypeName}CreateRequestExample`,
52
- request: {
53
- body: {
54
- blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==",
55
- metadata: {
56
- "@context": "https://schema.org",
57
- "@type": "DigitalDocument",
58
- name: "myfile.pdf"
59
- }
60
- }
61
- }
62
- }
63
- ]
64
- },
65
- responseType: [
66
- {
67
- type: "ICreatedResponse",
68
- examples: [
69
- {
70
- id: `${camelTypeName}CreateResponseExample`,
71
- response: {
72
- statusCode: HttpStatusCode.created,
73
- headers: {
74
- [HeaderTypes.Location]: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
75
- }
76
- }
77
- }
78
- ]
79
- }
80
- ]
81
- };
82
- const blobStorageGetRoute = {
83
- operationId: `${camelTypeName}Get`,
84
- summary: `Get the metadata for an item from ${lowerName}`,
85
- tag: options?.tagName ?? tagsBlobStorage[0].name,
86
- method: "GET",
87
- path: `${baseRouteName}/:id`,
88
- handler: async (httpRequestContext, request) => blobStorageGet(httpRequestContext, componentName, request),
89
- requestType: {
90
- type: "IBlobStorageGetRequest",
91
- examples: [
92
- {
93
- id: `${camelTypeName}GetRequestExample`,
94
- request: {
95
- pathParams: {
96
- id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
97
- },
98
- query: {
99
- includeContent: "true"
100
- }
101
- }
102
- }
103
- ]
104
- },
105
- responseType: [
106
- {
107
- type: "IBlobStorageGetResponse",
108
- examples: [
109
- {
110
- id: `${camelTypeName}GetResponseExample`,
111
- response: {
112
- body: {
113
- "@context": [
114
- BlobStorageContexts.ContextRoot,
115
- BlobStorageContexts.ContextRootCommon,
116
- SchemaOrgContexts.ContextRoot
117
- ],
118
- type: BlobStorageTypes.Entry,
119
- id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
120
- dateCreated: "2024-01-01T00:00:00Z",
121
- encodingFormat: MimeTypes.Pdf,
122
- blobSize: 42,
123
- blobHash: "sha256:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
124
- fileExtension: "pdf",
125
- metadata: {
126
- "@context": "https://schema.org",
127
- "@type": "DigitalDocument",
128
- name: "myfile.pdf"
129
- },
130
- blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
131
- }
132
- }
133
- }
134
- ]
135
- },
136
- {
137
- type: "IBlobStorageGetResponse",
138
- mimeType: MimeTypes.JsonLd,
139
- examples: [
140
- {
141
- id: `${camelTypeName}GetResponseJsonLdExample`,
142
- response: {
143
- body: {
144
- "@context": [
145
- BlobStorageContexts.ContextRoot,
146
- BlobStorageContexts.ContextRootCommon,
147
- SchemaOrgContexts.ContextRoot
148
- ],
149
- type: BlobStorageTypes.Entry,
150
- id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
151
- dateCreated: "2024-01-01T00:00:00Z",
152
- encodingFormat: MimeTypes.Pdf,
153
- blobSize: 42,
154
- blobHash: "sha256:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
155
- fileExtension: "pdf",
156
- metadata: {
157
- "@context": "https://schema.org",
158
- "@type": "DigitalDocument",
159
- name: "myfile.pdf"
160
- },
161
- blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
162
- }
163
- }
164
- }
165
- ]
166
- },
167
- {
168
- type: "INotFoundResponse"
169
- }
170
- ]
171
- };
172
- const blobStorageGetContentRoute = {
173
- operationId: `${camelTypeName}GetContent`,
174
- summary: `Get the content for an item in ${lowerName}`,
175
- tag: options?.tagName ?? tagsBlobStorage[0].name,
176
- method: "GET",
177
- path: `${baseRouteName}/:id/content`,
178
- handler: async (httpRequestContext, request) => blobStorageGetContent(httpRequestContext, componentName, request),
179
- requestType: {
180
- type: "IBlobStorageGetRequest",
181
- examples: [
182
- {
183
- id: `${camelTypeName}GetContentRequestExample`,
184
- request: {
185
- pathParams: {
186
- id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
187
- },
188
- query: {
189
- download: "true",
190
- filename: "my-file.pdf"
191
- }
192
- }
193
- }
194
- ]
195
- },
196
- responseType: [
197
- {
198
- type: "Uint8Array",
199
- mimeType: MimeTypes.OctetStream,
200
- examples: [
201
- {
202
- id: `${camelTypeName}GetContentResponseExample`,
203
- description: `The content of the blob, which will be a specific mime type if one can be detected from the content (or set as encodingFormat in the entry), or defaults to ${MimeTypes.OctetStream}.`,
204
- response: {
205
- body: new Uint8Array()
206
- }
207
- }
208
- ]
209
- },
210
- {
211
- type: "INotFoundResponse"
212
- }
213
- ]
214
- };
215
- const blobStorageUpdateRoute = {
216
- operationId: `${camelTypeName}Update`,
217
- summary: `Update the metadata for an item in ${lowerName}`,
218
- tag: options?.tagName ?? tagsBlobStorage[0].name,
219
- method: "PUT",
220
- path: `${baseRouteName}/:id`,
221
- handler: async (httpRequestContext, request) => blobStorageUpdate(httpRequestContext, componentName, request),
222
- requestType: {
223
- type: "IBlobStorageUpdateRequest",
224
- examples: [
225
- {
226
- id: `${camelTypeName}UpdateRequestExample`,
227
- request: {
228
- pathParams: {
229
- id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
230
- },
231
- body: {
232
- metadata: {
233
- "@context": "https://schema.org",
234
- "@type": "DigitalDocument",
235
- name: "myfile.pdf"
236
- }
237
- }
238
- }
239
- }
240
- ]
241
- },
242
- responseType: [
243
- {
244
- type: "INoContentResponse"
245
- }
246
- ]
247
- };
248
- const blobStorageRemoveRoute = {
249
- operationId: `${camelTypeName}Remove`,
250
- summary: `Remove an item from ${lowerName}`,
251
- tag: options?.tagName ?? tagsBlobStorage[0].name,
252
- method: "DELETE",
253
- path: `${baseRouteName}/:id`,
254
- handler: async (httpRequestContext, request) => blobStorageRemove(httpRequestContext, componentName, request),
255
- requestType: {
256
- type: "IBlobStorageRemoveRequest",
257
- examples: [
258
- {
259
- id: `${camelTypeName}RemoveRequestExample`,
260
- request: {
261
- pathParams: {
262
- id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
263
- }
264
- }
265
- }
266
- ]
267
- },
268
- responseType: [
269
- {
270
- type: "INoContentResponse"
271
- },
272
- {
273
- type: "INotFoundResponse"
274
- }
275
- ]
276
- };
277
- const blobStorageListRoute = {
278
- operationId: `${camelTypeName}Query`,
279
- summary: `Query the items from ${lowerName}`,
280
- tag: options?.tagName ?? tagsBlobStorage[0].name,
281
- method: "GET",
282
- path: `${baseRouteName}/`,
283
- handler: async (httpRequestContext, request) => blobStorageList(httpRequestContext, componentName, request),
284
- requestType: {
285
- type: "IBlobStorageListRequest",
286
- examples: [
287
- {
288
- id: `${camelTypeName}ListRequestExample`,
289
- request: {}
290
- }
291
- ]
292
- },
293
- responseType: [
294
- {
295
- type: "IBlobStorageListResponse",
296
- examples: [
297
- {
298
- id: `${camelTypeName}ListResponseExample`,
299
- response: {
300
- body: {
301
- "@context": [
302
- SchemaOrgContexts.ContextRoot,
303
- BlobStorageContexts.ContextRoot,
304
- BlobStorageContexts.ContextRootCommon
305
- ],
306
- type: SchemaOrgTypes.ItemList,
307
- [SchemaOrgTypes.ItemListElement]: [
308
- {
309
- "@context": [
310
- BlobStorageContexts.ContextRoot,
311
- BlobStorageContexts.ContextRootCommon,
312
- SchemaOrgContexts.ContextRoot
313
- ],
314
- type: BlobStorageTypes.Entry,
315
- id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
316
- dateCreated: "2024-01-01T00:00:00Z",
317
- encodingFormat: MimeTypes.Pdf,
318
- blobSize: 42,
319
- blobHash: "sha256:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
320
- fileExtension: "pdf",
321
- metadata: {
322
- "@context": "https://schema.org",
323
- "@type": "DigitalDocument",
324
- name: "myfile.pdf"
325
- },
326
- blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
327
- }
328
- ]
329
- }
330
- }
331
- }
332
- ]
333
- },
334
- {
335
- type: "IBlobStorageListResponse",
336
- mimeType: MimeTypes.JsonLd,
337
- examples: [
338
- {
339
- id: `${camelTypeName}ListResponseJsonLdExample`,
340
- response: {
341
- body: {
342
- "@context": [
343
- SchemaOrgContexts.ContextRoot,
344
- BlobStorageContexts.ContextRoot,
345
- BlobStorageContexts.ContextRootCommon
346
- ],
347
- type: SchemaOrgTypes.ItemList,
348
- [SchemaOrgTypes.ItemListElement]: [
349
- {
350
- "@context": [
351
- BlobStorageContexts.ContextRoot,
352
- BlobStorageContexts.ContextRootCommon,
353
- SchemaOrgContexts.ContextRoot
354
- ],
355
- type: BlobStorageTypes.Entry,
356
- id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
357
- dateCreated: "2024-01-01T00:00:00Z",
358
- encodingFormat: MimeTypes.Pdf,
359
- blobSize: 42,
360
- blobHash: "sha256:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
361
- fileExtension: "pdf",
362
- metadata: {
363
- "@context": "https://schema.org",
364
- "@type": "DigitalDocument",
365
- name: "myfile.pdf"
366
- },
367
- blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
368
- }
369
- ]
370
- }
371
- }
372
- }
373
- ]
374
- },
375
- {
376
- type: "INotFoundResponse"
377
- }
378
- ]
379
- };
380
- return [
381
- blobStorageCreateRoute,
382
- blobStorageGetRoute,
383
- blobStorageGetContentRoute,
384
- blobStorageUpdateRoute,
385
- blobStorageRemoveRoute,
386
- blobStorageListRoute
387
- ];
388
- }
389
- /**
390
- * Create a blob in storage.
391
- * @param httpRequestContext The request context for the API.
392
- * @param componentName The name of the component to use in the routes.
393
- * @param request The request.
394
- * @returns The response object with additional http response properties.
395
- */
396
- async function blobStorageCreate(httpRequestContext, componentName, request) {
397
- Guards.object(ROUTES_SOURCE, "request", request);
398
- Guards.object(ROUTES_SOURCE, "request.body", request.body);
399
- Guards.stringBase64(ROUTES_SOURCE, "request.body.blob", request.body.blob);
400
- const component = ComponentFactory.get(componentName);
401
- const id = await component.create(request.body.blob, request.body.encodingFormat, request.body.fileExtension, request.body.metadata, {
402
- disableEncryption: request.body.disableEncryption,
403
- overrideVaultKeyId: request.body.overrideVaultKeyId,
404
- namespace: request.body.namespace
405
- }, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
406
- return {
407
- statusCode: HttpStatusCode.created,
408
- headers: {
409
- location: id
410
- }
411
- };
412
- }
413
- /**
414
- * Get the blob from storage.
415
- * @param httpRequestContext The request context for the API.
416
- * @param componentName The name of the component to use in the routes.
417
- * @param request The request.
418
- * @returns The response object with additional http response properties.
419
- */
420
- async function blobStorageGet(httpRequestContext, componentName, request) {
421
- Guards.object(ROUTES_SOURCE, "request", request);
422
- Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
423
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
424
- const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
425
- const component = ComponentFactory.get(componentName);
426
- const result = await component.get(request.pathParams.id, {
427
- includeContent: Coerce.boolean(request.query?.includeContent),
428
- decompress: Coerce.boolean(request.query?.decompress),
429
- overrideVaultKeyId: request.query?.overrideVaultKeyId
430
- }, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
431
- return {
432
- headers: {
433
- [HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
434
- },
435
- body: result
436
- };
437
- }
438
- /**
439
- * Get the blob from storage.
440
- * @param httpRequestContext The request context for the API.
441
- * @param componentName The name of the component to use in the routes.
442
- * @param request The request.
443
- * @returns The response object with additional http response properties.
444
- */
445
- async function blobStorageGetContent(httpRequestContext, componentName, request) {
446
- Guards.object(ROUTES_SOURCE, "request", request);
447
- Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
448
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
449
- const component = ComponentFactory.get(componentName);
450
- const decompress = Coerce.boolean(request.query?.decompress);
451
- const download = Coerce.boolean(request.query?.download) ?? false;
452
- const result = await component.get(request.pathParams.id, {
453
- includeContent: true,
454
- decompress,
455
- overrideVaultKeyId: request.query?.overrideVaultKeyId
456
- }, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
457
- const encodingFormat = result?.encodingFormat ?? MimeTypes.OctetStream;
458
- let compressedEncodingFormat;
459
- let compressedExtension = "";
460
- // If the entry is compressed and we are not decompressing
461
- // we need to override the encoding format to the compressed type
462
- // and append an additional extension to the filename.
463
- if (result.compression && !decompress) {
464
- compressedEncodingFormat =
465
- result.compression === BlobStorageCompressionType.Gzip ? MimeTypes.Gzip : MimeTypes.Zlib;
466
- compressedExtension = `.${MimeTypeHelper.defaultExtension(compressedEncodingFormat)}`;
467
- }
468
- let filename = request.query?.filename;
469
- if (!Is.stringValue(filename)) {
470
- filename = `file.${result.fileExtension ?? MimeTypeHelper.defaultExtension(encodingFormat)}${compressedExtension}`;
471
- }
472
- return {
473
- body: Is.stringBase64(result.blob) ? Converter.base64ToBytes(result.blob) : new Uint8Array(),
474
- attachment: {
475
- mimeType: compressedEncodingFormat ?? encodingFormat,
476
- filename,
477
- inline: !download
478
- }
479
- };
480
- }
481
- /**
482
- * Update the blob storage metadata.
483
- * @param httpRequestContext The request context for the API.
484
- * @param componentName The name of the component to use in the routes.
485
- * @param request The request.
486
- * @returns The response object with additional http response properties.
487
- */
488
- async function blobStorageUpdate(httpRequestContext, componentName, request) {
489
- Guards.object(ROUTES_SOURCE, "request", request);
490
- Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
491
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
492
- const component = ComponentFactory.get(componentName);
493
- await component.update(request.pathParams.id, request.body.encodingFormat, request.body.fileExtension, request.body.metadata, httpRequestContext.userIdentity);
494
- return {
495
- statusCode: HttpStatusCode.noContent
496
- };
497
- }
498
- /**
499
- * Remove the blob from storage.
500
- * @param httpRequestContext The request context for the API.
501
- * @param componentName The name of the component to use in the routes.
502
- * @param request The request.
503
- * @returns The response object with additional http response properties.
504
- */
505
- async function blobStorageRemove(httpRequestContext, componentName, request) {
506
- Guards.object(ROUTES_SOURCE, "request", request);
507
- Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
508
- Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
509
- const component = ComponentFactory.get(componentName);
510
- await component.remove(request.pathParams.id, httpRequestContext.userIdentity);
511
- return {
512
- statusCode: HttpStatusCode.noContent
513
- };
514
- }
515
- /**
516
- * List the entries from blob storage.
517
- * @param httpRequestContext The request context for the API.
518
- * @param componentName The name of the component to use in the routes.
519
- * @param request The request.
520
- * @returns The response object with additional http response properties.
521
- */
522
- async function blobStorageList(httpRequestContext, componentName, request) {
523
- Guards.object(ROUTES_SOURCE, "request", request);
524
- const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
525
- const component = ComponentFactory.get(componentName);
526
- const result = await component.query(HttpParameterHelper.objectFromString(request.query?.conditions), request.query?.orderBy, request.query?.orderByDirection, request.query?.cursor, Coerce.number(request.query?.limit), httpRequestContext.userIdentity);
527
- return {
528
- headers: {
529
- [HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
530
- },
531
- body: result
532
- };
533
- }
534
-
535
- // Copyright 2024 IOTA Stiftung.
536
- // SPDX-License-Identifier: Apache-2.0.
537
- /**
538
- * Service for performing blob storage operations to a connector.
539
- */
540
- class BlobStorageService {
541
- /**
542
- * Runtime name for the class.
543
- */
544
- static CLASS_NAME = "BlobStorageService";
545
- /**
546
- * The namespace supported by the blob storage service.
547
- * @internal
548
- */
549
- static _NAMESPACE = "blob";
550
- /**
551
- * The namespace of the default storage connector to use.
552
- * Defaults to the first entry in the factory if not provided.
553
- * @internal
554
- */
555
- _defaultNamespace;
556
- /**
557
- * The storage connector for the metadata.
558
- * @internal
559
- */
560
- _entryEntityStorage;
561
- /**
562
- * The vault connector for the encryption, can be undefined if no encryption required.
563
- * @internal
564
- */
565
- _vaultConnector;
566
- /**
567
- * The id of the vault key to use for encryption if the service has a vault connector configured.
568
- * @internal
569
- */
570
- _vaultKeyId;
571
- /**
572
- * Include the user identity when performing storage operations, can be used to partition for users, defaults to false.
573
- * @internal
574
- */
575
- _partitionPerUser;
576
- /**
577
- * Create a new instance of BlobStorageService.
578
- * @param options The options for the service.
579
- */
580
- constructor(options) {
581
- const names = BlobStorageConnectorFactory.names();
582
- if (names.length === 0) {
583
- throw new GeneralError(BlobStorageService.CLASS_NAME, "noConnectors");
584
- }
585
- this._entryEntityStorage = EntityStorageConnectorFactory.get(options?.entryEntityStorageType ?? "blob-storage-entry");
586
- if (Is.stringValue(options?.vaultConnectorType)) {
587
- this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType);
588
- }
589
- this._defaultNamespace = options?.config?.defaultNamespace ?? names[0];
590
- this._vaultKeyId = options?.config?.vaultKeyId;
591
- this._partitionPerUser = options?.config?.partitionPerUser ?? false;
592
- SchemaOrgDataTypes.registerRedirects();
593
- }
594
- /**
595
- * Create the blob with some metadata.
596
- * @param blob The data for the blob in base64 format.
597
- * @param encodingFormat Mime type for the blob, will be detected if left undefined.
598
- * @param fileExtension Extension for the blob, will be detected if left undefined.
599
- * @param metadata Data for the custom metadata as JSON-LD.
600
- * @param options Optional options for the creation of the blob.
601
- * @param options.disableEncryption Disables encryption if enabled by default.
602
- * @param options.overrideVaultKeyId Use a different vault key id for encryption, if not provided the default vault key id will be used.
603
- * @param options.compress Optional compression type to use for the blob, defaults to no compression.*
604
- * @param options.namespace The namespace to use for storing, defaults to component configured namespace.
605
- * @param userIdentity The user identity to use with storage operations.
606
- * @param nodeIdentity The node identity to use with storage operations.
607
- * @returns The id of the stored blob in urn format.
608
- */
609
- async create(blob, encodingFormat, fileExtension, metadata, options, userIdentity, nodeIdentity) {
610
- Guards.stringBase64(BlobStorageService.CLASS_NAME, "blob", blob);
611
- if (this._partitionPerUser) {
612
- Guards.stringValue(BlobStorageService.CLASS_NAME, "userIdentity", userIdentity);
613
- }
614
- const disableEncryption = options?.disableEncryption ?? false;
615
- const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
616
- const encryptionEnabled = !disableEncryption && Is.stringValue(vaultKeyId);
617
- try {
618
- const connectorNamespace = options?.namespace ?? this._defaultNamespace;
619
- const blobStorageConnector = BlobStorageConnectorFactory.get(connectorNamespace);
620
- // Convert the base64 data into bytes
621
- let storeBlob = Converter.base64ToBytes(blob);
622
- const blobSize = storeBlob.length;
623
- // See if we can detect the mime type and default extension for the data.
624
- // If not already supplied by the caller. We have to perform this operation
625
- // on the unencrypted data.
626
- if (!Is.stringValue(encodingFormat)) {
627
- encodingFormat = await MimeTypeHelper.detect(storeBlob);
628
- }
629
- if (!Is.stringValue(fileExtension) && Is.stringValue(encodingFormat)) {
630
- fileExtension = await MimeTypeHelper.defaultExtension(encodingFormat);
631
- }
632
- if (Is.object(metadata)) {
633
- const validationFailures = [];
634
- JsonLdHelper.validate(metadata, validationFailures);
635
- Validation.asValidationError(BlobStorageService.CLASS_NAME, "metadata", validationFailures);
636
- }
637
- const blobHash = `sha256:${Converter.bytesToBase64(Sha256.sum256(storeBlob))}`;
638
- if (!Is.empty(options?.compress)) {
639
- storeBlob = await Compression.compress(storeBlob, options.compress);
640
- }
641
- // If we have a vault connector then encrypt the data.
642
- if (encryptionEnabled) {
643
- Guards.stringValue(BlobStorageService.CLASS_NAME, "nodeIdentity", nodeIdentity);
644
- if (Is.empty(this._vaultConnector)) {
645
- throw new GeneralError(BlobStorageService.CLASS_NAME, "vaultConnectorNotConfigured");
646
- }
647
- storeBlob = await this._vaultConnector.encrypt(`${nodeIdentity}/${vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, storeBlob);
648
- }
649
- // Set the blob in the storage connector, which may now be encrypted
650
- const blobId = await blobStorageConnector.set(storeBlob);
651
- // Now store the entry in entity storage
652
- const blobEntry = {
653
- id: blobId,
654
- dateCreated: new Date(Date.now()).toISOString(),
655
- blobSize,
656
- blobHash,
657
- encodingFormat,
658
- fileExtension,
659
- metadata,
660
- isEncrypted: encryptionEnabled,
661
- compression: options?.compress
662
- };
663
- const conditions = [];
664
- if (this._partitionPerUser) {
665
- ObjectHelper.propertySet(blobEntry, "userIdentity", userIdentity);
666
- conditions.push({ property: "userIdentity", value: userIdentity });
667
- }
668
- await this._entryEntityStorage.set(blobEntry, conditions);
669
- return blobId;
670
- }
671
- catch (error) {
672
- throw new GeneralError(BlobStorageService.CLASS_NAME, "createFailed", undefined, error);
673
- }
674
- }
675
- /**
676
- * Get the blob entry.
677
- * @param id The id of the blob to get in urn format.
678
- * @param options Optional options for the retrieval of the blob.
679
- * @param options.includeContent Include the content, or just get the metadata.
680
- * @param options.overrideVaultKeyId Use a different vault key id for decryption, if not provided the default vault key id will be used.
681
- * @param options.decompress If the content should be decompressed, if it was compressed when stored, defaults to true.
682
- * @param userIdentity The user identity to use with storage operations.
683
- * @param nodeIdentity The node identity to use with storage operations.
684
- * @returns The entry and data for the blob if it can be found.
685
- * @throws Not found error if the blob cannot be found.
686
- */
687
- async get(id, options, userIdentity, nodeIdentity) {
688
- Urn.guard(BlobStorageService.CLASS_NAME, "id", id);
689
- const includeContent = options?.includeContent ?? false;
690
- const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
691
- const conditions = [];
692
- if (this._partitionPerUser) {
693
- Guards.stringValue(BlobStorageService.CLASS_NAME, "userIdentity", userIdentity);
694
- conditions.push({
695
- property: "userIdentity",
696
- comparison: ComparisonOperator.Equals,
697
- value: userIdentity
698
- });
699
- }
700
- try {
701
- const blobEntry = await this.internalGet(id, userIdentity);
702
- let returnBlob;
703
- if (includeContent) {
704
- const blobStorageConnector = this.getConnector(id);
705
- returnBlob = await blobStorageConnector.get(id);
706
- if (Is.undefined(returnBlob)) {
707
- throw new NotFoundError(BlobStorageService.CLASS_NAME, "blobNotFound", id);
708
- }
709
- // If the data is encrypted then decrypt it.
710
- const decryptionEnabled = blobEntry.isEncrypted && Is.stringValue(vaultKeyId);
711
- if (decryptionEnabled) {
712
- Guards.stringValue(BlobStorageService.CLASS_NAME, "nodeIdentity", nodeIdentity);
713
- if (Is.empty(this._vaultConnector)) {
714
- throw new GeneralError(BlobStorageService.CLASS_NAME, "vaultConnectorNotConfigured");
715
- }
716
- returnBlob = await this._vaultConnector.decrypt(`${nodeIdentity}/${vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, returnBlob);
717
- }
718
- if (!Is.empty(blobEntry.compression) && (options?.decompress ?? true)) {
719
- returnBlob = await Compression.decompress(returnBlob, blobEntry.compression);
720
- }
721
- }
722
- const jsonLd = this.entryToJsonLd(blobEntry, returnBlob);
723
- return JsonLdProcessor.compact(jsonLd, jsonLd["@context"]);
724
- }
725
- catch (error) {
726
- throw new GeneralError(BlobStorageService.CLASS_NAME, "getFailed", undefined, error);
727
- }
728
- }
729
- /**
730
- * Update the blob with metadata.
731
- * @param id The id of the blob entry to update.
732
- * @param encodingFormat Mime type for the blob, will be detected if left undefined.
733
- * @param fileExtension Extension for the blob, will be detected if left undefined.
734
- * @param metadata Data for the custom metadata as JSON-LD.
735
- * @param userIdentity The user identity to use with storage operations.
736
- * @returns Nothing.
737
- * @throws Not found error if the blob cannot be found.
738
- */
739
- async update(id, encodingFormat, fileExtension, metadata, userIdentity) {
740
- Urn.guard(BlobStorageService.CLASS_NAME, "id", id);
741
- if (this._partitionPerUser) {
742
- Guards.stringValue(BlobStorageService.CLASS_NAME, "userIdentity", userIdentity);
743
- }
744
- try {
745
- const blobEntry = await this._entryEntityStorage.get(id);
746
- if (Is.undefined(blobEntry)) {
747
- throw new NotFoundError(BlobStorageService.CLASS_NAME, "blobNotFound", id);
748
- }
749
- if (Is.object(metadata)) {
750
- const validationFailures = [];
751
- await JsonLdHelper.validate(metadata, validationFailures);
752
- Validation.asValidationError(BlobStorageService.CLASS_NAME, "metadata", validationFailures);
753
- }
754
- // Now store the entry in entity storage
755
- const updatedBlobEntry = {
756
- id: blobEntry.id,
757
- dateCreated: blobEntry.dateCreated,
758
- dateModified: new Date(Date.now()).toISOString(),
759
- blobSize: blobEntry.blobSize,
760
- blobHash: blobEntry.blobHash,
761
- encodingFormat: encodingFormat ?? blobEntry.encodingFormat,
762
- fileExtension: fileExtension ?? blobEntry.fileExtension,
763
- metadata: metadata ?? blobEntry.metadata,
764
- isEncrypted: blobEntry.isEncrypted,
765
- compression: blobEntry.compression
766
- };
767
- const conditions = [];
768
- if (this._partitionPerUser) {
769
- ObjectHelper.propertySet(updatedBlobEntry, "userIdentity", userIdentity);
770
- conditions.push({ property: "userIdentity", value: userIdentity });
771
- }
772
- await this._entryEntityStorage.set(updatedBlobEntry, conditions);
773
- }
774
- catch (error) {
775
- throw new GeneralError(BlobStorageService.CLASS_NAME, "updateFailed", undefined, error);
776
- }
777
- }
778
- /**
779
- * Remove the blob.
780
- * @param id The id of the blob to remove in urn format.
781
- * @param userIdentity The user identity to use with storage operations.
782
- * @returns Nothing.
783
- */
784
- async remove(id, userIdentity) {
785
- Urn.guard(BlobStorageService.CLASS_NAME, "id", id);
786
- if (this._partitionPerUser) {
787
- Guards.stringValue(BlobStorageService.CLASS_NAME, "userIdentity", userIdentity);
788
- }
789
- try {
790
- const blobStorageConnector = this.getConnector(id);
791
- const conditions = [];
792
- if (this._partitionPerUser) {
793
- conditions.push({ property: "userIdentity", value: userIdentity });
794
- }
795
- await this._entryEntityStorage.remove(id, conditions);
796
- const removed = await blobStorageConnector.remove(id);
797
- if (!removed) {
798
- throw new NotFoundError(BlobStorageService.CLASS_NAME, "blobNotFound", id);
799
- }
800
- }
801
- catch (error) {
802
- throw new GeneralError(BlobStorageService.CLASS_NAME, "removeFailed", undefined, error);
803
- }
804
- }
805
- /**
806
- * Query all the blob storage entries which match the conditions.
807
- * @param conditions The conditions to match for the entries.
808
- * @param orderBy The order for the results, defaults to created.
809
- * @param orderByDirection The direction for the order, defaults to descending.
810
- * @param cursor The cursor to request the next page of entries.
811
- * @param limit The suggested number of entries to return in each chunk, in some scenarios can return a different amount.
812
- * @param userIdentity The user identity to use with storage operations.
813
- * @returns All the entries for the storage matching the conditions,
814
- * and a cursor which can be used to request more entities.
815
- */
816
- async query(conditions, orderBy, orderByDirection, cursor, limit, userIdentity) {
817
- const finalConditions = {
818
- conditions: [],
819
- logicalOperator: LogicalOperator.And
820
- };
821
- if (this._partitionPerUser) {
822
- Guards.stringValue(BlobStorageService.CLASS_NAME, "userIdentity", userIdentity);
823
- finalConditions.conditions.push({
824
- property: "userIdentity",
825
- comparison: ComparisonOperator.Equals,
826
- value: userIdentity
827
- });
828
- }
829
- if (!Is.empty(conditions)) {
830
- finalConditions.conditions.push(conditions);
831
- }
832
- const orderProperty = orderBy ?? "dateCreated";
833
- const orderDirection = orderByDirection ?? SortDirection.Descending;
834
- const result = await this._entryEntityStorage.query(finalConditions.conditions.length > 0 ? finalConditions : undefined, [
835
- {
836
- property: orderProperty,
837
- sortDirection: orderDirection
838
- }
839
- ], undefined, cursor, limit);
840
- let context = [
841
- SchemaOrgContexts.ContextRoot,
842
- BlobStorageContexts.ContextRoot,
843
- BlobStorageContexts.ContextRootCommon
844
- ];
845
- const entriesJsonLd = [];
846
- for (const entry of result.entities) {
847
- // The entries are never Partial as we don't allow custom property requests.
848
- entriesJsonLd.push(this.entryToJsonLd(entry));
849
- context = JsonLdProcessor.combineContexts(context, entry.metadata?.["@context"]);
850
- }
851
- const jsonLd = {
852
- "@context": context,
853
- type: SchemaOrgTypes.ItemList,
854
- [SchemaOrgTypes.ItemListElement]: entriesJsonLd,
855
- [SchemaOrgTypes.NextItem]: result.cursor
856
- };
857
- return JsonLdProcessor.compact(jsonLd, jsonLd["@context"]);
858
- }
859
- /**
860
- * Get the connector from the uri.
861
- * @param id The id of the blob storage item in urn format.
862
- * @returns The connector.
863
- * @internal
864
- */
865
- getConnector(id) {
866
- const idUri = Urn.fromValidString(id);
867
- if (idUri.namespaceIdentifier() !== BlobStorageService._NAMESPACE) {
868
- throw new GeneralError(BlobStorageService.CLASS_NAME, "namespaceMismatch", {
869
- namespace: BlobStorageService._NAMESPACE,
870
- id
871
- });
872
- }
873
- return BlobStorageConnectorFactory.get(idUri.namespaceMethod());
874
- }
875
- /**
876
- * Get an entity.
877
- * @param id The id of the entity to get, or the index value if secondaryIndex is set.
878
- * @param secondaryIndex Get the item using a secondary index.
879
- * @param userIdentity The user identity to use with storage operations.
880
- * @returns The object if it can be found or throws.
881
- * @internal
882
- */
883
- async internalGet(id, userIdentity) {
884
- const conditions = [];
885
- if (this._partitionPerUser) {
886
- Guards.stringValue(BlobStorageService.CLASS_NAME, "userIdentity", userIdentity);
887
- conditions.push({
888
- property: "userIdentity",
889
- comparison: ComparisonOperator.Equals,
890
- value: userIdentity
891
- });
892
- }
893
- let entity;
894
- if (conditions.length === 0) {
895
- entity = await this._entryEntityStorage.get(id);
896
- }
897
- else {
898
- const schema = this._entryEntityStorage.getSchema();
899
- const primaryKey = EntitySchemaHelper.getPrimaryKey(schema);
900
- conditions.unshift({
901
- property: primaryKey.property,
902
- comparison: ComparisonOperator.Equals,
903
- value: id
904
- });
905
- const results = await this._entryEntityStorage.query({
906
- conditions,
907
- logicalOperator: LogicalOperator.And
908
- }, undefined, undefined, undefined, 1);
909
- entity = results.entities[0];
910
- }
911
- if (Is.empty(entity)) {
912
- throw new NotFoundError(BlobStorageService.CLASS_NAME, "entityNotFound", id);
913
- }
914
- return ObjectHelper.omit(entity, ["userIdentity"]);
915
- }
916
- /**
917
- * Convert the entry to JSON-LD.
918
- * @param entry The entry to convert.
919
- * @param blob The optional blob to return.
920
- * @returns The JSON-LD representation of the entry.
921
- * @internal
922
- */
923
- entryToJsonLd(entry, blob) {
924
- const jsonLd = {
925
- "@context": JsonLdProcessor.combineContexts([
926
- BlobStorageContexts.ContextRoot,
927
- BlobStorageContexts.ContextRootCommon,
928
- SchemaOrgContexts.ContextRoot
929
- ], entry?.metadata?.["@context"]),
930
- id: entry.id,
931
- type: BlobStorageTypes.Entry,
932
- dateCreated: entry.dateCreated,
933
- dateModified: entry.dateModified,
934
- blobSize: entry.blobSize,
935
- blobHash: entry.blobHash,
936
- encodingFormat: entry?.encodingFormat,
937
- fileExtension: entry?.fileExtension,
938
- metadata: entry?.metadata,
939
- blob: Is.uint8Array(blob) ? Converter.bytesToBase64(blob) : undefined,
940
- isEncrypted: entry.isEncrypted,
941
- compression: entry.compression
942
- };
943
- return jsonLd;
944
- }
945
- }
946
-
947
- /**
948
- * Class representing entry for the blob storage.
949
- */
950
- let BlobStorageEntry = class BlobStorageEntry {
951
- /**
952
- * The id for the blob.
953
- */
954
- id;
955
- /**
956
- * The date/time when the entry was created.
957
- */
958
- dateCreated;
959
- /**
960
- * The date/time when the entry was modified.
961
- */
962
- dateModified;
963
- /**
964
- * The length of the data in the blob.
965
- */
966
- blobSize;
967
- /**
968
- * The hash of the data in the blob.
969
- */
970
- blobHash;
971
- /**
972
- * The mime type for the blob.
973
- */
974
- encodingFormat;
975
- /**
976
- * The extension.
977
- */
978
- fileExtension;
979
- /**
980
- * The metadata for the blob as JSON-LD.
981
- */
982
- metadata;
983
- /**
984
- * Is the entry encrypted.
985
- */
986
- isEncrypted;
987
- /**
988
- * Is the entry compressed.
989
- */
990
- compression;
991
- /**
992
- * The user identity that created the blob.
993
- */
994
- userIdentity;
995
- };
996
- __decorate([
997
- property({ type: "string", isPrimary: true }),
998
- __metadata("design:type", String)
999
- ], BlobStorageEntry.prototype, "id", void 0);
1000
- __decorate([
1001
- property({ type: "string", format: "date-time", sortDirection: SortDirection.Descending }),
1002
- __metadata("design:type", String)
1003
- ], BlobStorageEntry.prototype, "dateCreated", void 0);
1004
- __decorate([
1005
- property({
1006
- type: "string",
1007
- format: "date-time",
1008
- sortDirection: SortDirection.Descending,
1009
- optional: true
1010
- }),
1011
- __metadata("design:type", String)
1012
- ], BlobStorageEntry.prototype, "dateModified", void 0);
1013
- __decorate([
1014
- property({ type: "number" }),
1015
- __metadata("design:type", Number)
1016
- ], BlobStorageEntry.prototype, "blobSize", void 0);
1017
- __decorate([
1018
- property({ type: "string" }),
1019
- __metadata("design:type", String)
1020
- ], BlobStorageEntry.prototype, "blobHash", void 0);
1021
- __decorate([
1022
- property({ type: "string", optional: true }),
1023
- __metadata("design:type", String)
1024
- ], BlobStorageEntry.prototype, "encodingFormat", void 0);
1025
- __decorate([
1026
- property({ type: "string", optional: true }),
1027
- __metadata("design:type", String)
1028
- ], BlobStorageEntry.prototype, "fileExtension", void 0);
1029
- __decorate([
1030
- property({ type: "object", itemTypeRef: "IJsonLdNodeObject", optional: true }),
1031
- __metadata("design:type", Object)
1032
- ], BlobStorageEntry.prototype, "metadata", void 0);
1033
- __decorate([
1034
- property({ type: "boolean" }),
1035
- __metadata("design:type", Boolean)
1036
- ], BlobStorageEntry.prototype, "isEncrypted", void 0);
1037
- __decorate([
1038
- property({ type: "string", optional: true }),
1039
- __metadata("design:type", String)
1040
- ], BlobStorageEntry.prototype, "compression", void 0);
1041
- __decorate([
1042
- property({ type: "string", optional: true }),
1043
- __metadata("design:type", String)
1044
- ], BlobStorageEntry.prototype, "userIdentity", void 0);
1045
- BlobStorageEntry = __decorate([
1046
- entity()
1047
- ], BlobStorageEntry);
1048
-
1049
- /**
1050
- * These are dummy entry points for the blob storage service.
1051
- * In reality your application would create its own entry points based on the
1052
- * blob types it wants to store, using a custom defaultBaseRoute.
1053
- */
1054
- const restEntryPoints = [
1055
- {
1056
- name: "blob-storage",
1057
- defaultBaseRoute: "blob-storage",
1058
- tags: tagsBlobStorage,
1059
- generateRoutes: generateRestRoutesBlobStorage
1060
- }
1061
- ];
1062
-
1063
- // Copyright 2024 IOTA Stiftung.
1064
- // SPDX-License-Identifier: Apache-2.0.
1065
- /**
1066
- * Initialize the schema for the blob storage entities.
1067
- */
1068
- function initSchema() {
1069
- EntitySchemaFactory.register("BlobStorageEntry", () => EntitySchemaHelper.getSchema(BlobStorageEntry));
1070
- }
1071
-
1072
- export { BlobStorageEntry, BlobStorageService, blobStorageCreate, blobStorageGet, blobStorageGetContent, blobStorageList, blobStorageRemove, blobStorageUpdate, generateRestRoutesBlobStorage, initSchema, restEntryPoints, tagsBlobStorage };