@ocfl/ocfl 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/digest.js CHANGED
@@ -58,17 +58,18 @@ for (let algo of FIXITY) {
58
58
  */
59
59
  function createStream(algorithm) {
60
60
  const hash = algorithms[algorithm || 'sha512']();
61
- hash.setEncoding('hex');
61
+ //hash.setEncoding('hex');
62
62
  return hash;
63
63
  }
64
64
 
65
65
  /**
66
66
  *
67
- * @param {string} algorithm
67
+ * @param {string} [algorithm]
68
+ * @param {object} [options]
68
69
  */
69
- function createStreamThrough(algorithm, options) {
70
- const hash = algorithms[algorithm || 'sha512']();
71
- hash.setEncoding('hex');
70
+ function createStreamThrough(algorithm = 'sha512', options) {
71
+ const hash = algorithms[algorithm]();
72
+ //hash.setEncoding('hex');
72
73
  return new HashThrough(hash, options);
73
74
  }
74
75
 
package/lib/extension.js CHANGED
@@ -99,7 +99,7 @@ class OcflExtension {
99
99
 
100
100
  static get forObject() { return false; }
101
101
  static get forStorage() { return false; }
102
- static setup(ocfl) { };
102
+ static async setup(ocfl) { };
103
103
  static get NAME() { return '0000-example-extension' }
104
104
  static get DESCRIPTION() { return 'Example extension' }
105
105
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  const {OcflExtension} = require("../extension");
4
4
  const crypto = require('crypto');
5
- const blake2 = require('blake2');
5
+ //const blake2 = require('blake2');
6
+ const { createBLAKE2b } = require('hash-wasm');
6
7
 
7
8
  class DigestAlgorithm extends OcflExtension {
8
9
  static get NAME() { return '0001-digest-algorithms' }
@@ -15,7 +16,7 @@ class DigestAlgorithm extends OcflExtension {
15
16
  *
16
17
  * @param {import('ocfl')} ocfl
17
18
  */
18
- static setup(ocfl) {
19
+ static async setup(ocfl) {
19
20
  let {OcflDigest} = ocfl;
20
21
  let { FIXITY } = OcflDigest;
21
22
  class FIXITY_EX extends FIXITY {
@@ -26,9 +27,13 @@ class DigestAlgorithm extends OcflExtension {
26
27
  };
27
28
  OcflDigest.FIXITY = FIXITY_EX;
28
29
  //console.log(OcflDigest.FIXITY);
29
- for (let len of [160, 256, 384]) {
30
- OcflDigest.algorithms['blake2b-' + len] = function() {
31
- return blake2.createHash('blake2b', {digestLength: len/8});
30
+ const blake2b = Object.fromEntries(await Promise.all([160, 256, 384].map(bits => [bits, createBLAKE2b(bits)])));
31
+
32
+ for (let bits of [160, 256, 384]) {
33
+ OcflDigest.algorithms['blake2b-' + bits] = function() {
34
+ //return blake2.createHash('blake2b', {digestLength: len/8});
35
+ blake2b[bits].init();
36
+ return blake2b[bits];
32
37
  }
33
38
  }
34
39
  OcflDigest.algorithms['sha512/256'] = function() {
package/lib/inventory.js CHANGED
@@ -26,31 +26,6 @@ function prevVersion(currentVersion) {
26
26
  * @typedef {{logicalPath?: string; digest?: string; contentPath?: string;}} FileRef
27
27
  */
28
28
 
29
- /**
30
- * @typedef {Object.<string, string[]>} InventoryState
31
- */
32
-
33
- /**
34
- * @typedef {Object} InventoryVersion
35
- * @property {string} [created]
36
- * @property {InventoryState} state
37
- * @property {string} [message]
38
- * @property {{name:string, address:string}} [user]
39
- */
40
-
41
- /**
42
- * @typedef {Object} Inventory
43
- * @property {string} id - A unique identifier for the OCFL Object.
44
- * @property {string} type - The URI of the inventory section of the specification version matching the object conformance declaration.
45
- * @property {string} digestAlgorithm - The digest algorithm used for calculating digests for content-addressing within the OCFL Object and for the Inventory Digest.
46
- * @property {string} head - The version directory name of the most recent version of the object.
47
- * @property {string} [contentDirectory='content'] - The name of the designated content directory within the version directories.
48
- * @property {InventoryState} manifest
49
- * @property {Object.<string, InventoryVersion>} versions
50
- * @property {Object} [fixity]
51
- */
52
-
53
-
54
29
  /**
55
30
  * Wrapper class for inventory data
56
31
  */
@@ -59,18 +34,10 @@ class OcflObjectInventory {
59
34
  #data;
60
35
  /** @type {InventoryVersion} */
61
36
  #version;
62
-
37
+ metadata;
63
38
  /**
64
39
  * Create a mutable inventory and increase the inventory version number
65
- * @param {Object} data
66
- * @param {string} data.id
67
- * @param {string} data.type
68
- * @param {string} data.digestAlgorithm
69
- * @param {string} [data.head]
70
- * @param {string} [data.contentDirectory]
71
- * @param {Object.<string, string[]>} [data.manifest]
72
- * @param {Object.<string, InventoryVersion>} [data.versions]
73
- * @param {Object} [data.fixity]
40
+ * @param {Inventory} data
74
41
  * @param {boolean} [cleanState]
75
42
  */
76
43
  static newVersion(data, cleanState) {
@@ -170,7 +137,8 @@ class OcflObjectInventory {
170
137
  logicalPath,
171
138
  version,
172
139
  digest,
173
- contentPath
140
+ contentPath,
141
+ ...this.metadata?.[contentPath]
174
142
  };
175
143
  }
176
144
  }
@@ -212,7 +180,7 @@ class OcflObjectInventory {
212
180
  * Compare the differences between the state of two versions
213
181
  * @param {string} version0
214
182
  * @param {string} version1
215
- * @return {[InventoryState, InventoryState]} - A tuple of [A, B] where A contains files
183
+ * @return {[InventoryDigests, InventoryDigests]} - A tuple of [A, B] where A contains files
216
184
  * that are in `version0` but not in `version1` and B contains files
217
185
  * that are in `version1` but not in `version0`
218
186
  */
@@ -237,7 +205,7 @@ class OcflObjectInventory {
237
205
  return Object.fromEntries(r);
238
206
  });
239
207
 
240
- return /** @type {[InventoryState, InventoryState]} */(result);
208
+ return /** @type {[InventoryDigests, InventoryDigests]} */(result);
241
209
  }
242
210
  }
243
211
 
package/lib/object.js CHANGED
@@ -11,7 +11,6 @@ const { OcflStore } = require('./store.js');
11
11
  const { OcflExtension } = require('./extension.js');
12
12
  const { NotImplementedError } = require('./error.js');
13
13
  const { parallelize, dataSourceAsIterable, isDirEmpty, findNamasteVersion } = require('./utils');
14
- const { format } = require('util');
15
14
  const { OCFL_VERSION, OCFL_VERSIONS, INVENTORY_NAME, NAMASTE_PREFIX_OBJECT, NAMASTE_T } = require('./constants').OcflConstants;
16
15
 
17
16
  class UPDATE_MODE extends Enum {
@@ -24,9 +23,9 @@ const DIGEST = OcflDigest.CONTENT;
24
23
 
25
24
  /**
26
25
  * @typedef {Object} OcflObjectConfig
27
- * @property {string} root - Absolute path to the ocfl object root
28
- * @property {string} [workspace] - Absolute path to object workspace directory
29
- * @property {('sha256' | 'sha512')} [digestAlgorithm] - Digest algorithm for content-addressing, must use either sha512 or sha256
26
+ * @property {string} root - Absolute path to the ocfl object root.
27
+ * @property {string} [workspace] - Absolute path to object workspace directory.
28
+ * @property {('sha256' | 'sha512')} [digestAlgorithm] - Digest algorithm for content-addressing, must use either sha512 or sha256.
30
29
  * @property {string} [id] - Identifier for the object. Only be used in a newly created object.
31
30
  * @property {string} [contentDirectory='content'] - Content directory name. Only applies to a newly created object.
32
31
  * @property {string} [ocflVersion=c.OCFL_VERSION] - Ocfl version. Only applies to a newly created object.
@@ -35,14 +34,26 @@ const DIGEST = OcflDigest.CONTENT;
35
34
  /**
36
35
  * @typedef {UPDATE_MODE | ('MERGE' | 'REPLACE') } ObjectUpdateMode
37
36
  *
38
- * @typedef {{logicalPath?: string, digest?: string, contentPath?: string, version?: string}} FileRef
37
+ * @typedef {Object} FileRef
38
+ * @property {string} [contentPath] - Actual path to the file location relative to the ocfl object root.
39
+ * @property {string} [logicalPath] - A path that represents a file’s location in the logical state of an object.
40
+ * @property {string} [digest] - An algorithmic characterization of the contents of a file conforming to a standard digest algorithm.
41
+ * @property {string} [version] - Version of the OCFL object.
42
+ * @property {string} [lastModified] - Last modified date of the file.
43
+ * @property {string} [size] - Size of the file in bytes.
39
44
  * @typedef {{logicalPath: string, version?: string}} FileRefLogical
40
45
  * @typedef {{digest: string}} FileRefDigest
41
46
  * @typedef {{contentPath: string}} FileRefContent
42
47
  */
43
48
 
49
+ /**
50
+ * Represent a file in an OCFL object
51
+ */
44
52
  class OcflObjectFile {
45
53
  #ocflObject;
54
+ #lastModified;
55
+ #size;
56
+
46
57
  /**
47
58
  *
48
59
  * @param {OcflObjectImpl} ocflObject
@@ -57,6 +68,8 @@ class OcflObjectFile {
57
68
  if (fileRef.contentPath) this.contentPath = fileRef.contentPath;
58
69
  if (fileRef.digest) this.digest = fileRef.digest;
59
70
  if (fileRef.version) this.version = fileRef.version;
71
+ if (fileRef.lastModified != null) this.#lastModified = fileRef.lastModified;
72
+ if (fileRef.size != null) this.#size = fileRef.size;
60
73
  }
61
74
 
62
75
  /**
@@ -66,6 +79,7 @@ class OcflObjectFile {
66
79
  let contentPath = this.contentPath;
67
80
  if (!contentPath) {
68
81
  let inv = await this.#ocflObject.getInventory();
82
+ if (!inv) throw new Error(`OCFL Object "${this.#ocflObject.id}" does not exist`);
69
83
  let version = !this.version || this.version === 'latest' ? inv.head : this.version;
70
84
  let digest = this.digest || inv.getDigest(this.logicalPath, version);
71
85
  contentPath = inv.getContentPath(digest);
@@ -74,12 +88,22 @@ class OcflObjectFile {
74
88
  return contentPath;
75
89
  }
76
90
 
91
+ /**
92
+ * Returns the last modified time of the file, in millisecond since the UNIX epoch (January 1st, 1970 at Midnight).
93
+ */
94
+ get lastModified() { return this.#lastModified }
95
+
96
+ /**
97
+ * Returns the file size in bytes.
98
+ */
99
+ get size() { return this.#size }
100
+
77
101
  toString() {
78
- let o = {};
102
+ let props = [];
79
103
  for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
80
- if (this[key]) o[key] = this[key];
104
+ if (this[key]) props.push(key + ": '" + this[key] + "'");
81
105
  }
82
- return format('%O', o);
106
+ return `{ ${props.join(', ')} }`;
83
107
  }
84
108
 
85
109
  /**
@@ -88,28 +112,51 @@ class OcflObjectFile {
88
112
 
89
113
  * @return {Promise<Buffer>}
90
114
  */
91
- async asBuffer(options) {
115
+ async buffer(options) {
92
116
  return this.#ocflObject.readFile(await this.#resolveContentPath(), options);
93
117
  }
118
+ /**
119
+ * @deprecated
120
+ */
121
+ async asBuffer(options) {
122
+ return this.buffer(options);
123
+ }
94
124
 
95
125
  /**
96
126
  * Get the file content as a string
97
127
  * @param {BufferEncoding} [encoding='utf8'] String encoding, default to utf8
98
128
  * @return {Promise<string>}
99
129
  */
130
+ async text(encoding = 'utf8') {
131
+ // @ts-ignore
132
+ return this.buffer(encoding);
133
+ }
134
+ /**
135
+ * @deprecated
136
+ */
100
137
  async asString(encoding = 'utf8') {
101
138
  // @ts-ignore
102
- return this.asBuffer(encoding);
139
+ return this.text(encoding);
103
140
  }
104
141
 
105
142
  /**
106
- * Get the file content as a stream
143
+ * Get the file content as a NodeJS stream
107
144
  * @param {*} [options] Options to be passed to underlying data store specific implementation
108
145
  * @return {Promise<NodeJS.ReadableStream>}
146
+ * @deprecated
109
147
  */
110
148
  async asStream(options) {
111
149
  return this.#ocflObject.createReadStream(await this.#resolveContentPath(), options);
112
150
  }
151
+
152
+ /**
153
+ * Get the file content as a ES Streams standard ReadableStream
154
+ * @param {*} [options] Options to be passed to underlying data store specific implementation
155
+ * @return {Promise<ReadableStream>}
156
+ */
157
+ async stream(options) {
158
+ return this.#ocflObject.createReadable(await this.#resolveContentPath(), options);
159
+ }
113
160
  }
114
161
 
115
162
 
@@ -196,12 +243,7 @@ class OcflObject {
196
243
  await this.import(sourceDir, mode);
197
244
  }
198
245
 
199
- async load() {
200
- this._inventory = {};
201
- await this.getInventory();
202
- //await this.loadExtensions();
203
- return;
204
- }
246
+ async load() { throw new NotImplementedError(); }
205
247
 
206
248
  /**
207
249
  * Iterate through the content files contained in the specified version.
@@ -328,7 +370,7 @@ class OcflObjectImpl extends OcflObject {
328
370
  //inventory does not exist yet
329
371
  return;
330
372
  }
331
- let data = /** @type import('./inventory.js').Inventory */(JSON.parse(datastr));
373
+ let data = /** @type Inventory */(JSON.parse(datastr));
332
374
  let [digest, actualDigest] = /** @type [string,string] */ (await Promise.all([
333
375
  this.readFile(invPath + '.' + data.digestAlgorithm, 'utf8'),
334
376
  OcflDigest.digestAsync(data.digestAlgorithm, datastr)
@@ -339,6 +381,23 @@ class OcflObjectImpl extends OcflObject {
339
381
  return new OcflObjectInventory(data);
340
382
  }
341
383
 
384
+ async load() {
385
+ this._inventory = {};
386
+ const inv = await this.getInventory();
387
+ //await this.loadExtensions();
388
+
389
+ //load file metadata from underlying storage
390
+ const metadata = inv.metadata = {};
391
+ for await (const file of await this.#store.list(this.root, { recursive: true })) {
392
+ const contentPath = file.path;
393
+ metadata[contentPath] = {
394
+ size: file.size,
395
+ lastModified: file.lastModified.getTime()
396
+ }
397
+ }
398
+ return;
399
+ }
400
+
342
401
  /**
343
402
  * @param {function(OcflObjectTransaction):Promise<*>} [updater]
344
403
  * @param {ObjectUpdateMode} [mode=UPDATE_MODE.MERGE]
@@ -363,10 +422,15 @@ class OcflObjectImpl extends OcflObject {
363
422
  return t;
364
423
  }
365
424
 
425
+ /** @deprecated */
366
426
  async createReadStream(filePath, options) {
367
427
  return this.#store.createReadStream(path.join(this.root, filePath), options);
368
428
  }
369
429
 
430
+ async createReadable(filePath, options) {
431
+ return this.#store.createReadable(path.join(this.root, filePath), options);
432
+ }
433
+
370
434
  /**
371
435
  @typedef {import('fs').OpenMode} OpenMode
372
436
  @typedef {import('events').Abortable} Abortable
package/lib/storage.js CHANGED
@@ -15,7 +15,9 @@ const DIGEST = require('./digest').OcflDigest.CONTENT;
15
15
 
16
16
  const DEFAULT_LAYOUT = HashedNTupleStorageLayout;
17
17
 
18
- /**
18
+ /**
19
+ * An abstraction to represent the OCFL Storage Root, which is
20
+ * a base directory used to store OCFL Objects, identified by a [NAMASTE] file “0=ocfl_1.1”.
19
21
  * @typedef {import('./extension').OcflExtensionConfig} OcflExtensionConfig
20
22
  * @typedef {import('./store').OcflStore} OcflStore
21
23
  */
package/lib/store.js CHANGED
@@ -52,6 +52,7 @@ class OcflStore {
52
52
  * @param {string} filePath - Absolute path
53
53
  * @param {*} options
54
54
  * @return {Promise<import('fs').ReadStream>}
55
+ * @deprecated
55
56
  */
56
57
  async createReadStream(filePath, options) { throw new Error('Not Implemented'); }
57
58
 
@@ -60,9 +61,26 @@ class OcflStore {
60
61
  * @param {string} relPath
61
62
  * @param {*} options
62
63
  * @return {Promise<import('fs').WriteStream>}
64
+ * @deprecated
63
65
  */
64
66
  async createWriteStream(relPath, options) { throw new Error('Not Implemented'); }
65
67
 
68
+ /**
69
+ * Create a ReadableStream to get the content of a file
70
+ * @param {string} filePath - Absolute path
71
+ * @param {*} options
72
+ * @return {Promise<ReadableStream>}
73
+ */
74
+ async createReadable(filePath, options) { throw new Error('Not Implemented'); }
75
+
76
+ /**
77
+ * Create a WritableStream that writes data to {@link relPath}.
78
+ * @param {string} relPath
79
+ * @param {*} options
80
+ * @return {Promise<WritableStream>}
81
+ */
82
+ async createWritable(relPath, options) { throw new Error('Not Implemented'); }
83
+
66
84
  /**
67
85
  * Provide a common interface to read a file inside an object.
68
86
  * A concrete subclass MAY implement this method to provide read access to its underlying storage backend.
@@ -125,6 +143,18 @@ class OcflStore {
125
143
  */
126
144
  async readdir(filePath, options) { throw new Error('Not Implemented'); }
127
145
 
146
+ /**
147
+ * List all files contained in a directory including the metadata such as size and last modified date.
148
+ * The returned file.path is a relative path to the dirPath
149
+ * @param {string} dirPath
150
+ * @param {object} [options]
151
+ * @param {BufferEncoding} [options.encoding]
152
+ * @param {number} [options.bufferSize]
153
+ * @param {boolean} [options.recursive]
154
+ * @return {Promise<AsyncIterableIterator<{name: string, path: string, size: number, lastModified: Date}>> }
155
+ */
156
+ async list(dirPath, { encoding='utf8', bufferSize=32, recursive=false} = {}) { throw new Error('Not Implemented'); }
157
+
128
158
  /**
129
159
  * Recursively create a directory if the storage backend supports it, otherwise do nothing
130
160
  * @param {string} filePath - The directory path
package/lib/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- type OcflExtensionCreate = <T extends typeof import('./extension').OcflExtension>(this: T, config?: OcflExtensionConfig) => InstanceType<T>;
2
+ //type OcflExtensionCreate = <T extends typeof import('./extension').OcflExtension>(this: T, config?: OcflExtensionConfig) => InstanceType<T>;
3
3
 
4
4
 
5
5
  // import { Abortable } from "events";
@@ -51,3 +51,45 @@ type StorageLayout = {
51
51
  NTupleOmitPrefixStorageLayout: typeof import('./extensions/0007-n-tuple-omit-prefix-storage-layout').NTupleOmitPrefixStorageLayout
52
52
  PathDirectStorageLayout: typeof import('./extensions/000N-path-direct-storage-layout').PathDirectStorageLayout
53
53
  }
54
+
55
+ interface InventoryDigests {
56
+ [key:string]: string[];
57
+ }
58
+
59
+ interface InventoryVersion {
60
+ created: string;
61
+ message?: string;
62
+ state: InventoryDigests;
63
+ user?: {
64
+ name: string;
65
+ address?: string;
66
+ };
67
+ }
68
+
69
+ interface Inventory {
70
+ /** A unique identifier for the OCFL Object. */
71
+ id: string;
72
+ /** The URI of the inventory section of the specification version matching the object conformance declaration. */
73
+ type: 'https://ocfl.io/1.0/spec/#inventory' | 'https://ocfl.io/1.1/spec/#inventory';
74
+ /**
75
+ * The algorithm used for calculating digests for content-addressing
76
+ * within the OCFL Object and for the Inventory Digest.
77
+ * This MUST be the algorithm used in the manifest and state blocks
78
+ */
79
+ digestAlgorithm: 'sha256' | 'sha512';
80
+ /** The version directory name of the most recent version of the object. */
81
+ head: string;
82
+ manifest: InventoryDigests;
83
+ versions: {
84
+ [key:string]: InventoryVersion;
85
+ };
86
+ fixity?: {
87
+ [key in 'md5'|'sha1'|'sha256'|'sha512'|'blake2b-512']: {};
88
+ };
89
+ /**
90
+ * The name of the designated content directory within the version directories.
91
+ * @default content
92
+ */
93
+ contentDirectory?: string;
94
+ }
95
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ocfl/ocfl",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Oxford Common File Layout (OCFL) JS library",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "repository": {
27
27
  "type": "git",
28
- "url": "git+https://github.com/Arkisto-Platform/ocfl-js.git"
28
+ "url": "git+https://github.com/Language-Research-Technology/ocfl-js.git"
29
29
  },
30
30
  "author": "Alvin Sebastian (https://orcid.org/0000-0002-9086-1722)",
31
31
  "contributors": [
@@ -34,13 +34,13 @@
34
34
  ],
35
35
  "license": "GPL-3.0-or-later",
36
36
  "dependencies": {
37
- "blake2": "^4.1.1"
37
+ "hash-wasm": "^4.11.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/mocha": "^9.1.1",
41
41
  "@types/node": "^17.0.33",
42
- "typedoc": "^0.23.15",
43
- "mocha": "^10.0.0"
42
+ "mocha": "^10.7.3",
43
+ "typedoc": "^0.23.15"
44
44
  },
45
45
  "mocha": {
46
46
  "extension": [
@@ -51,9 +51,12 @@
51
51
  "recursive": true
52
52
  },
53
53
  "bugs": {
54
- "url": "https://github.com/Arkisto-Platform/ocfl-js/issues"
54
+ "url": "https://github.com/Language-Research-Technology/ocfl-js/issues"
55
+ },
56
+ "homepage": "https://github.com/Language-Research-Technology/ocfl-js#readme",
57
+ "engines": {
58
+ "node": ">=17.0.0"
55
59
  },
56
- "homepage": "https://github.com/Arkisto-Platform/ocfl-js#readme",
57
60
  "directories": {
58
61
  "lib": "lib",
59
62
  "test": "test"