@twin.org/blob-storage-service 0.0.1-next.9 → 0.0.1

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,11 +1,16 @@
1
- import { StringHelper, Guards, ComponentFactory, Is, Converter, GeneralError, Validation, ObjectHelper, Urn, NotFoundError } from '@twin.org/core';
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';
2
5
  import { HttpStatusCode, HeaderTypes, MimeTypes, MimeTypeHelper } from '@twin.org/web';
3
- import { BlobStorageConnectorFactory } from '@twin.org/blob-storage-models';
4
- import { JsonLdHelper } from '@twin.org/data-json-ld';
5
- import { ComparisonOperator, EntitySchemaHelper, LogicalOperator, property, entity, EntitySchemaFactory } from '@twin.org/entity';
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';
6
9
  import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
7
10
  import { VaultConnectorFactory, VaultEncryptionType } from '@twin.org/vault-models';
8
11
 
12
+ // Copyright 2024 IOTA Stiftung.
13
+ // SPDX-License-Identifier: Apache-2.0.
9
14
  /**
10
15
  * The source used when communicating about these routes.
11
16
  */
@@ -48,7 +53,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
48
53
  body: {
49
54
  blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==",
50
55
  metadata: {
51
- "@context": "http://schema.org/",
56
+ "@context": "https://schema.org",
52
57
  "@type": "DigitalDocument",
53
58
  name: "myfile.pdf"
54
59
  }
@@ -91,7 +96,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
91
96
  id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
92
97
  },
93
98
  query: {
94
- includeContent: true
99
+ includeContent: "true"
95
100
  }
96
101
  }
97
102
  }
@@ -105,8 +110,51 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
105
110
  id: `${camelTypeName}GetResponseExample`,
106
111
  response: {
107
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",
108
156
  metadata: {
109
- "@context": "http://schema.org/",
157
+ "@context": "https://schema.org",
110
158
  "@type": "DigitalDocument",
111
159
  name: "myfile.pdf"
112
160
  },
@@ -138,7 +186,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
138
186
  id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
139
187
  },
140
188
  query: {
141
- download: true,
189
+ download: "true",
142
190
  filename: "my-file.pdf"
143
191
  }
144
192
  }
