@eluvio/elv-client-js 4.2.30 → 4.2.31

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.
@@ -25,6 +25,34 @@ const {
25
25
  ValidatePresence,
26
26
  } = require("../Validation");
27
27
 
28
+ /* Validation for Fabric tags, groups and query fields */
29
+
30
+ const ValidateTags = (tags) => {
31
+ if(!Array.isArray(tags)) {
32
+ throw Error("Tags must be an array");
33
+ }
34
+ };
35
+
36
+ // Group references are content object IDs. A library ID is also accepted, since a library
37
+ // may act as a group (see the Fabric's "lib" unique group type).
38
+ const ValidateGroups = (groups) => {
39
+ if(!Array.isArray(groups)) {
40
+ throw Error("Groups must be an array");
41
+ }
42
+
43
+ groups.forEach(groupId => {
44
+ if(typeof groupId !== "string" || !(groupId.startsWith("iq__") || groupId.startsWith("ilib"))) {
45
+ throw Error(`Invalid group ID: ${groupId}`);
46
+ }
47
+ });
48
+ };
49
+
50
+ const ValidateQueryFields = (queryFields) => {
51
+ if(typeof queryFields !== "object" || queryFields === null || Array.isArray(queryFields)) {
52
+ throw Error("Query fields must be an object");
53
+ }
54
+ };
55
+
28
56
  exports.SetVisibility = async function({id, visibility}) {
29
57
  this.Log(`Setting visibility ${visibility} on ${id}`);
30
58
 
@@ -536,14 +564,18 @@ exports.RemoveLibraryContentType = async function({libraryId, typeId, typeName,
536
564
  * @namedParams
537
565
  * @param {string} libraryId - ID of the library
538
566
  * @param {string=} objectId - ID of the object (if contract already exists)
539
- * @param {Object=} options -
540
- * type: Version hash of the content type to associate with the object
541
- *
542
- * meta: Metadata to use for the new object
543
- *
544
- * noEncryptionConk: Set to true to prevent creation of an encryption conk for the object
545
- *
546
- * createKMSConk: Set to true to create a KMS conk for object (usually for sharing a playable object) (incompatible with noEncryptionConk: true)
567
+ * @param {Object=} options - Additional object creation options
568
+ * @param {string=} options.type - Version hash of the content type to associate with the object
569
+ * @param {Object=} options.meta - Metadata to use for the new object
570
+ * @param {string=} options.copy_from - Version hash of an existing object version to copy
571
+ * @param {string=} options.objectType - System-level object type, e.g. "cgroup" for a content group. May only be
572
+ * specified when creating a new object - the Fabric rejects an object type change on a new version of an existing object
573
+ * @param {Array<string>=} options.tags - Fabric tags to associate with the object
574
+ * @param {Array<string>=} options.groups - IDs of the content groups to add the object to. These categorize content and
575
+ * are distinct from the access groups that grant permissions
576
+ * @param {Object<string, string>=} options.queryFields - Unencrypted, indexed query fields to associate with the object
577
+ * @param {boolean=} options.noEncryptionConk - Set to true to prevent creation of an encryption conk for the object
578
+ * @param {boolean=} options.createKMSConk - Set to true to create a KMS conk for object (usually for sharing a playable object) (incompatible with noEncryptionConk: true)
547
579
  *
548
580
  * @returns {Promise<Object>} - Response containing the object ID and write token of the draft, as well as the url of the node that created the write token.
549
581
  */
@@ -555,6 +587,10 @@ exports.CreateContentObject = async function({libraryId, objectId, options={}})
555
587
 
556
588
  if(options.noEncryptionConk && options.createKMSConk) throw new Error("Incompatible options: noEncryptionConk and createKMSConk both set to true");
557
589
 
590
+ if(options.tags !== undefined) { ValidateTags(options.tags); }
591
+ if(options.groups !== undefined) { ValidateGroups(options.groups); }
592
+ if(options.queryFields !== undefined) { ValidateQueryFields(options.queryFields); }
593
+
558
594
  // Look up content type, if specified
559
595
  let typeId;
560
596
  if(options.type) {
@@ -613,7 +649,11 @@ exports.CreateContentObject = async function({libraryId, objectId, options={}})
613
649
  body: { // filter out options not recognized by server (noEncryptionConk, createKMSConk)
614
650
  type: options.type,
615
651
  meta: options.meta,
616
- copy_from: options.copy_from
652
+ copy_from: options.copy_from,
653
+ object_type: options.objectType,
654
+ tags: options.tags,
655
+ groups: options.groups,
656
+ query_fields: options.queryFields
617
657
  }
618
658
  });
619
659
 
@@ -1621,3 +1661,320 @@ exports.DeleteWriteToken = async function({writeToken}) {
1621
1661
 
1622
1662
  await this.HttpClient.ClearWriteToken({writeToken});
1623
1663
  };
1664
+
1665
+ /* Content group creation, modification, deletion */
1666
+
1667
+ /**
1668
+ * Create content tags
1669
+ *
1670
+ * @methodGroup Content Groups
1671
+ * @namedParams
1672
+ * @param {string} libraryId - ID of the library
1673
+ * @param {string} writeToken - Write token of the draft
1674
+ * @param {Array<string>} tags - List of tags
1675
+ *
1676
+ * @returns {Promise<Array<string>>} - The tags on the object after the update
1677
+ */
1678
+ exports.AddContentObjectTags = async function({libraryId, writeToken, tags}) {
1679
+ ValidateLibrary(libraryId);
1680
+ ValidateWriteToken(writeToken);
1681
+ ValidatePresence("tags", tags);
1682
+
1683
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "tags");
1684
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1685
+
1686
+ this.Log(`Adding tags: ${tags.join(", ")}`);
1687
+
1688
+ return this.HttpClient.RequestJsonBody({
1689
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1690
+ method: "POST",
1691
+ path: path,
1692
+ body: tags,
1693
+ allowFailover: false
1694
+ });
1695
+ };
1696
+
1697
+ /**
1698
+ * Update content tags, replacing any existing tags
1699
+ *
1700
+ * @methodGroup Content Groups
1701
+ * @namedParams
1702
+ * @param {string} libraryId - ID of the library
1703
+ * @param {string} writeToken - Write token of the draft
1704
+ * @param {Array<string>} tags - Tags the object should have. Must be given explicitly - pass an empty
1705
+ * array to remove all tags
1706
+ *
1707
+ * @returns {Promise<Array<string>>} - The tags on the object after the update
1708
+ */
1709
+ exports.SetContentObjectTags = async function({libraryId, writeToken, tags}) {
1710
+ ValidateLibrary(libraryId);
1711
+ ValidateWriteToken(writeToken);
1712
+ ValidateTags(tags);
1713
+
1714
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "tags");
1715
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1716
+
1717
+ this.Log(`Updating tags: ${Array.isArray(tags) ? tags.join(", ") : tags}`);
1718
+
1719
+ return this.HttpClient.RequestJsonBody({
1720
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1721
+ method: "PUT",
1722
+ path: path,
1723
+ body: tags,
1724
+ allowFailover: false
1725
+ });
1726
+ };
1727
+
1728
+ /**
1729
+ * Remove the given tags from a content object
1730
+ *
1731
+ * @methodGroup Content Groups
1732
+ * @namedParams
1733
+ * @param {string} libraryId - ID of the library
1734
+ * @param {string} writeToken - Write token of the draft
1735
+ * @param {Array<string>} tags - List of tags
1736
+ *
1737
+ * @returns {Promise<Array<string>>} - The tags on the object after the update
1738
+ */
1739
+ exports.RemoveContentObjectTags = async function({libraryId, writeToken, tags=[]}) {
1740
+ ValidateLibrary(libraryId);
1741
+ ValidateWriteToken(writeToken);
1742
+ ValidatePresence("tags", tags);
1743
+
1744
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "tags");
1745
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1746
+
1747
+ this.Log(`Removing tags: ${tags.join(", ")}`);
1748
+
1749
+ return this.HttpClient.RequestJsonBody({
1750
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1751
+ method: "DELETE",
1752
+ path: path,
1753
+ body: tags,
1754
+ allowFailover: false
1755
+ });
1756
+ };
1757
+
1758
+ /**
1759
+ * Create content query fields
1760
+ *
1761
+ * @methodGroup Content Groups
1762
+ * @namedParams
1763
+ * @param {string} libraryId - ID of the library
1764
+ * @param {string} writeToken - Write token of the draft
1765
+ * @param {Object} queryFields - Query fields defined by key/value pairs for each field
1766
+ *
1767
+ * @returns {Promise<Object<string, string>>} - The query fields on the object after the update
1768
+ */
1769
+ exports.AddContentObjectQueryFields = async function({libraryId, writeToken, queryFields}) {
1770
+ ValidateLibrary(libraryId);
1771
+ ValidateWriteToken(writeToken);
1772
+ ValidatePresence("queryFields", queryFields);
1773
+
1774
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "query_fields");
1775
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1776
+
1777
+ return this.HttpClient.RequestJsonBody({
1778
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1779
+ method: "POST",
1780
+ path: path,
1781
+ body: queryFields,
1782
+ allowFailover: false
1783
+ });
1784
+ };
1785
+
1786
+ /**
1787
+ * Replace existing content query fields
1788
+ *
1789
+ * @methodGroup Content Groups
1790
+ * @namedParams
1791
+ * @param {string} libraryId - ID of the library
1792
+ * @param {string} writeToken - Write token of the draft
1793
+ * @param {Object<string, string>} queryFields - Query fields as key/value pairs. Must be given explicitly -
1794
+ * pass an empty object to remove all fields
1795
+ *
1796
+ * @returns {Promise<Object<string, string>>} - The query fields on the object after the update
1797
+ */
1798
+ exports.SetContentObjectQueryFields = async function({libraryId, writeToken, queryFields}) {
1799
+ ValidateLibrary(libraryId);
1800
+ ValidateWriteToken(writeToken);
1801
+ ValidateQueryFields(queryFields);
1802
+
1803
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "query_fields");
1804
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1805
+
1806
+ return this.HttpClient.RequestJsonBody({
1807
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1808
+ method: "PUT",
1809
+ path: path,
1810
+ body: queryFields,
1811
+ allowFailover: false
1812
+ });
1813
+ };
1814
+
1815
+ /**
1816
+ * Remove the given query fields from a content object
1817
+ *
1818
+ * @methodGroup Content Groups
1819
+ * @namedParams
1820
+ * @param {string} libraryId - ID of the library
1821
+ * @param {string} writeToken - Write token of the draft
1822
+ * @param {Array<string>} fieldNames - Names of the query fields to remove
1823
+ *
1824
+ * @returns {Promise<Object<string, string>>} - The query fields on the object after the update
1825
+ */
1826
+ exports.RemoveContentObjectQueryFields = async function({libraryId, writeToken, fieldNames=[]}) {
1827
+ ValidateLibrary(libraryId);
1828
+ ValidateWriteToken(writeToken);
1829
+ ValidatePresence("fieldNames", fieldNames);
1830
+
1831
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "query_fields");
1832
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1833
+
1834
+ this.Log(`Removing query fields: ${fieldNames.join(", ")}`);
1835
+
1836
+ return this.HttpClient.RequestJsonBody({
1837
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1838
+ method: "DELETE",
1839
+ path: path,
1840
+ body: fieldNames,
1841
+ allowFailover: false
1842
+ });
1843
+ };
1844
+
1845
+ /**
1846
+ * Create a content group - a content object that other content objects can be associated with in order to
1847
+ * categorize them. Content groups are distinct from the access groups that grant permissions.
1848
+ *
1849
+ * The group is left as a draft; finalize it before adding content objects to it.
1850
+ *
1851
+ * @methodGroup Content Groups
1852
+ * @namedParams
1853
+ * @param {string} libraryId - ID of the library
1854
+ * @param {string=} name - Internal content management name of the group
1855
+ * @param {string=} displayTitle - Public-facing title of the group
1856
+ * @param {Array<string>=} tags - Fabric tags to associate with the group
1857
+ * @param {Object<string, string>=} queryFields - Unencrypted, indexed query fields to associate with the group,
1858
+ * as key/value pairs
1859
+ *
1860
+ * @returns {Promise<Object>} - Response containing the object ID and write token of the draft
1861
+ */
1862
+ exports.CreateContentGroup = async function({libraryId, name, displayTitle, tags, queryFields}) {
1863
+ ValidateLibrary(libraryId);
1864
+
1865
+ const {objectId, writeToken} = await this.CreateContentObject({
1866
+ libraryId,
1867
+ options: {
1868
+ objectType: "cgroup",
1869
+ meta: {
1870
+ public: {
1871
+ name,
1872
+ asset_metadata: {
1873
+ display_title: displayTitle
1874
+ }
1875
+ }
1876
+ },
1877
+ tags,
1878
+ queryFields
1879
+ }
1880
+ });
1881
+
1882
+ this.Log(`Created content group: ${objectId}`);
1883
+
1884
+ return {
1885
+ objectId,
1886
+ writeToken
1887
+ };
1888
+ };
1889
+
1890
+ /**
1891
+ * Add a content object to the given content groups. Groups the object already belongs to are retained.
1892
+ *
1893
+ * @methodGroup Content Groups
1894
+ * @namedParams
1895
+ * @param {string} libraryId - ID of the library
1896
+ * @param {string} writeToken - Write token of the draft
1897
+ * @param {Array<string>} groups - IDs of the content groups to add the object to. An empty array is a no-op
1898
+ *
1899
+ * @returns {Promise<Array<string>>} - The groups the object belongs to after the update. Order is not preserved
1900
+ */
1901
+ exports.AddContentObjectGroups = async function({libraryId, writeToken, groups=[]}) {
1902
+ ValidateLibrary(libraryId);
1903
+ ValidateWriteToken(writeToken);
1904
+ ValidateGroups(groups);
1905
+
1906
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "groups");
1907
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1908
+
1909
+ this.Log(`Adding groups: ${groups.join(", ")}`);
1910
+
1911
+ return this.HttpClient.RequestJsonBody({
1912
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1913
+ method: "POST",
1914
+ path: path,
1915
+ body: groups,
1916
+ allowFailover: false
1917
+ });
1918
+ };
1919
+
1920
+ /**
1921
+ * Set the content groups a content object belongs to, replacing any existing groups.
1922
+ *
1923
+ * @methodGroup Content Groups
1924
+ * @namedParams
1925
+ * @param {string} libraryId - ID of the library
1926
+ * @param {string} writeToken - Write token of the draft
1927
+ * @param {Array<string>} groups - IDs of the content groups the object should belong to. Must be given
1928
+ * explicitly - pass an empty array to remove the object from all groups
1929
+ *
1930
+ * @returns {Promise<Array<string>>} - The groups the object belongs to after the update. Order is not preserved
1931
+ */
1932
+ exports.SetContentObjectGroups = async function({libraryId, writeToken, groups}) {
1933
+ ValidateLibrary(libraryId);
1934
+ ValidateWriteToken(writeToken);
1935
+ ValidateGroups(groups);
1936
+
1937
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "groups");
1938
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1939
+
1940
+ this.Log(`Setting groups: ${groups.join(", ")}`);
1941
+
1942
+ return this.HttpClient.RequestJsonBody({
1943
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1944
+ method: "PUT",
1945
+ path: path,
1946
+ body: groups,
1947
+ allowFailover: false
1948
+ });
1949
+ };
1950
+
1951
+ /**
1952
+ * Remove a content object from the given content groups. Groups not listed are retained, and
1953
+ * removing a group the object does not belong to is a no-op.
1954
+ *
1955
+ * @methodGroup Content Groups
1956
+ * @namedParams
1957
+ * @param {string} libraryId - ID of the library
1958
+ * @param {string} writeToken - Write token of the draft
1959
+ * @param {Array<string>} groups - IDs of the content groups to remove the object from. An empty array is a no-op
1960
+ *
1961
+ * @returns {Promise<Array<string>>} - The groups the object belongs to after the update. Order is not preserved
1962
+ */
1963
+ exports.RemoveContentObjectGroups = async function({libraryId, writeToken, groups=[]}) {
1964
+ ValidateLibrary(libraryId);
1965
+ ValidateWriteToken(writeToken);
1966
+ ValidateGroups(groups);
1967
+
1968
+ const path = UrlJoin("qlibs", libraryId, "q", writeToken, "groups");
1969
+ const objectId = this.utils.DecodeWriteToken(writeToken).objectId;
1970
+
1971
+ this.Log(`Removing groups: ${groups.join(", ")}`);
1972
+
1973
+ return this.HttpClient.RequestJsonBody({
1974
+ headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
1975
+ method: "DELETE",
1976
+ path: path,
1977
+ body: groups,
1978
+ allowFailover: false
1979
+ });
1980
+ };
@@ -4215,6 +4215,43 @@ exports.OutputsReset = async function({libraryId, objectId, outputId}) {
4215
4215
  }
