@ocfl/ocfl 0.2.0 → 0.3.0

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/storage.js CHANGED
@@ -5,19 +5,23 @@ const validation = require('./validation.js');
5
5
  const { NotImplementedError } = require("./error");
6
6
  const { OcflStorageLayout } = require("./extension");
7
7
  const { HashedNTupleStorageLayout } = require("./extensions/0004-hashed-n-tuple-storage-layout");
8
- const { OcflObject, OcflObjectImpl } = require("./object");
8
+ const { OcflObject } = require("./object");
9
9
  const { isDirEmpty, findNamasteVersion } = require("./utils.js");
10
10
  const { OCFL_VERSION, OCFL_VERSIONS, OCFL_LAYOUT,
11
11
  EXTENSIONS_DIR, EXTENSION_CONFIG,
12
12
  NAMASTE_PREFIX_STORAGE, NAMASTE_PREFIX_OBJECT, NAMASTE_T } = require('./constants').OcflConstants;
13
- const DIGEST = require('./digest').OcflDigest.CONTENT;
14
-
13
+ const { OcflDigest } = require('./digest');
15
14
 
16
15
  const DEFAULT_LAYOUT = HashedNTupleStorageLayout;
17
16
 
18
- /**
19
- * @typedef {import('./extension').OcflExtensionConfig} OcflExtensionConfig
20
- * @typedef {import('./store').OcflStore} OcflStore
17
+ /** @typedef {import('./store').OcflStore} OcflStore */
18
+ /** @typedef {import('./types.js').OcflExtensionConfig} OcflExtensionConfig */
19
+ /** @typedef {import('./types.js').OcflVersion} OcflVersion */
20
+
21
+
22
+ /**
23
+ * An abstraction to represent the OCFL Storage Root, which is
24
+ * a base directory used to store OCFL Objects, identified by a [NAMASTE] file “0=ocfl_1.1”.
21
25
  */
22
26
 
