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