4216
4216
  };
4217
4217
 
4218
+ /**
4219
+ * Manually switch a live output's active input hop (e.g. primary <-> failover).
4220
+ *
4221
+ * @methodGroup Live Stream
4222
+ * @namedParams
4223
+ * @param {string=} libraryId - Library ID of the output settings object. If not provided, it will be retrieved automatically.
4224
+ * @param {string} objectId - Object ID of the output settings object
4225
+ * @param {string} outputId - ID of the output to switch
4226
+ * @param {number=} hop=0 - Target hop index (0 = primary input, 1 = first failover input)
4227
+ *
4228
+ * @returns {Promise<Object>} - Response from the hop call
4229
+ */
4230
+ exports.OutputsHop = async function({libraryId, objectId, outputId, hop=0}) {
4231
+ ValidateObject(objectId);
4232
+ ValidatePresence("outputId", outputId);
4233
+
4234
+ if(!libraryId) {
4235
+ libraryId = await this.ContentObjectLibraryId({objectId});
4236
+ }
4237
+
4238
+ // Route to a live egress node, then to the specific output's node
4239
+ const {restore} = await RouteToLiveEgress({client: this});
4240
+ await RouteToOutputNode({client: this, libraryId, objectId, outputId});
4241
+
4242
+ try {
4243
+ return await this.CallBitcodeMethod({
4244
+ libraryId,
4245
+ objectId,
4246
+ method: UrlJoin("live", "outputs", outputId, "ctrl", "hop"),
4247
+ queryParams: {hop},
4248
+ constant: false
4249
+ });
4250
+ } finally {
4251
+ restore();
4252
+ }
4253
+ };
4254
+
4218
4255
  /**
4219
4256
  * Delete a live output.
4220
4257
  *
@@ -11,7 +11,7 @@ const Test = async () => {
11
11
 
12
12
  try {
13
13
  const client = await ElvClient.FromNetworkName({
14
- networkName: "demo"
14
+ networkName: "main"
15
15
  });
16
16
 
17
17
  const wallet = client.GenerateWallet();
@@ -21,7 +21,19 @@ const Test = async () => {
21
21
 
22
22
  client.SetSigner({signer});
23
23
 
24
- console.log(await client.ContentLibraries());
24
+ const configUrl = new URL(await client.ConfigUrl());
25
+ configUrl.pathname = "/config";
26
+ // if(geo) { configUrl.searchParams.set("elvgeo", geo); } // optional region scoping
27
+
28
+ const fabricInfo = await (await fetch(configUrl.toString())).json();
29
+ // fabricInfo.network.services.* e.g. live_egress, ethereum, authority, etc.
30
+ const liveEgressUrls = fabricInfo?.network?.services?.live_egress;
31
+ console.log('fabricINfo', fabricInfo.network?.services?.tenant_query)
32
+
33
+ // tenant_query nodes are now wired into the client and used automatically by TenantContent:
34
+ console.log('client tenantQueryURIs', client.Nodes().tenantQueryURIs)
35
+ // const result = await client.TenantContent({limit: 5});
36
+ // console.log(JSON.stringify(result, null, 2));
25
37
  } catch(error) {
26
38
  console.error(error);
27
39
  console.error(JSON.stringify(error, null, 2));
@@ -0,0 +1,99 @@
1
+ // List all info for an object: details, profile, tags, content groups and query fields
2
+
3
+ const R = require("ramda");
4
+
5
+ const {NewOpt} = require("./lib/options");
6
+
7
+ const Utility = require("./lib/Utility");
8
+
9
+ const Client = require("./lib/concerns/Client");
10
+ const ExistObjOrVer = require("./lib/concerns/ExistObjOrVer");
11
+ const ArgOutfile = require("./lib/concerns/ArgOutfile");
12
+
13
+ // Sections that come from the Fabric's tags/groups/query_fields endpoints. A node that does not
14
+ // serve them should not prevent the rest of the info from being reported.
15
+ const FABRIC_PROPERTIES = [
16
+ {key: "tags", method: "ContentObjectTags", empty: []},
17
+ {key: "groups", method: "ContentObjectGroups", empty: []},
18
+ {key: "query_fields", method: "ContentObjectQueryFields", empty: {}}
19
+ ];
20
+
21
+ class ObjectInfo extends Utility {
22
+ blueprint() {
23
+ return {
24
+ concerns: [Client, ExistObjOrVer, ArgOutfile],
25
+ options: [
26
+ NewOpt("details", {
27
+ descTemplate: "Include object details (id, hash, type, size stats). Default: true",
28
+ default: true,
29
+ type: "boolean"
30
+ }),
31
+ NewOpt("profile", {
32
+ descTemplate: "Include the content profile (owner, visibility, status code, KMS, tenant). Default: true",
33
+ default: true,
34
+ type: "boolean"
35
+ }),
36
+ NewOpt("properties", {
37
+ descTemplate: "Include Fabric tags, content groups and query fields. Default: true",
38
+ default: true,
39
+ type: "boolean"
40
+ })
41
+ ]
42
+ };
43
+ }
44
+
45
+ async body() {
46
+ const logger = this.logger;
47
+ const client = await this.concerns.Client.get();
48
+
49
+ const {libraryId, objectId, versionHash} = await this.concerns.ExistObjOrVer.argsProc();
50
+
51
+ const info = {
52
+ object_id: objectId,
53
+ version_hash: versionHash,
54
+ library_id: libraryId
55
+ };
56
+
57
+ if(this.args.details || this.args.profile) {
58
+ // ContentObject requests details and profile together
59
+ const contentObject = await client.ContentObject({objectId, versionHash});
60
+
61
+ if(this.args.profile) {
62
+ info.profile = contentObject.content_profile;
63
+ }
64
+
65
+ if(this.args.details) {
66
+ info.details = R.omit(["content_profile"], contentObject);
67
+ }
68
+ }
69
+
70
+ if(this.args.properties) {
71
+ for(const {key, method, empty} of FABRIC_PROPERTIES) {
72
+ try {
73
+ info[key] = await client[method]({objectId, versionHash}) || empty;
74
+ } catch(error) {
75
+ logger.warn(`Unable to retrieve ${key}: ${error.message || error}`);
76
+ info[key] = {error: error.message || String(error)};
77
+ }
78
+ }
79
+ }
80
+
81
+ logger.data("object_info", info);
82
+
83
+ if(this.args.outfile) {
84
+ this.concerns.ArgOutfile.writeJson({obj: info});
85
+ } else {
86
+ logger.logObject(info);
87
+ }
88
+ }
89
+
90
+ header() {
91
+ return `Get info for object ${this.args.versionHash || this.args.objectId}`;
92
+ }
93
+ }
94
+
95
+ if(require.main === module) {
96
+ Utility.cmdLineInvoke(ObjectInfo);
97
+ } else {
98
+ module.exports = ObjectInfo;
99
+ }