@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.
@@ -1,17 +1,13 @@
1
1
  //@ts-check
2
2
  /** Models a set of ocfl object modifying operations as a transaction that is as close as atomic as possible */
3
- const { createReadStream } = require('fs');
4
- const { stat, readdir } = require('fs/promises');
3
+ //todo: replace path module with a more generic path module that works in both node and browser
5
4
  const path = require('path');
6
- const { parallelize, dataSourceAsIterable } = require('./utils');
5
+ const { parallelize } = require('./utils');
7
6
  const { OcflDigest } = require('./digest');
8
7
  const { INVENTORY_NAME } = require('./constants').OcflConstants;
9
8
 
10
- //const { pipeline } = require('stream/promises');
11
- //const { OcflObjectImpl, OcflObjectInventory } = require('./object');
12
-
13
9
  /**
14
- * @typedef {import('./object').OcflObjectImpl} OcflObjectImpl
10
+ * @typedef {import('./object').OcflObject} OcflObject
15
11
  * @typedef {import('./store').OcflStore} OcflStore
16
12
  * @typedef {import('./inventory').OcflObjectInventoryMut} OcflObjectInventoryMut
17
13
  * @typedef {import('stream').Writable} Writable
@@ -39,6 +35,7 @@ class OcflObjectTransaction {
39
35
  * @param {Object} [options.user] - Identify the user or agent that created the current Version
40
36
  * @param {string} options.user.name - Any readable name of the user, e.g., a proper name, user ID, agent ID.
41
37
  * @param {string} [options.user.address] - A URI: either a mailto URI [RFC6068] with the e-mail address of the user or a URL to a personal identifier, e.g., an ORCID iD.
38
+ * @param {boolean} [options.force=false] - If true, force to commit. Use it after a purge.
42
39
  */
43
40
  async commit(options) { throw new Error('Not Implemented'); }
44
41
 
@@ -51,22 +48,24 @@ class OcflObjectTransaction {
51
48
  return await this.remove(logicalPath, { purge: true });
52
49
  }
53
50
 
54
-
55
51
  /**
56
52
  * Write data to a file at `logicalPath`
57
53
  * @abstract
58
54
  * @param {string} logicalPath - A path relative to the object content directory
59
- * @param {*} data - The same data as https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback
55
+ * @param {string|Uint8Array|DataView|ArrayBuffer|ReadableStream} data - Data to be written to the file
60
56
  * @param {Object|string} [options] - The same options as https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback
61
57
  */
62
58
  async write(logicalPath, data, options) { throw new Error('Not Implemented'); }
63
59
 
64
60
  /**
65
61
  * Import content by recursively copying a file or directory from local file system to the
66
- * OCFL Object content. For example, `copy('/home/john/data/b','data/b')` will
62
+ * OCFL Object content. For example, `object.import('/home/john/data/b','data/b')` will
67
63
  * copy `/home/john/data/b` from the local filesystem to the content `data/b` in the OFCL Object.
68
64
  * Set target to empty string ('') to copy a directory content to the object's content root.
69
65
  * If target is not specified, the base name of the source will be used.
66
+ * For examples:
67
+ * To copy '/src/data' directory as 'data' use import('/src/data', 'data').
68
+ * To copy all files inside '/src/data' directory to the logical root, use import('/src/data', '').
70
69
  * @abstract
71
70
  * @param {string} source - A path to a file or directory in the local filesystem to be copied
72
71
  * @param {string} [target] - A logical path in the OCFL Object content.
@@ -84,7 +83,7 @@ class OcflObjectTransaction {
84
83
 
85
84
  /**
86
85
  * Changes the logical path of existing content. This is similar to move operation in file systems
87
- * This operation will NOT rename the actual file name referenced by the manifest.
86
+ * Unless renaming files that are not yet commited, this operation will NOT rename the actual file name referenced by the manifest.
88
87
  * @abstract
89
88
  * @param {*} source
90
89
  * @param {*} target
@@ -92,7 +91,7 @@ class OcflObjectTransaction {
92
91
  async rename(source, target) { throw new Error('Not Implemented'); }
93
92
 
94
93
  /**
95
- * Changes the file path of existing content. This is similar to move operation in file systems
94
+ * Alias of {@link rename()}
96
95
  * @param {*} source
97
96
  * @param {*} target
98
97
  */