@@ -152,7 +200,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
152
200
  examples: [
153
201
  {
154
202
  id: `${camelTypeName}GetContentResponseExample`,
155
- description: `The content of the blob, which will be a specific mime type if one can be detected from the content (or set as mimeType in the metadata), or defaults to ${MimeTypes.OctetStream}.`,
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}.`,
156
204
  response: {
157
205
  body: new Uint8Array()
158
206
  }
@@ -182,7 +230,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
182
230
  },
183
231
  body: {
184
232
  metadata: {
185
- "@context": "http://schema.org/",
233
+ "@context": "https://schema.org",
186
234
  "@type": "DigitalDocument",
187
235
  name: "myfile.pdf"
188
236
  }
@@ -226,12 +274,116 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
226
274
  }
227
275
  ]
228
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
+ };
229
380
  return [
230
381
  blobStorageCreateRoute,
231
382
  blobStorageGetRoute,
232
383
  blobStorageGetContentRoute,
233
384
  blobStorageUpdateRoute,
234
- blobStorageRemoveRoute
385
+ blobStorageRemoveRoute,
386
+ blobStorageListRoute
235
387
  ];
236
388
  }
237
389
  /**
@@ -246,7 +398,11 @@ async function blobStorageCreate(httpRequestContext, componentName, request) {
246
398
  Guards.object(ROUTES_SOURCE, "request.body", request.body);
247
399
  Guards.stringBase64(ROUTES_SOURCE, "request.body.blob", request.body.blob);
248
400
  const component = ComponentFactory.get(componentName);
249
- const id = await component.create(request.body.blob, request.body.mimeType, request.body.extension, request.body.metadata, request.body.namespace, httpRequestContext.nodeIdentity);
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);
250
406
  return {
251
407
  statusCode: HttpStatusCode.created,
252
408
  headers: {
@@ -265,9 +421,17 @@ async function blobStorageGet(httpRequestContext, componentName, request) {
265
421
  Guards.object(ROUTES_SOURCE, "request", request);
266
422
  Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
267
423
  Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
424
+ const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
268
425
  const component = ComponentFactory.get(componentName);
269
- const result = await component.get(request.pathParams.id, request.query?.includeContent ?? false, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
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);
270
431
  return {
432
+ headers: {
433
+ [HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
434
+ },
271
435
  body: result
272
436
  };
273
437
  }
@@ -283,18 +447,34 @@ async function blobStorageGetContent(httpRequestContext, componentName, request)
283
447
  Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
284
448
  Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
285
449
  const component = ComponentFactory.get(componentName);
286
- const result = await component.get(request.pathParams.id, true, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
287
- const mimeType = result?.mimeType ?? MimeTypes.OctetStream;
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
+ }
288
468
  let filename = request.query?.filename;
289
469
  if (!Is.stringValue(filename)) {
290
- filename = `file.${result.extension ?? MimeTypeHelper.defaultExtension(mimeType)}`;
470
+ filename = `file.${result.fileExtension ?? MimeTypeHelper.defaultExtension(encodingFormat)}${compressedExtension}`;
291
471
  }
292
472
  return {
293
473
  body: Is.stringBase64(result.blob) ? Converter.base64ToBytes(result.blob) : new Uint8Array(),
294
474
  attachment: {
295
- mimeType,
475
+ mimeType: compressedEncodingFormat ?? encodingFormat,
296
476
  filename,
297
- inline: !(request.query?.download ?? false)
477
+ inline: !download
298
478
  }
299
479
  };
300
480
  }
@@ -310,7 +490,7 @@ async function blobStorageUpdate(httpRequestContext, componentName, request) {
310
490
  Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
311
491
  Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
312
492
  const component = ComponentFactory.get(componentName);
313
- await component.update(request.pathParams.id, request.body.mimeType, request.body.extension, request.body.metadata, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
493
+ await component.update(request.pathParams.id, request.body.encodingFormat, request.body.fileExtension, request.body.metadata, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
314
494
  return {
315
495
  statusCode: HttpStatusCode.noContent
316
496
  };
@@ -332,6 +512,25 @@ async function blobStorageRemove(httpRequestContext, componentName, request) {
332
512
  statusCode: HttpStatusCode.noContent
333
513
  };
334
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?.pageSize), httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
527
+ return {
528
+ headers: {
529
+ [HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
530
+ },
531
+ body: result
532
+ };
533
+ }
335
534
 
336
535
  // Copyright 2024 IOTA Stiftung.
337
536
  // SPDX-License-Identifier: Apache-2.0.
@@ -357,7 +556,7 @@ class BlobStorageService {
357
556
  * The storage connector for the metadata.
358
557
  * @internal
359
558
  */
360
- _metadataEntityStorage;
559
+ _entryEntityStorage;
361
560
  /**
362
561
  * The vault connector for the encryption, can be undefined if no encryption required.
363
562
  * @internal
@@ -380,86 +579,104 @@ class BlobStorageService {
380
579
  _includeUserIdentity;
381
580
  /**
382
581
  * Create a new instance of BlobStorageService.
383
- * @param options The dependencies for the service.
384
- * @param options.metadataEntityStorageType The type of the storage connector for the metadata, defaults to "blob-metadata".
385
- * @param options.vaultConnectorType The type of the vault connector for encryption, if undefined no encryption will be performed.
386
- * @param options.config The configuration for the service.
582
+ * @param options The options for the service.
387
583
  */
388
584
  constructor(options) {
389
585
  const names = BlobStorageConnectorFactory.names();
390
586
  if (names.length === 0) {
391
587
  throw new GeneralError(this.CLASS_NAME, "noConnectors");
392
588
  }
393
- this._metadataEntityStorage = EntityStorageConnectorFactory.get(options?.metadataEntityStorageType ?? "blob-metadata");
589
+ this._entryEntityStorage = EntityStorageConnectorFactory.get(options?.entryEntityStorageType ?? "blob-storage-entry");
394
590
  if (Is.stringValue(options?.vaultConnectorType)) {
395
- this._vaultConnector = VaultConnectorFactory.getIfExists(options.vaultConnectorType);
591
+ this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType);
396
592
  }
397
593
  this._defaultNamespace = options?.config?.defaultNamespace ?? names[0];
398
- this._vaultKeyId = options?.config?.vaultKeyId ?? "blob-storage";
594
+ this._vaultKeyId = options?.config?.vaultKeyId;
399
595
  this._includeNodeIdentity = options?.config?.includeNodeIdentity ?? true;
400
596
  this._includeUserIdentity = options?.config?.includeUserIdentity ?? true;
597
+ SchemaOrgDataTypes.registerRedirects();
401
598
  }
402
599
  /**
403
600
  * Create the blob with some metadata.
404
601
  * @param blob The data for the blob in base64 format.
405
- * @param mimeType Mime type for the blob, will be detected if left undefined.
406
- * @param extension Extension for the blob, will be detected if left undefined.
602
+ * @param encodingFormat Mime type for the blob, will be detected if left undefined.
603
+ * @param fileExtension Extension for the blob, will be detected if left undefined.
407
604
  * @param metadata Data for the custom metadata as JSON-LD.
408
- * @param namespace The namespace to use for storing, defaults to component configured namespace.
605
+ * @param options Optional options for the creation of the blob.
606
+ * @param options.disableEncryption Disables encryption if enabled by default.
607
+ * @param options.overrideVaultKeyId Use a different vault key id for encryption, if not provided the default vault key id will be used.
608
+ * @param options.compress Optional compression type to use for the blob, defaults to no compression.*
609
+ * @param options.namespace The namespace to use for storing, defaults to component configured namespace.
409
610
  * @param userIdentity The user identity to use with storage operations.
410
611
  * @param nodeIdentity The node identity to use with storage operations.
411
612
  * @returns The id of the stored blob in urn format.
412
613
  */
413
- async create(blob, mimeType, extension, metadata, namespace, userIdentity, nodeIdentity) {
614
+ async create(blob, encodingFormat, fileExtension, metadata, options, userIdentity, nodeIdentity) {
414
615
  Guards.stringBase64(this.CLASS_NAME, "blob", blob);
415
616
  if (this._includeUserIdentity) {
416
617
  Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
417
618
  }
418
- if (this._includeNodeIdentity || Is.notEmpty(this._vaultConnector)) {
619
+ const disableEncryption = options?.disableEncryption ?? false;
620
+ const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
621
+ const encryptionEnabled = !disableEncryption && Is.stringValue(vaultKeyId);
622
+ if (this._includeNodeIdentity || encryptionEnabled) {
419
623
  Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
420
624
  }
421
625
  try {
422
- const connectorNamespace = namespace ?? this._defaultNamespace;
626
+ const connectorNamespace = options?.namespace ?? this._defaultNamespace;
423
627
  const blobStorageConnector = BlobStorageConnectorFactory.get(connectorNamespace);
424
628
  // Convert the base64 data into bytes
425
629
  let storeBlob = Converter.base64ToBytes(blob);
630
+ const blobSize = storeBlob.length;
426
631
  // See if we can detect the mime type and default extension for the data.
427
632
  // If not already supplied by the caller. We have to perform this operation
428
633
  // on the unencrypted data.
429
- if (!Is.stringValue(mimeType)) {
430
- mimeType = await MimeTypeHelper.detect(storeBlob);
634
+ if (!Is.stringValue(encodingFormat)) {
635
+ encodingFormat = await MimeTypeHelper.detect(storeBlob);
431
636
  }
432
- if (!Is.stringValue(extension) && Is.stringValue(mimeType)) {
433
- extension = await MimeTypeHelper.defaultExtension(mimeType);
637
+ if (!Is.stringValue(fileExtension) && Is.stringValue(encodingFormat)) {
638
+ fileExtension = await MimeTypeHelper.defaultExtension(encodingFormat);
434
639
  }
435
640
  if (Is.object(metadata)) {
436
641
  const validationFailures = [];
437
642
  JsonLdHelper.validate(metadata, validationFailures);
438
643
  Validation.asValidationError(this.CLASS_NAME, "metadata", validationFailures);
439
644
  }
645
+ const blobHash = `sha256:${Converter.bytesToBase64(Sha256.sum256(storeBlob))}`;
646
+ if (!Is.empty(options?.compress)) {
647
+ storeBlob = await Compression.compress(storeBlob, options.compress);
648
+ }
440
649
  // If we have a vault connector then encrypt the data.
441
- if (this._vaultConnector) {
442
- storeBlob = await this._vaultConnector.encrypt(`${nodeIdentity}/${this._vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, storeBlob);
650
+ if (encryptionEnabled) {
651
+ if (Is.empty(this._vaultConnector)) {
652
+ throw new GeneralError(this.CLASS_NAME, "vaultConnectorNotConfigured");
653
+ }
654
+ storeBlob = await this._vaultConnector.encrypt(`${nodeIdentity}/${vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, storeBlob);
443
655
  }
444
656
  // Set the blob in the storage connector, which may now be encrypted
445
657
  const blobId = await blobStorageConnector.set(storeBlob);
446
- // Now store the metadata in entity storage
447
- const blobMetadata = {
658
+ // Now store the entry in entity storage
659
+ const blobEntry = {
448
660
  id: blobId,
449
- mimeType,
450
- extension,
451
- metadata
661
+ dateCreated: new Date(Date.now()).toISOString(),
662
+ blobSize,
663
+ blobHash,
664
+ encodingFormat,
665
+ fileExtension,
666
+ metadata,
667
+ isEncrypted: encryptionEnabled,
668
+ compression: options?.compress
452
669
  };
453
670
  const conditions = [];
454
671
  if (this._includeUserIdentity) {
455
- ObjectHelper.propertySet(blobMetadata, "userIdentity", userIdentity);
672
+ ObjectHelper.propertySet(blobEntry, "userIdentity", userIdentity);
456
673
  conditions.push({ property: "userIdentity", value: userIdentity });
457
674
  }
458
675
  if (this._includeNodeIdentity) {
459
- ObjectHelper.propertySet(blobMetadata, "nodeIdentity", nodeIdentity);
676
+ ObjectHelper.propertySet(blobEntry, "nodeIdentity", nodeIdentity);
460
677
  conditions.push({ property: "nodeIdentity", value: nodeIdentity });
461
678
  }
462
- await this._metadataEntityStorage.set(blobMetadata, conditions);
679
+ await this._entryEntityStorage.set(blobEntry, conditions);
463
680
  return blobId;
464
681
  }
465
682
  catch (error) {
@@ -467,16 +684,21 @@ class BlobStorageService {
467
684
  }
468
685
  }
469
686
  /**
470
- * Get the blob and metadata.
687
+ * Get the blob entry.
471
688
  * @param id The id of the blob to get in urn format.
472
- * @param includeContent Include the content, or just get the metadata.
689
+ * @param options Optional options for the retrieval of the blob.
690
+ * @param options.includeContent Include the content, or just get the metadata.
691
+ * @param options.overrideVaultKeyId Use a different vault key id for decryption, if not provided the default vault key id will be used.
692
+ * @param options.decompress If the content should be decompressed, if it was compressed when stored, defaults to true.
473
693
  * @param userIdentity The user identity to use with storage operations.
474
694
  * @param nodeIdentity The node identity to use with storage operations.
475
- * @returns The metadata and data for the blob if it can be found.
695
+ * @returns The entry and data for the blob if it can be found.
476
696
  * @throws Not found error if the blob cannot be found.
477
697
  */
478
- async get(id, includeContent, userIdentity, nodeIdentity) {
698
+ async get(id, options, userIdentity, nodeIdentity) {
479
699
  Urn.guard(this.CLASS_NAME, "id", id);
700
+ const includeContent = options?.includeContent ?? false;
701
+ const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
480
702
  const conditions = [];
481
703
  if (this._includeUserIdentity) {
482
704
  Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
@@ -495,7 +717,7 @@ class BlobStorageService {
495
717
  });
496
718
  }
497
719
  try {
498
- const blobMetadata = await this.internalGet(id, userIdentity, nodeIdentity);
720
+ const blobEntry = await this.internalGet(id, userIdentity, nodeIdentity);
499
721
  let returnBlob;
500
722
  if (includeContent) {
501
723
  const blobStorageConnector = this.getConnector(id);
@@ -503,17 +725,20 @@ class BlobStorageService {
503
725
  if (Is.undefined(returnBlob)) {
504
726
  throw new NotFoundError(this.CLASS_NAME, "blobNotFound", id);
505
727
  }
506
- // If we have a vault connector then decrypt the data.
507
- if (this._vaultConnector) {
508
- returnBlob = await this._vaultConnector.decrypt(`${nodeIdentity}/${this._vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, returnBlob);
728
+ // If the data is encrypted then decrypt it.
729
+ const decryptionEnabled = blobEntry.isEncrypted && Is.stringValue(vaultKeyId);
730
+ if (decryptionEnabled) {
731
+ if (Is.empty(this._vaultConnector)) {
732
+ throw new GeneralError(this.CLASS_NAME, "vaultConnectorNotConfigured");
733
+ }
734
+ returnBlob = await this._vaultConnector.decrypt(`${nodeIdentity}/${vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, returnBlob);
735
+ }
736
+ if (!Is.empty(blobEntry.compression) && (options?.decompress ?? true)) {
737
+ returnBlob = await Compression.decompress(returnBlob, blobEntry.compression);
509
738
  }
510
739
  }
511
- return {
512
- blob: Is.uint8Array(returnBlob) ? Converter.bytesToBase64(returnBlob) : undefined,
513
- mimeType: blobMetadata?.mimeType,
514
- extension: blobMetadata?.extension,
515
- metadata: blobMetadata?.metadata
516
- };
740
+ const jsonLd = this.entryToJsonLd(blobEntry, returnBlob);
741
+ return JsonLdProcessor.compact(jsonLd, jsonLd["@context"]);
517
742
  }
518
743
  catch (error) {
519
744
  throw new GeneralError(this.CLASS_NAME, "getFailed", undefined, error);
@@ -521,16 +746,16 @@ class BlobStorageService {
521
746
  }
522
747
  /**
523
748
  * Update the blob with metadata.
524
- * @param id The id of the blob metadata to update.
525
- * @param mimeType Mime type for the blob, will be detected if left undefined.
526
- * @param extension Extension for the blob, will be detected if left undefined.
749
+ * @param id The id of the blob entry to update.
750
+ * @param encodingFormat Mime type for the blob, will be detected if left undefined.
751
+ * @param fileExtension Extension for the blob, will be detected if left undefined.
527
752
  * @param metadata Data for the custom metadata as JSON-LD.
528
753
  * @param userIdentity The user identity to use with storage operations.
529
754
  * @param nodeIdentity The node identity to use with storage operations.
530
755
  * @returns Nothing.
531
756
  * @throws Not found error if the blob cannot be found.
532
757
  */
533
- async update(id, mimeType, extension, metadata, userIdentity, nodeIdentity) {
758
+ async update(id, encodingFormat, fileExtension, metadata, userIdentity, nodeIdentity) {
534
759
  Urn.guard(this.CLASS_NAME, "id", id);
535
760
  if (this._includeUserIdentity) {
536
761
  Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
@@ -539,8 +764,8 @@ class BlobStorageService {
539
764
  Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
540
765
  }
541
766
  try {
542
- const blobMetadata = await this._metadataEntityStorage.get(id);
543
- if (Is.undefined(blobMetadata)) {
767
+ const blobEntry = await this._entryEntityStorage.get(id);
768
+ if (Is.undefined(blobEntry)) {
544
769
  throw new NotFoundError(this.CLASS_NAME, "blobNotFound", id);
545
770
  }
546
771
  if (Is.object(metadata)) {
@@ -548,23 +773,29 @@ class BlobStorageService {
548
773
  await JsonLdHelper.validate(metadata, validationFailures);
549
774
  Validation.asValidationError(this.CLASS_NAME, "metadata", validationFailures);
550
775
  }
551
- // Now store the metadata in entity storage
552
- const updatedBlobMetadata = {
553
- id: blobMetadata.id,
554
- mimeType: mimeType ?? blobMetadata.mimeType,
555
- extension: extension ?? blobMetadata.extension,
556
- metadata: metadata ?? blobMetadata.metadata
776
+ // Now store the entry in entity storage
777
+ const updatedBlobEntry = {
778
+ id: blobEntry.id,
779
+ dateCreated: blobEntry.dateCreated,
780
+ dateModified: new Date(Date.now()).toISOString(),
781
+ blobSize: blobEntry.blobSize,
782
+ blobHash: blobEntry.blobHash,
783
+ encodingFormat: encodingFormat ?? blobEntry.encodingFormat,
784
+ fileExtension: fileExtension ?? blobEntry.fileExtension,
785
+ metadata: metadata ?? blobEntry.metadata,
786
+ isEncrypted: blobEntry.isEncrypted,
787
+ compression: blobEntry.compression
557
788
  };
558
789
  const conditions = [];
559
790
  if (this._includeUserIdentity) {
560
- ObjectHelper.propertySet(updatedBlobMetadata, "userIdentity", userIdentity);
791
+ ObjectHelper.propertySet(updatedBlobEntry, "userIdentity", userIdentity);
561
792
  conditions.push({ property: "userIdentity", value: userIdentity });
562
793
  }
563
794
  if (this._includeNodeIdentity) {
564
- ObjectHelper.propertySet(updatedBlobMetadata, "nodeIdentity", nodeIdentity);
795
+ ObjectHelper.propertySet(updatedBlobEntry, "nodeIdentity", nodeIdentity);
565
796
  conditions.push({ property: "nodeIdentity", value: nodeIdentity });
566
797
  }
567
- await this._metadataEntityStorage.set(updatedBlobMetadata, conditions);
798
+ await this._entryEntityStorage.set(updatedBlobEntry, conditions);
568
799
  }
569
800
  catch (error) {
570
801
  throw new GeneralError(this.CLASS_NAME, "updateFailed", undefined, error);
@@ -594,7 +825,7 @@ class BlobStorageService {
594
825
  if (this._includeNodeIdentity) {
595
826
  conditions.push({ property: "nodeIdentity", value: nodeIdentity });
596
827
  }
597
- await this._metadataEntityStorage.remove(id, conditions);
828
+ await this._entryEntityStorage.remove(id, conditions);
598
829
  const removed = await blobStorageConnector.remove(id);
599
830
  if (!removed) {
600
831
  throw new NotFoundError(this.CLASS_NAME, "blobNotFound", id);
@@ -604,6 +835,69 @@ class BlobStorageService {
604
835
  throw new GeneralError(this.CLASS_NAME, "removeFailed", undefined, error);
605
836
  }
606
837
  }
838
+ /**
839
+ * Query all the blob storage entries which match the conditions.
840
+ * @param conditions The conditions to match for the entries.
841
+ * @param orderBy The order for the results, defaults to created.
842
+ * @param orderByDirection The direction for the order, defaults to descending.
843
+ * @param cursor The cursor to request the next page of entries.
844
+ * @param pageSize The suggested number of entries to return in each chunk, in some scenarios can return a different amount.
845
+ * @param userIdentity The user identity to use with storage operations.
846
+ * @param nodeIdentity The node identity to use with storage operations.
847
+ * @returns All the entries for the storage matching the conditions,
848
+ * and a cursor which can be used to request more entities.
849
+ */
850
+ async query(conditions, orderBy, orderByDirection, cursor, pageSize, userIdentity, nodeIdentity) {
851
+ const finalConditions = {
852
+ conditions: [],
853
+ logicalOperator: LogicalOperator.And
854
+ };
855
+ if (this._includeNodeIdentity) {
856
+ Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
857
+ finalConditions.conditions.push({
858
+ property: "nodeIdentity",
859
+ comparison: ComparisonOperator.Equals,
860
+ value: nodeIdentity
861
+ });
862
+ }
863
+ if (this._includeUserIdentity) {
864
+ Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
865
+ finalConditions.conditions.push({
866
+ property: "userIdentity",
867
+ comparison: ComparisonOperator.Equals,
868
+ value: userIdentity
869
+ });
870
+ }
871
+ if (!Is.empty(conditions)) {
872
+ finalConditions.conditions.push(conditions);
873
+ }
874
+ const orderProperty = orderBy ?? "dateCreated";
875
+ const orderDirection = orderByDirection ?? SortDirection.Descending;
876
+ const result = await this._entryEntityStorage.query(finalConditions.conditions.length > 0 ? finalConditions : undefined, [
877
+ {
878
+ property: orderProperty,
879
+ sortDirection: orderDirection
880
+ }
881
+ ], undefined, cursor, pageSize);
882
+ let context = [
883
+ SchemaOrgContexts.ContextRoot,
884
+ BlobStorageContexts.ContextRoot,
885
+ BlobStorageContexts.ContextRootCommon
886
+ ];
887
+ const entriesJsonLd = [];
888
+ for (const entry of result.entities) {
889
+ // The entries are never Partial as we don't allow custom property requests.
890
+ entriesJsonLd.push(this.entryToJsonLd(entry));
891
+ context = JsonLdProcessor.combineContexts(context, entry.metadata?.["@context"]);
892
+ }
893
+ const jsonLd = {
894
+ "@context": context,
895
+ type: SchemaOrgTypes.ItemList,
896
+ [SchemaOrgTypes.ItemListElement]: entriesJsonLd,
897
+ [SchemaOrgTypes.NextItem]: result.cursor
898
+ };
899
+ return JsonLdProcessor.compact(jsonLd, jsonLd["@context"]);
900
+ }
607
901
  /**
608
902
  * Get the connector from the uri.
609
903
  * @param id The id of the blob storage item in urn format.
@@ -649,17 +943,17 @@ class BlobStorageService {
649
943
  }
650
944
  let entity;
651
945
  if (conditions.length === 0) {
652
- entity = await this._metadataEntityStorage.get(id);
946
+ entity = await this._entryEntityStorage.get(id);
653
947
  }
654
948
  else {
655
- const schema = this._metadataEntityStorage.getSchema();
949
+ const schema = this._entryEntityStorage.getSchema();
656
950
  const primaryKey = EntitySchemaHelper.getPrimaryKey(schema);
657
951
  conditions.unshift({
658
952
  property: primaryKey.property,
659
953
  comparison: ComparisonOperator.Equals,
660
954
  value: id
661
955
  });
662
- const results = await this._metadataEntityStorage.query({
956
+ const results = await this._entryEntityStorage.query({
663
957
  conditions,
664
958
  logicalOperator: LogicalOperator.And
665
959
  }, undefined, undefined, undefined, 1);
@@ -668,32 +962,83 @@ class BlobStorageService {
668
962
  if (Is.empty(entity)) {
669
963
  throw new NotFoundError(this.CLASS_NAME, "entityNotFound", id);
670
964
  }
671
- ObjectHelper.propertyDelete(entity, "nodeIdentity");
672
- ObjectHelper.propertyDelete(entity, "userIdentity");
673
- return entity;
965
+ return ObjectHelper.omit(entity, ["nodeIdentity", "userIdentity"]);
966
+ }
967
+ /**
968
+ * Convert the entry to JSON-LD.
969
+ * @param entry The entry to convert.
970
+ * @param blob The optional blob to return.
971
+ * @returns The JSON-LD representation of the entry.
972
+ * @internal
973
+ */
974
+ entryToJsonLd(entry, blob) {
975
+ const jsonLd = {
976
+ "@context": JsonLdProcessor.combineContexts([
977
+ BlobStorageContexts.ContextRoot,
978
+ BlobStorageContexts.ContextRootCommon,
979
+ SchemaOrgContexts.ContextRoot
980
+ ], entry?.metadata?.["@context"]),
981
+ id: entry.id,
982
+ type: BlobStorageTypes.Entry,
983
+ dateCreated: entry.dateCreated,
984
+ dateModified: entry.dateModified,
985
+ blobSize: entry.blobSize,
986
+ blobHash: entry.blobHash,
987
+ encodingFormat: entry?.encodingFormat,
988
+ fileExtension: entry?.fileExtension,
989
+ metadata: entry?.metadata,
990
+ blob: Is.uint8Array(blob) ? Converter.bytesToBase64(blob) : undefined,
991
+ isEncrypted: entry.isEncrypted,
992
+ compression: entry.compression
993
+ };
994
+ return jsonLd;
674
995
  }
675
996
  }
676
997
 
677
998
  /**
678
- * Class representing metadata for the blob storage.
999
+ * Class representing entry for the blob storage.
679
1000
  */
680
- let BlobMetadata = class BlobMetadata {
1001
+ let BlobStorageEntry = class BlobStorageEntry {
681
1002
  /**
682
1003
  * The id for the blob.
683
1004
  */
684
1005
  id;
1006
+ /**
1007
+ * The date/time when the entry was created.
1008
+ */
1009
+ dateCreated;
1010
+ /**
1011
+ * The date/time when the entry was modified.
1012
+ */
1013
+ dateModified;
1014
+ /**
1015
+ * The length of the data in the blob.
1016
+ */
1017
+ blobSize;
1018
+ /**
1019
+ * The hash of the data in the blob.
1020
+ */
1021
+ blobHash;
685
1022
  /**
686
1023
  * The mime type for the blob.
687
1024
  */
688
- mimeType;
1025
+ encodingFormat;
689
1026
  /**
690
1027
  * The extension.
691
1028
  */
692
- extension;
1029
+ fileExtension;
693
1030
  /**
694
1031
  * The metadata for the blob as JSON-LD.
695
1032
  */
696
1033
  metadata;
1034
+ /**
1035
+ * Is the entry encrypted.
1036
+ */
1037
+ isEncrypted;
1038
+ /**
1039
+ * Is the entry compressed.
1040
+ */
1041
+ compression;
697
1042
  /**
698
1043
  * The user identity that created the blob.
699
1044
  */
@@ -706,30 +1051,59 @@ let BlobMetadata = class BlobMetadata {
706
1051
  __decorate([
707
1052
  property({ type: "string", isPrimary: true }),
708
1053
  __metadata("design:type", String)
709
- ], BlobMetadata.prototype, "id", void 0);
1054
+ ], BlobStorageEntry.prototype, "id", void 0);
710
1055
  __decorate([
711
- property({ type: "string" }),
1056
+ property({ type: "string", format: "date-time", sortDirection: SortDirection.Descending }),
712
1057
  __metadata("design:type", String)
713
- ], BlobMetadata.prototype, "mimeType", void 0);
1058
+ ], BlobStorageEntry.prototype, "dateCreated", void 0);
1059
+ __decorate([
1060
+ property({
1061
+ type: "string",
1062
+ format: "date-time",
1063
+ sortDirection: SortDirection.Descending,
1064
+ optional: true
1065
+ }),
1066
+ __metadata("design:type", String)
1067
+ ], BlobStorageEntry.prototype, "dateModified", void 0);
1068
+ __decorate([
1069
+ property({ type: "number" }),
1070
+ __metadata("design:type", Number)
1071
+ ], BlobStorageEntry.prototype, "blobSize", void 0);
714
1072
  __decorate([
715
1073
  property({ type: "string" }),
716
1074
  __metadata("design:type", String)
717
- ], BlobMetadata.prototype, "extension", void 0);
1075
+ ], BlobStorageEntry.prototype, "blobHash", void 0);
1076
+ __decorate([
1077
+ property({ type: "string", optional: true }),
1078
+ __metadata("design:type", String)
1079
+ ], BlobStorageEntry.prototype, "encodingFormat", void 0);
1080
+ __decorate([
1081
+ property({ type: "string", optional: true }),
1082
+ __metadata("design:type", String)
1083
+ ], BlobStorageEntry.prototype, "fileExtension", void 0);
718
1084
  __decorate([
719
- property({ type: "object", itemTypeRef: "IJsonLdNodeObject" }),
1085
+ property({ type: "object", itemTypeRef: "IJsonLdNodeObject", optional: true }),
720
1086
  __metadata("design:type", Object)
721
- ], BlobMetadata.prototype, "metadata", void 0);
1087
+ ], BlobStorageEntry.prototype, "metadata", void 0);
722
1088
  __decorate([
723
- property({ type: "string" }),
1089
+ property({ type: "boolean" }),
1090
+ __metadata("design:type", Boolean)
1091
+ ], BlobStorageEntry.prototype, "isEncrypted", void 0);
1092
+ __decorate([
1093
+ property({ type: "string", optional: true }),
724
1094
  __metadata("design:type", String)
725
- ], BlobMetadata.prototype, "userIdentity", void 0);
1095
+ ], BlobStorageEntry.prototype, "compression", void 0);
726
1096
  __decorate([
727
- property({ type: "string" }),
1097
+ property({ type: "string", optional: true }),
1098
+ __metadata("design:type", String)
1099
+ ], BlobStorageEntry.prototype, "userIdentity", void 0);
1100
+ __decorate([
1101
+ property({ type: "string", optional: true }),
728
1102
  __metadata("design:type", String)
729
- ], BlobMetadata.prototype, "nodeIdentity", void 0);
730
- BlobMetadata = __decorate([
1103
+ ], BlobStorageEntry.prototype, "nodeIdentity", void 0);
1104
+ BlobStorageEntry = __decorate([
731
1105
  entity()
732
- ], BlobMetadata);
1106
+ ], BlobStorageEntry);
733
1107
 
734
1108
  /**
735
1109
  * These are dummy entry points for the blob storage service.
@@ -751,7 +1125,7 @@ const restEntryPoints = [
751
1125
  * Initialize the schema for the blob storage entities.
752
1126
  */
753
1127
  function initSchema() {
754
- EntitySchemaFactory.register("BlobMetadata", () => EntitySchemaHelper.getSchema(BlobMetadata));
1128
+ EntitySchemaFactory.register("BlobStorageEntry", () => EntitySchemaHelper.getSchema(BlobStorageEntry));
755
1129
  }
756
1130
 
757
- export { BlobMetadata, BlobStorageService, blobStorageCreate, blobStorageGet, blobStorageGetContent, blobStorageRemove, blobStorageUpdate, generateRestRoutesBlobStorage, initSchema, restEntryPoints, tagsBlobStorage };
1131
+ export { BlobStorageEntry, BlobStorageService, blobStorageCreate, blobStorageGet, blobStorageGetContent, blobStorageList, blobStorageRemove, blobStorageUpdate, generateRestRoutesBlobStorage, initSchema, restEntryPoints, tagsBlobStorage };