23
27
  class OcflStorage {
@@ -120,13 +124,13 @@ class OcflStorage {
120
124
  * @property {('sha256' | 'sha512')} [digestAlgorithm] - Digest algorithm for content-addressing, must use either sha512 or sha256
121
125
  * @property {string} [contentDirectory='content'] - Content directory name. Only applies to a newly created object.
122
126
  * @property {string} [ocflVersion=c.OCFL_VERSION] - Ocfl version. Only applies to a newly created object.
127
+ * @property {string[]} [fixityAlgorithms] - A default list of digest algorithm names to be added to the fixity block of objects under this storage.
123
128
  */
124
129
 
125
130
  /**
126
131
  * General implementation of {@link OcflStorage} that can uses different datastore backends.
127
132
  * This class provides common functionalities for the subclasses and
128
133
  * at the same provide encapsulation emulating private and protected methods.
129
- * @implements {OcflStorage}
130
134
  */
131
135
  class OcflStorageImpl extends OcflStorage {
132
136
 
@@ -156,7 +160,9 @@ class OcflStorageImpl extends OcflStorage {
156
160
  if (this.#workspace && this.#workspace.startsWith(this.#root + path.sep) && !this.#workspace.startsWith(this.#root + path.sep + 'extensions' + path.sep)) {
157
161
  throw new Error('[OcflStorage] config.workspace cannot be the same as or a subpath of config.root');
158
162
  }
159
- this.#layout = layout instanceof OcflStorageLayout ? layout : this.#createLayout(layout);
163
+ if (layout) {
164
+ this.#layout = layout instanceof OcflStorageLayout ? layout : this.#createLayout(layout);
165
+ }
160
166
  this.ocflVersion = ocflVersion || OCFL_VERSION;
161
167
  this.#objectConfig = objectConfig;
162
168
  }
@@ -192,15 +198,14 @@ class OcflStorageImpl extends OcflStorage {
192
198
  if (typeof opt === 'string') id = opt;
193
199
  else ({ id, root } = opt);
194
200
  let relObjectRoot = root || this.objectRoot(id);
195
- return new OcflObjectImpl({
201
+ return new OcflObject({
196
202
  id,
197
203
  root: path.join(this.root, relObjectRoot),
198
204
  // @todo:use extension workspace if not defined
199
205
  workspace: this.#workspace ? path.join(this.#workspace, relObjectRoot) : undefined,
200
- ocflVersion: this.ocflVersion,
206
+ ocflVersion: /** @type {OcflVersion} */(this.ocflVersion),
201
207
  ...this.#objectConfig
202
- },
203
- this.#store);
208
+ }, this.#store);
204
209
  }
205
210
 
206
211
  async exists() {
@@ -229,7 +234,8 @@ class OcflStorageImpl extends OcflStorage {
229
234
  } catch (error) {
230
235
  }
231
236
  this.#layout = this.#createLayout(layoutConfig || layoutName);
232
-
237
+ const layoutDigestAlgorithm = this.#layout.get('digestAlgorithm');
238
+ if (layoutDigestAlgorithm) await OcflDigest.initSync(layoutDigestAlgorithm);
233
239
  //@todo: load storage level extensions
234
240
 
235
241
  return this;
@@ -256,6 +262,8 @@ class OcflStorageImpl extends OcflStorage {
256
262
  if (!this.#layout) {
257
263
  this.#layout = new DEFAULT_LAYOUT();
258
264
  }
265
+ const layoutDigestAlgorithm = this.#layout.get('digestAlgorithm');
266
+ if (layoutDigestAlgorithm) await OcflDigest.initSync(layoutDigestAlgorithm);
259
267
  let layout = { extension: this.#layout.name, description: this.#layout.description };
260
268
  await this.#store.writeFile(path.join(this.root, OCFL_LAYOUT), JSON.stringify(layout, null, 2), 'utf8');
261
269
  if (this.#layout.config) {
@@ -269,6 +277,9 @@ class OcflStorageImpl extends OcflStorage {
269
277
  return this;
270
278
  }
271
279
 
280
+ /**
281
+ * Allow async iteration over all OCFL objects in this storage.
282
+ */
272
283
  [Symbol.asyncIterator]() {
273
284
  let store = this.#store;
274
285
  let workspace = this.#workspace;
@@ -297,10 +308,10 @@ class OcflStorageImpl extends OcflStorage {
297
308
  if (nv) {
298
309
  //ocfl object exists
299
310
  // inventory exists
300
- let object = new OcflObjectImpl({
311
+ let object = new OcflObject({
301
312
  root: basePath,
302
313
  workspace: workspace ? path.join(workspace, path.relative(root, basePath)) : undefined,
303
- ocflVersion: nv,
314
+ ocflVersion: /** @type {OcflVersion} */(nv),
304
315
  ...config
305
316
  }, store);
306
317
  //await object.load();
package/lib/store.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
- const { pipeline } = require('stream/promises');
3
- const { parallelize, dataSourceAsIterable } = require('./utils');
2
+ const { parallelize, joinTypedArray } = require('./utils');
4
3
 
5
4
  const emptyOptions = {};
6
5
 
@@ -9,6 +8,7 @@ const emptyOptions = {};
9
8
  * such as local file system and cloud storage
10
9
  */
11
10
  class OcflStore {
11
+ /** @type {Map<Object, OcflStore>} */
12
12
  static instances;
13
13
 
14
14
  /**
@@ -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 Use {@link createReadable()} instead
55
56
  */
56
57
  async createReadStream(filePath, options) { throw new Error('Not Implemented'); }
57
58
 
@@ -60,30 +61,50 @@ class OcflStore {
60
61
  * @param {string} relPath
61
62
  * @param {*} options
62
63
  * @return {Promise<import('fs').WriteStream>}
64
+ * @deprecated Use {@link createWritable()} instead
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.
69
- * The default implementation uses the {@linkcode OcflStore#createReadStream} method.
87
+ * The default implementation uses the {@linkcode OcflStore#createReadable} method.
70
88
  * @param {string} filePath
71
89
  * @param {*} [options]
72
90
  */
73
91
  async readFile(filePath, options) {
74
- let rs = await this.createReadStream(filePath, options);
75
- let chunks = [];
92
+ const rs = await this.createReadable(filePath, options);
93
+ let buffer = (typeof options === 'string' || options.encoding) ? '' : new Uint8Array(new ArrayBuffer(0, { maxByteLength: 16 * 1024 }));
76
94
  for await (const chunk of rs) {
77
- chunks.push(chunk);
78
- }
79
- if (typeof options === 'string' || options.encoding) {
80
- return Buffer.concat(chunks);
81
- } else {
82
- return chunks.join('');
95
+ if (typeof chunk === 'string') {
96
+ if (typeof buffer !== 'string') buffer = new TextDecoder().decode(buffer);
97
+ buffer += chunk;
98
+ } else {
99
+ if (typeof buffer === 'string') buffer = new TextEncoder().encode(buffer);
100
+ // @ts-ignore
101
+ buffer = joinTypedArray(buffer, chunk);
102
+ }
83
103
  }
104
+ return buffer;
84
105
  }
85
106
 
86
- /** @typedef {string|Buffer|NodeJS.ArrayBufferView|AsyncIterable|Iterable|import('stream').Readable} WriteFileData */
107
+ /** @typedef {string|Buffer|Uint8Array|DataView|AsyncIterable|Iterable|ReadableStream} WriteFileData */
87
108
  /**
88
109
  * Provide a common interface to write a file inside an object.
89
110
  * A concrete subclass MAY implement this method to provide write access to its underlying storage backend.
@@ -93,10 +114,15 @@ class OcflStore {
93
114
  * @param {Object|string} options
94
115
  */
95
116
  async writeFile(filePath, data, options) {
96
- var source = dataSourceAsIterable(data);
97
117
  await this.mkdir(path.dirname(filePath));
98
- const target = await this.createWriteStream(filePath, options);
99
- await pipeline(source, target);
118
+ const target = await this.createWritable(filePath, options);
119
+ if (data instanceof ReadableStream) {
120
+ await data.pipeTo(target);
121
+ } else {
122
+ const w = target.getWriter();
123
+ await w.write(data);
124
+ await w.close();
125
+ }
100
126
  }
101
127
 
102
128
  /**
@@ -106,7 +132,7 @@ class OcflStore {
106
132
  * @param {string} target - destination filename of the copy operation
107
133
  */
108
134
  async copyFile(source, target) {
109
- await this.writeFile(target, await this.createReadStream(source));
135
+ await this.writeFile(target, await this.createReadable(source));
110
136
  }
111
137
 
112
138
  /**
@@ -125,6 +151,18 @@ class OcflStore {
125
151
  */
126
152
  async readdir(filePath, options) { throw new Error('Not Implemented'); }
127
153
 
154
+ /**
155
+ * List all files contained in a directory including the metadata such as size and last modified date.
156
+ * The returned file.path is a relative path to the dirPath
157
+ * @param {string} dirPath
158
+ * @param {object} [options]
159
+ * @param {BufferEncoding} [options.encoding]
160
+ * @param {number} [options.bufferSize]
161
+ * @param {boolean} [options.recursive]
162
+ * @return {Promise<AsyncIterableIterator<{name: string, path: string, size: number, lastModified: Date}>> }
163
+ */
164
+ async list(dirPath, { encoding='utf8', bufferSize=32, recursive=false} = {}) { throw new Error('Not Implemented'); }
165
+
128
166
  /**
129
167
  * Recursively create a directory if the storage backend supports it, otherwise do nothing
130
168
  * @param {string} filePath - The directory path