@@ -103,11 +102,12 @@ class OcflObjectTransaction {
103
102
  * @abstract
104
103
  * @param {string} logicalPath - A path relative to the object content directory
105
104
  * @param {Object} [options]
105
+ * @param {boolean} [options.purge=false] - If true, completely remove the content from all versions
106
106
  */
107
107
  async remove(logicalPath, options) { throw new Error('Not Implemented'); }
108
108
 
109
109
  /**
110
- * Remove a file or directory
110
+ * Remove a file or directory. Alias of {@link remove()}
111
111
  * @param {string} logicalPath - A path relative to the object content directory
112
112
  * @param {Object} [options]
113
113
  */
@@ -123,14 +123,23 @@ class OcflObjectTransaction {
123
123
  async reinstate(logicalPath, versionName) { throw new Error('Not Implemented'); }
124
124
 
125
125
  /**
126
- * Create a writable stream
126
+ * Create a NodeJS writable stream
127
127
  * @abstract
128
128
  * @param {string} logicalPath
129
129
  * @param {BufferEncoding|StreamOptions} [options]
130
130
  * @return {Promise<import('stream').Writable>}
131
+ * @deprecated Use {@link createWritable()} instead
131
132
  */
132
133
  async createWriteStream(logicalPath, options) { throw new Error('Not Implemented'); }
133
134
 
135
+ /**
136
+ * Create a writable stream
137
+ * @abstract
138
+ * @param {string} logicalPath
139
+ * @param {*} [options]
140
+ * @return {Promise<WritableStream>}
141
+ */
142
+ async createWritable(logicalPath, options) { throw new Error('Not Implemented'); }
134
143
  }
135
144
 
136
145
 
@@ -142,12 +151,10 @@ class OcflObjectTransactionImpl extends OcflObjectTransaction {
142
151
  _object;
143
152
  _inventory;
144
153
  _committed = false;
145
- _unfinished = 0;
146
-
147
154
 
148
155
  /**
149
156
  *
150
- * @param {OcflObjectImpl} ocflObject
157
+ * @param {OcflObject} ocflObject
151
158
  * @param {OcflStore} ocflStore
152
159
  * @param {OcflObjectInventoryMut} inventory
153
160
  * @param {string} workspaceVersionPath
@@ -161,9 +168,29 @@ class OcflObjectTransactionImpl extends OcflObjectTransaction {
161
168
  this._store = ocflStore;
162
169
  this._inventory = inventory;
163
170
  this._createdDir = createdDir;
171
+ /** @type {(Promise<any> & { done?: boolean})[]} */
164
172
  this._queue = [];
165
173
  }
166
174
 
175
+ /**
176
+ * Create a new transaction instance.
177
+ * @param {OcflObject} ocflObject
178
+ * @param {OcflStore} ocflStore
179
+ * @param {OcflObjectInventoryMut} inventory
180
+ * @param {string} workspacePath
181
+ */
182
+ static async create(ocflObject, ocflStore, inventory, workspacePath) {
183
+ // @todo: validate existing object
184
+ // check existing
185
+ let workspaceVersionPath = path.join(workspacePath, inventory.head);
186
+ if (await ocflStore.exists(workspaceVersionPath)) {
187
+ // workspace dir exists, abort update
188
+ throw new Error('Uncommitted changes detected. Object is being updated by other process or there has been a failed update attempt');
189
+ }
190
+ await ocflStore.mkdir(workspacePath);
191
+ return new OcflObjectTransactionImpl(ocflObject, ocflStore, inventory, workspaceVersionPath, workspacePath);
192
+ }
193
+
167
194
  _getRealPath(logicalPath) {
168
195
  let p = path.normalize(logicalPath);
169
196
  if (p.startsWith('/') || p.startsWith('..')) throw new Error('logicalPath must be a relative path without `..`');
@@ -182,13 +209,13 @@ class OcflObjectTransactionImpl extends OcflObjectTransaction {
182
209
  // console.log('commit');
183
210
  // console.log(this._inventory.toString());
184
211
  if (!this._committed) {
185
- let count = this._unfinished;
212
+ let count = this._queue.filter(p => !p.done).length;
186
213
  if (count > 0) {
187
214
  await this.rollback();
188
215
  throw new Error(`Unfinished operations detected (${count}). Make sure that updater callback is async function and there are no floating promises inside.`);
189
216
  }
190
217
  // if there is no changes, abort commit
191
- if (!this._inventory.isChanged) {
218
+ if (!options?.force && !this._inventory.isChanged) {
192
219
  return await this.rollback();
193
220
  }
194
221
  await this._commit(options);
@@ -199,22 +226,26 @@ class OcflObjectTransactionImpl extends OcflObjectTransaction {
199
226
  }
200
227
 
201
228
  async _commit(options = {}) {
229
+ const inventory = this._inventory;
230
+ const digestAlgorithm = inventory.digestAlgorithm;
202
231
  // set commit info
203
- this._inventory.created = (new Date()).toISOString();
204
- if (options.message) this._inventory.message = options.message;
205
- if (options.user) this._inventory.user = options.user;
232
+ inventory.created = (new Date()).toISOString();
233
+ if (options.message) inventory.message = options.message;
234
+ if (options.user) inventory.user = options.user;
206
235
 
207
236
  let workspaceVersionPath = this._workspaceVersionPath;
208
- let objectVersionPath = path.join(this._object.root, this._inventory.head);
237
+ let objectVersionPath = path.join(this._object.root, inventory.head);
209
238
  /* eg: /workspace/aa/bb/cc/object-1/v1/inventory.json */
210
239
  let invPath = path.join(workspaceVersionPath, INVENTORY_NAME);
211
240
  /* eg: /workspace/aa/bb/cc/object-1/v1/inventory.json.sha512 */
212
- let invDigestPath = invPath + '.' + this._inventory.digestAlgorithm;
241
+ let invDigestPath = invPath + '.' + digestAlgorithm;
213
242
 
214
243
  // calculate digest
215
- let digest = this._inventory.digest() + ' ' + INVENTORY_NAME;
244
+ const inventoryContent = inventory.toString();
245
+ let digest = (await OcflDigest.digest(digestAlgorithm, inventoryContent)) + ' ' + INVENTORY_NAME;
246
+
216
247
  // write inventory.json to workspace
217
- await this._store.writeFile(invPath, this._inventory.toString());
248
+ await this._store.writeFile(invPath, inventoryContent);
218
249
  await this._store.writeFile(invDigestPath, digest);
219
250
  if (workspaceVersionPath !== objectVersionPath) {
220
251
  try {
@@ -228,11 +259,11 @@ class OcflObjectTransactionImpl extends OcflObjectTransaction {
228
259
  }
229
260
  // replace root inventory
230
261
  /* eg: /data/aa/bb/cc/object-1/inventory.json */
231
- let rootInvPath = path.join(this._object.root, INVENTORY_NAME);
262
+ const rootInvPath = path.join(this._object.root, INVENTORY_NAME);
232
263
  /* eg: /data/aa/bb/cc/object-1/v1/inventory.json */
233
264
  invPath = path.join(objectVersionPath, INVENTORY_NAME);
234
- invDigestPath = invPath + '.' + this._inventory.digestAlgorithm;
235
- let rootInvDigestPath = rootInvPath + '.' + this._inventory.digestAlgorithm;
265
+ invDigestPath = invPath + '.' + digestAlgorithm;
266
+ let rootInvDigestPath = rootInvPath + '.' + digestAlgorithm;
236
267
  await this._store.copyFile(invPath, rootInvPath + '.tmp');
237
268
  await Promise.all([
238
269
  this._store.move(rootInvPath + '.tmp', rootInvPath),
@@ -244,88 +275,124 @@ class OcflObjectTransactionImpl extends OcflObjectTransaction {
244
275
  *
245
276
  * @param {string} logicalPath
246
277
  * @param {Object} options
247
- * @return {Promise<import('stream').Writable>}
278
+ * @return {Promise<WritableStream>}
248
279
  */
249
- async createWriteStream(logicalPath, options) {
250
- let realPath = this._getRealPath(logicalPath);
251
- let ws = await this._store.createWriteStream(realPath, options);
252
- let hs = OcflDigest.createStream(this._inventory.digestAlgorithm);
253
- const wwrite = ws.write;
254
- function nwrite(chunk, encoding, cb) {
255
- hs.update(chunk, encoding);
256
- return wwrite.apply(ws, arguments);
257
- };
258
- ws.write = nwrite;
259
- ws.on('close', async () => {
260
- let digest = hs.digest('hex');
261
- if (this._inventory.getContentPath(digest)) await this._store.remove(realPath);
262
- this._inventory.add(logicalPath, digest);
280
+ async createWritable(logicalPath, options) {
281
+ if (this._committed) throw new Error('Transaction already commited');
282
+ const digestAlgo = this._inventory.digestAlgorithm;
283
+ const digestAlgos = [digestAlgo, ...(this._object.fixityAlgorithms || [])];
284
+ const hs = await OcflDigest.createStream(digestAlgos);
285
+ const realPath = this._getRealPath(logicalPath);
286
+ const writer = (await this._store.createWritable(realPath, options)).getWriter();
287
+ //const hs = await OcflDigest.createStream(this._inventory.digestAlgorithm);
288
+ const inv = this._inventory;
289
+ const store = this._store;
290
+ const ws = new WritableStream({
291
+ write(chunk, controller) {
292
+ hs.update(chunk);
293
+ return writer.write(chunk);
294
+ },
295
+ async close() {
296
+ await writer.close();
297
+ //const digest = hs.digest('hex');
298
+ const {[digestAlgo]: digest, ...fixity} = hs.digest();
299
+ if (inv.getContentPath(digest)) await store.remove(realPath);
300
+ inv.add(logicalPath, digest, fixity);
301
+ }
263
302
  });
303
+ /** @type {Promise<any> & { done?: boolean }} */
304
+ const p = writer.closed;
305
+ p.finally(() => { p.done = true; });
306
+ this._queue.push(p);
307
+
264
308
  return ws;
265
309
  }
266
310
 
267
311
  async write(logicalPath, data, options) {
268
- let realPath = this._getRealPath(logicalPath);
269
- let dataSrc = dataSourceAsIterable(data);
270
- let digest;
271
- if (Array.isArray(dataSrc)) {
272
- // if data is not stream, check digest first
273
- digest = await OcflDigest.digestAsync(this._inventory.digestAlgorithm, dataSrc);
274
- if (!this._inventory.getContentPath(digest)) {
275
- // no digest yet, write the file to storage backend
276
- await this._store.writeFile(realPath, data, options);
312
+ return this._transact(async () => {
313
+ let realPath = this._getRealPath(logicalPath);
314
+ const digestAlgo = this._inventory.digestAlgorithm;
315
+ const digestAlgos = [digestAlgo, ...(this._object.fixityAlgorithms || [])];
316
+ let digest, fixity;
317
+ if (typeof data === 'string' || ArrayBuffer.isView(data)) {
318
+ // if data is not stream, check digest first
319
+ if (ArrayBuffer.isView(data) && !(data instanceof Uint8Array || data instanceof Uint16Array || data instanceof Uint32Array)) {
320
+ data = new Uint8Array(data.buffer)
321
+ }
322
+ const hashes = await OcflDigest.digest(digestAlgos, data);
323
+ ({[digestAlgo]: digest, ...fixity} = hashes);
324
+ if (!this._inventory.getContentPath(digest)) {
325
+ // no digest yet, write the file to storage backend
326
+ await this._store.writeFile(realPath, data, options);
327
+ }
328
+ } else if (data instanceof ReadableStream) {
329
+ // save the stream first
330
+ const hs = await OcflDigest.createStreamThrough(digestAlgos);
331
+ const rs = data.pipeThrough(hs);
332
+ await this._store.writeFile(realPath, rs, options);
333
+ ({[digestAlgo]: digest, ...fixity} = hs.digest());
334
+ // already exists, delete temp file
335
+ if (this._inventory.getContentPath(digest)) await this._store.remove(realPath);
336
+ } else {
337
+ throw new TypeError('Unsupported data type ', data);
277
338
  }
278
- } else {
279
- // save the stream first
280
- let hs = OcflDigest.createStreamThrough(this._inventory.digestAlgorithm);
281
- data.pipe(hs);
282
- await this._store.writeFile(realPath, hs, options);
283
- digest = hs.digest();
284
- // already exists, delete temp file
285
- if (this._inventory.getContentPath(digest)) await this._store.remove(realPath);
286
- }
287
- this._inventory.add(logicalPath, digest);
288
- // console.log(this._inventory.toString());
339
+ this._inventory.add(logicalPath, digest, fixity);
340
+ // console.log(this._inventory.toString());
341
+ });
289
342
  }
290
343
 
344
+ /**
345
+ * Import a single file from source. This is a helper method for {@link OcflObjectTransaction.import | import()} method.
346
+ * @param {*} source
347
+ * @param {*} target
348
+ */
291
349
  async importFile(source, target) {
292
350
  if (!target) throw new TypeError('Target logical path must not be empty if source is a file.');
293
- let realPath = this._getRealPath(target);
294
- let digest = await OcflDigest.digestFromFile(this._inventory.digestAlgorithm, source);
351
+ const realPath = this._getRealPath(target);
352
+ const rs = await this._store.createReadable(source);
353
+ const digestAlgo = this._inventory.digestAlgorithm;
354
+ const digestAlgos = [digestAlgo, ...(this._object.fixityAlgorithms || [])];
355
+ const {[digestAlgo]: digest, ...fixity} = await OcflDigest.digest(digestAlgos, rs);
295
356
 
296
357
  if (!this._inventory.getContentPath(digest)) {
297
358
  await this._store.copyFile(source, realPath);
298
359
  }
299
- this._inventory.add(target, digest);
360
+ this._inventory.add(target, digest, fixity);
300
361
  }
301
362
 
302
363
  async import(source, target) {
303
- target = target ?? path.basename(source);
304
- let srcStat = await stat(source);
305
- if (srcStat.isFile()) {
306
- await this.importFile(source, target);
307
- } else if (srcStat.isDirectory()) {
308
- const files = await readdir(source);
309
- await parallelize(files, async (filename) => {
310
- await this.import(path.join(source, filename), path.join(target, filename));
311
- });
312
- }
364
+ return this._transact(async () => {
365
+ target = target ?? path.basename(source);
366
+ let srcStat = await this._store.stat(source);
367
+ if (srcStat.isFile()) {
368
+ await this.importFile(source, target);
369
+ } else if (srcStat.isDirectory()) {
370
+ const files = await this._store.readdir(source);
371
+ await parallelize(files, async (filename) => {
372
+ await this.import(path.join(source, filename), path.join(target, filename));
373
+ });
374
+ }
375
+ });
313
376
  }
314
377
 
315
378
  async copy(source, target) {
316
- return this._inventory.copy(source, target);
379
+ return this._transact(async () => {
380
+ return this._inventory.copy(source, target);
381
+ });
317
382
  }
318
383
 
319
384
  async rename(source, target) {
320
- // if file already exists in the workspace, rename the actual file
321
- try {
322
- let realSource = this._getRealPath(source);
323
- let realTarget = this._getRealPath(target);
324
- await this._store.move(realSource, realTarget);
325
- } catch (error) {
326
- if (error.code !== 'ENOENT') throw error;
327
- }
328
- return this._inventory.rename(source, target);
385
+ return this._transact(async () => {
386
+ // if file already exists in the workspace, rename the actual file
387
+ try {
388
+ let realSource = this._getRealPath(source);
389
+ let realTarget = this._getRealPath(target);
390
+ await this._store.move(realSource, realTarget);
391
+ } catch (error) {
392
+ if (error.code !== 'ENOENT') throw error;
393
+ }
394
+ return this._inventory.rename(source, target);
395
+ });
329
396
  }
330
397
 
331
398
  async reinstate(logicalPath, versionName) {
@@ -343,71 +410,39 @@ class OcflObjectTransactionImpl extends OcflObjectTransaction {
343
410
  }
344
411
 
345
412
  async remove(logicalPath, options) {
346
- if (this._inventory.delete(logicalPath)) {
347
- try {
348
- await this._store.remove(this._getRealPath(logicalPath));
349
- } catch (error) {
413
+ return this._transact(async () => {
414
+ if (options?.purge) {
415
+ // completely remove content from all versions
416
+ let ver = this._inventory.versionNumber.previous();
417
+ while (ver) {
418
+ this._inventory.delete(logicalPath, ver.toString());
419
+ ver = ver.previous();
420
+ };
350
421
  }
351
- }
422
+ if (this._inventory.delete(logicalPath)) {
423
+ return this._store.remove(this._getRealPath(logicalPath));
424
+ }
425
+ });
352
426
  }
353
427
 
354
- }
355
-
356
-
357
- const transactionInterface = new OcflObjectTransaction();
358
- const methodWrapper = {
359
- createWriteStream: async function (logicalPath, options) {
360
- if (this._committed) throw new Error('Transaction already commited');
361
- this._unfinished++;
362
- let result = await this.createWriteStream(logicalPath, options);
363
- result.on('close', () => --this._unfinished);
364
- return result;
365
- }
366
- };
367
- ['write', 'import', 'copy', 'rename', 'remove', 'reinstate'].forEach(name => {
368
- methodWrapper[name] = async function () {
369
- if (this._committed) throw new Error('Transaction already commited');
370
- this._unfinished++;
371
- let p = this[name](...arguments);
372
- this._queue.push(p);
373
- try {
374
- let result = await p;
375
- this._unfinished--;
376
- return result;
377
- } catch (error) {
378
- this._unfinished--;
379
- throw error;
380
- }
381
- };
382
- });
383
- const proxyHandler = {
384
428
  /**
385
- * @param {OcflObjectTransactionImpl} target
386
- * @param {string} prop
429
+ * A function wrapper to provide safe mutating operation that
430
+ * will check if a transaction is already committed and track any unfinished promises.
431
+ * @template T
432
+ * @param {() => Promise<T>} op
387
433
  */
388
- get(target, prop) {
389
- if (prop in transactionInterface) {
390
- let p = target[prop];
391
- if (typeof p === 'function') {
392
- return methodWrapper[prop]?.bind(target) ?? p.bind(target);
393
- } else {
394
- return p;
395
- }
396
- }
434
+ async _transact(op) {
435
+ if (this._committed) throw new Error('Transaction already commited');
436
+ /** @type {Promise<any> & { done?: boolean }} */
437
+ const p = op();
438
+ p.finally(() => { p.done = true; });
439
+ this._queue.push(p);
440
+ return p;
397
441
  }
398
- }
399
442
 
400
- /**
401
- * Return any OcflObjectTransactionImpl implementation as OcflObjectTransaction.
402
- * @param {OcflObjectTransactionImpl} impl
403
- * @return {OcflObjectTransaction}
404
- */
405
- function createTransactionProxy(impl) {
406
- return new Proxy(impl, proxyHandler);
407
443
  }
408
444
 
409
445
  module.exports = {
410
- createTransactionProxy,
411
446
  OcflObjectTransaction,
412
447
  OcflObjectTransactionImpl
413
448
  };
package/lib/types.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
 
2
- type OcflExtensionCreate = <T extends typeof import('./extension').OcflExtension>(this: T, config?: OcflExtensionConfig) => InstanceType<T>;
3
-
2
+ //type OcflExtensionCreate = <T extends typeof import('./extension').OcflExtension>(this: T, config?: OcflExtensionConfig) => InstanceType<T>;
4
3
 
5
4
  // import { Abortable } from "events";
6
5
  // import { ObjectEncodingOptions, OpenMode, PathLike } from "fs";
@@ -43,11 +42,113 @@ interface ReadFile {
43
42
  ): Promise<string>;
44
43
  }
45
44
 
46
- type StorageLayout = {
47
- FlatDirectStorageLayout: typeof import('./extensions/0002-flat-direct-storage-layout').FlatDirectStorageLayout
48
- HashAndIdNTupleStorageLayout: typeof import('./extensions/0003-hash-and-id-n-tuple-storage-layout').HashAndIdNTupleStorageLayout
49
- HashedNTupleStorageLayout: typeof import('./extensions/0004-hashed-n-tuple-storage-layout').HashedNTupleStorageLayout
50
- FlatOmitPrefixStorageLayout: typeof import('./extensions/0006-flat-omit-prefix-storage-layout').FlatOmitPrefixStorageLayout
51
- NTupleOmitPrefixStorageLayout: typeof import('./extensions/0007-n-tuple-omit-prefix-storage-layout').NTupleOmitPrefixStorageLayout
52
- PathDirectStorageLayout: typeof import('./extensions/000N-path-direct-storage-layout').PathDirectStorageLayout
45
+ export interface InventoryDigests {
46
+ [key: string]: string[];
47
+ }
48
+
49
+ export interface InventoryVersion {
50
+ created: string;
51
+ message?: string;
52
+ state: InventoryDigests;
53
+ user?: {
54
+ name: string;
55
+ address?: string;
56
+ };
53
57
  }
58
+
59
+ export interface Inventory {
60
+ /** A unique identifier for the OCFL Object. */
61
+ id: string;
62
+ /** The URI of the inventory section of the specification version matching the object conformance declaration. */
63
+ type: 'https://ocfl.io/1.0/spec/#inventory' | 'https://ocfl.io/1.1/spec/#inventory';
64
+ /**
65
+ * The algorithm used for calculating digests for content-addressing
66
+ * within the OCFL Object and for the Inventory Digest.
67
+ * This MUST be the algorithm used in the manifest and state blocks
68
+ */
69
+ digestAlgorithm: 'sha256' | 'sha512';
70
+ /** The version directory name of the most recent version of the object. */
71
+ head: string;
72
+ manifest: InventoryDigests;
73
+ versions: {
74
+ [key: string]: InventoryVersion;
75
+ };
76
+ fixity?: {
77
+ [key: string]: {
78
+ [key: string]: string[];
79
+ };
80
+ // [key in 'md5'|'sha1'|'sha256'|'sha512'|'blake2b-512'|'blake2b-160'|'blake2b-256'|'blake2b-384'|'sha512-256'|'size'|'crc32']: {};
81
+ };
82
+ /**
83
+ * The name of the designated content directory within the version directories.
84
+ * @default content
85
+ */
86
+ contentDirectory?: string;
87
+ }
88
+
89
+ //type Enum<T extends readonly string[]> = {[key in T[number]]: ReturnType<typeof enumeration<T>>};
90
+ //declare function enumeration<T extends readonly string[]>(arr: T): Enum<T>;
91
+ export type IDataType = string | ArrayBufferLike | Uint8Array | Uint16Array | Uint32Array;
92
+
93
+ export interface CommonHasher {
94
+ init: () => CommonHasher;
95
+ update: (data: IDataType) => CommonHasher;
96
+ digest: {
97
+ (outputType: "binary"): Uint8Array;
98
+ (outputType?: "hex"): string;
99
+ };
100
+ digestSize: number;
101
+ }
102
+
103
+ export interface MultiHasher {
104
+ multi: true;
105
+ update: (data: IDataType) => MultiHasher;
106
+ digest: {
107
+ (outputType: "binary"): { [key: string]: Uint8Array };
108
+ (outputType?: "hex"): { [key: string]: string };
109
+ };
110
+ }
111
+
112
+ export type OcflVersion = '1.0' | '1.1';
113
+
114
+ export type OcflExtensionConfig = { extensionName?: string;[key: string]: any };
115
+ type OcflStorageLayout = import('./extension').OcflStorageLayout<OcflExtensionConfig>;
116
+
117
+ /** Configuration options for an OCFL Object. */
118
+ export type OcflStorageConfig = {
119
+ /** Absolute path to the ocfl storage root. */
120
+ root: string;
121
+ /** Absolute path to storage workspace directory. */
122
+ workspace?: string;
123
+ /** A layout that identifies an arrangement of directories and OCFL objects under the storage root */
124
+ layout?: OcflStorageLayout | OcflExtensionConfig | string;
125
+ /** A default list of digest algorithm names to be added to the fixity block of objects under this storage. */
126
+ fixityAlgorithms?: string[];
127
+ /** Digest algorithm for content-addressing, must use either sha512 or sha256. Defaults to 'sha512'. */
128
+ digestAlgorithm?: 'sha256' | 'sha512';
129
+ /** Content directory name. Only applies to a newly created object. @defaultValue `"content"` */
130
+ contentDirectory?: string;
131
+ /** Ocfl version. Only applies to a newly created object. @defaultValue {@link OcflConstant.OCFL_VERSION} */
132
+ ocflVersion?: OcflVersion;
133
+ };
134
+
135
+ /** Configuration options for an OCFL Object. */
136
+ export type OcflObjectConfig = {
137
+ /** Absolute path to the ocfl object root. */
138
+ root: string;
139
+ /** Absolute path to object workspace directory. */
140
+ workspace?: string;
141
+ /** Identifier for the object. Only be used in a newly created object. */
142
+ id?: string;
143
+ /** Reference to existing extensions defined outside of the object, such as in the storage root. */
144
+ extensions?: import('./extension').OcflExtension<{}>[];
145
+ /** Additional digest algorithms to be calculated for each file and added to the fixity block. */
146
+ fixityAlgorithms?: string[];
147
+ /** Digest algorithm for content-addressing, must use either sha512 or sha256. Defaults to 'sha512'. */
148
+ digestAlgorithm?: 'sha256' | 'sha512';
149
+ /** Content directory name. Only applies to a newly created object. @defaultValue `"content"` */
150
+ contentDirectory?: string;
151
+ /** Ocfl version. Only applies to a newly created object. @defaultValue {@link OcflConstant.OCFL_VERSION} */
152
+ ocflVersion?: OcflVersion;
153
+ };
154
+