@ocfl/ocfl 0.2.2 → 0.4.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,21 +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
 
17
+ /** @typedef {import('./store').OcflStore} OcflStore */
18
+ /** @typedef {import('./types.js').OcflExtensionConfig} OcflExtensionConfig */
19
+ /** @typedef {import('./types.js').OcflVersion} OcflVersion */
20
+
21
+
18
22
  /**
19
23
  * An abstraction to represent the OCFL Storage Root, which is
20
24
  * a base directory used to store OCFL Objects, identified by a [NAMASTE] file “0=ocfl_1.1”.
21
- * @typedef {import('./extension').OcflExtensionConfig} OcflExtensionConfig
22
- * @typedef {import('./store').OcflStore} OcflStore
23
25
  */
24
26
 
25
27
  class OcflStorage {
@@ -122,13 +124,13 @@ class OcflStorage {
122
124
  * @property {('sha256' | 'sha512')} [digestAlgorithm] - Digest algorithm for content-addressing, must use either sha512 or sha256
123
125
  * @property {string} [contentDirectory='content'] - Content directory name. Only applies to a newly created object.
124
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.
125
128
  */
126
129
 
127
130
  /**
128
131
  * General implementation of {@link OcflStorage} that can uses different datastore backends.
129
132
  * This class provides common functionalities for the subclasses and
130
133
  * at the same provide encapsulation emulating private and protected methods.
131
- * @implements {OcflStorage}
132
134
  */
133
135
  class OcflStorageImpl extends OcflStorage {
134
136
 
@@ -158,7 +160,9 @@ class OcflStorageImpl extends OcflStorage {
158
160
  if (this.#workspace && this.#workspace.startsWith(this.#root + path.sep) && !this.#workspace.startsWith(this.#root + path.sep + 'extensions' + path.sep)) {
159
161
  throw new Error('[OcflStorage] config.workspace cannot be the same as or a subpath of config.root');
160
162
  }
161
- this.#layout = layout instanceof OcflStorageLayout ? layout : this.#createLayout(layout);
163
+ if (layout) {
164
+ this.#layout = layout instanceof OcflStorageLayout ? layout : this.#createLayout(layout);
165
+ }
162
166
  this.ocflVersion = ocflVersion || OCFL_VERSION;
163
167
  this.#objectConfig = objectConfig;
164
168
  }
@@ -194,15 +198,14 @@ class OcflStorageImpl extends OcflStorage {
194
198
  if (typeof opt === 'string') id = opt;
195
199
  else ({ id, root } = opt);
196
200
  let relObjectRoot = root || this.objectRoot(id);
197
- return new OcflObjectImpl({
201
+ return new OcflObject({
198
202
  id,
199
203
  root: path.join(this.root, relObjectRoot),
200
204
  // @todo:use extension workspace if not defined
201
205
  workspace: this.#workspace ? path.join(this.#workspace, relObjectRoot) : undefined,
202
- ocflVersion: this.ocflVersion,
206
+ ocflVersion: /** @type {OcflVersion} */(this.ocflVersion),
203
207
  ...this.#objectConfig
204
- },
205
- this.#store);
208
+ }, this.#store);
206
209
  }
207
210
 
208
211
  async exists() {
@@ -231,7 +234,8 @@ class OcflStorageImpl extends OcflStorage {
231
234
  } catch (error) {
232
235
  }
233
236
  this.#layout = this.#createLayout(layoutConfig || layoutName);
234
-
237
+ const layoutDigestAlgorithm = this.#layout.get('digestAlgorithm');
238
+ if (layoutDigestAlgorithm) await OcflDigest.initSync(layoutDigestAlgorithm);
235
239
  //@todo: load storage level extensions
236
240
 
237
241
  return this;
@@ -258,6 +262,8 @@ class OcflStorageImpl extends OcflStorage {
258
262
  if (!this.#layout) {
259
263
  this.#layout = new DEFAULT_LAYOUT();
260
264
  }
265
+ const layoutDigestAlgorithm = this.#layout.get('digestAlgorithm');
266
+ if (layoutDigestAlgorithm) await OcflDigest.initSync(layoutDigestAlgorithm);
261
267
  let layout = { extension: this.#layout.name, description: this.#layout.description };
262
268
  await this.#store.writeFile(path.join(this.root, OCFL_LAYOUT), JSON.stringify(layout, null, 2), 'utf8');
263
269
  if (this.#layout.config) {
@@ -271,6 +277,9 @@ class OcflStorageImpl extends OcflStorage {
271
277
  return this;
272
278
  }
273
279
 
280
+ /**
281
+ * Allow async iteration over all OCFL objects in this storage.
282
+ */
274
283
  [Symbol.asyncIterator]() {
275
284
  let store = this.#store;
276
285
  let workspace = this.#workspace;
@@ -299,10 +308,10 @@ class OcflStorageImpl extends OcflStorage {
299
308
  if (nv) {
300
309
  //ocfl object exists
301
310
  // inventory exists
302
- let object = new OcflObjectImpl({
311
+ let object = new OcflObject({
303
312
  root: basePath,
304
313
  workspace: workspace ? path.join(workspace, path.relative(root, basePath)) : undefined,
305
- ocflVersion: nv,
314
+ ocflVersion: /** @type {OcflVersion} */(nv),
306
315
  ...config
307
316
  }, store);
308
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,7 +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
+ * @deprecated Use {@link createReadable()} instead
56
56
  */
57
57
  async createReadStream(filePath, options) { throw new Error('Not Implemented'); }
58
58
 
@@ -61,7 +61,7 @@ class OcflStore {
61
61
  * @param {string} relPath
62
62
  * @param {*} options
63
63
  * @return {Promise<import('fs').WriteStream>}
64
- * @deprecated
64
+ * @deprecated Use {@link createWritable()} instead
65
65
  */
66
66
  async createWriteStream(relPath, options) { throw new Error('Not Implemented'); }
67
67
 
@@ -84,24 +84,27 @@ class OcflStore {
84
84
  /**
85
85
  * Provide a common interface to read a file inside an object.
86
86
  * A concrete subclass MAY implement this method to provide read access to its underlying storage backend.
87
- * The default implementation uses the {@linkcode OcflStore#createReadStream} method.
87
+ * The default implementation uses the {@linkcode OcflStore#createReadable} method.
88
88
  * @param {string} filePath
89
89
  * @param {*} [options]
90
90
  */
91
91
  async readFile(filePath, options) {
92
- let rs = await this.createReadStream(filePath, options);
93
- 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 }));
94
94
  for await (const chunk of rs) {
95
- chunks.push(chunk);
96
- }
97
- if (typeof options === 'string' || options.encoding) {
98
- return Buffer.concat(chunks);
99
- } else {
100
- 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
+ }
101
103
  }
104
+ return buffer;
102
105
  }
103
106
 
104
- /** @typedef {string|Buffer|NodeJS.ArrayBufferView|AsyncIterable|Iterable|import('stream').Readable} WriteFileData */
107
+ /** @typedef {string|Buffer|Uint8Array|DataView|AsyncIterable|Iterable|ReadableStream} WriteFileData */
105
108
  /**
106
109
  * Provide a common interface to write a file inside an object.
107
110
  * A concrete subclass MAY implement this method to provide write access to its underlying storage backend.
@@ -111,10 +114,15 @@ class OcflStore {
111
114
  * @param {Object|string} options
112
115
  */
113
116
  async writeFile(filePath, data, options) {
114
- var source = dataSourceAsIterable(data);
115
117
  await this.mkdir(path.dirname(filePath));
116
- const target = await this.createWriteStream(filePath, options);
117
- 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
+ }
118
126
  }
119
127
 
120
128
  /**
@@ -124,7 +132,7 @@ class OcflStore {
124
132
  * @param {string} target - destination filename of the copy operation
125
133
  */
126
134
  async copyFile(source, target) {
127
- await this.writeFile(target, await this.createReadStream(source));
135
+ await this.writeFile(target, await this.createReadable(source));
128
136
  }
129
137
 
130
138
  /**