@ocfl/ocfl 0.1.0 → 0.1.1
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/index.js +11 -0
- package/lib/extensions/0003-hash-and-id-n-tuple-storage-layout.js +5 -5
- package/lib/inventory.js +17 -7
- package/lib/object.js +140 -99
- package/lib/storage.js +3 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -108,8 +108,19 @@ class Ocfl {
|
|
|
108
108
|
storage(config, storeOptions = this.#defaultOptions) {
|
|
109
109
|
return new OcflStorageImpl(config, this.#store.getInstance(storeOptions));
|
|
110
110
|
}
|
|
111
|
+
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
class Storage {
|
|
115
|
+
async create(config) {
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
async load(config) {
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
113
124
|
module.exports = {
|
|
114
125
|
OcflConstants,
|
|
115
126
|
OcflDigest,
|
|
@@ -28,8 +28,8 @@ class HashAndIdNTupleStorageLayout extends OcflStorageLayout {
|
|
|
28
28
|
/**
|
|
29
29
|
* @param {Partial<typeof DefaultConfig>} [config]
|
|
30
30
|
*/
|
|
31
|
-
|
|
32
|
-
super(config);
|
|
31
|
+
constructor(config) {
|
|
32
|
+
super(config, DefaultConfig);
|
|
33
33
|
let c = this.parameters;
|
|
34
34
|
if (!OcflDigest.FIXITY.of(c.digestAlgorithm)) throw new Error('Invalid digestAlgorithm');
|
|
35
35
|
let digest = OcflDigest.digestSync(c.digestAlgorithm, 'test');
|
|
@@ -42,10 +42,10 @@ class HashAndIdNTupleStorageLayout extends OcflStorageLayout {
|
|
|
42
42
|
* @return {string}
|
|
43
43
|
*/
|
|
44
44
|
map(id) {
|
|
45
|
-
let digest = OcflDigest.digestSync(this.
|
|
45
|
+
let digest = OcflDigest.digestSync(this.parameters.digestAlgorithm, id);
|
|
46
46
|
let segments = [];
|
|
47
|
-
let s = this.
|
|
48
|
-
let n = this.
|
|
47
|
+
let s = this.parameters.tupleSize;
|
|
48
|
+
let n = this.parameters.numberOfTuples;
|
|
49
49
|
let i;
|
|
50
50
|
for (i = 0; i < n; ++i) {
|
|
51
51
|
segments.push(digest.slice(s * i, s * i + s));
|
package/lib/inventory.js
CHANGED
|
@@ -155,13 +155,20 @@ class OcflObjectInventory {
|
|
|
155
155
|
* @param {string} [version] - The version name
|
|
156
156
|
*/
|
|
157
157
|
*files(version) {
|
|
158
|
-
let state
|
|
158
|
+
let state;
|
|
159
|
+
if (!version || version === 'latest') {
|
|
160
|
+
state = this.state;
|
|
161
|
+
version = this.head;
|
|
162
|
+
} else {
|
|
163
|
+
state = this.versions[version]?.state;
|
|
164
|
+
}
|
|
159
165
|
for (let digest in state) {
|
|
160
166
|
let files = state[digest];
|
|
161
167
|
let contentPath = this.getContentPath(digest);
|
|
162
168
|
for (let logicalPath of files) {
|
|
163
169
|
yield {
|
|
164
170
|
logicalPath,
|
|
171
|
+
version,
|
|
165
172
|
digest,
|
|
166
173
|
contentPath
|
|
167
174
|
};
|
|
@@ -283,14 +290,13 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
283
290
|
if (mf && mf.length > 0) {
|
|
284
291
|
// this is a reinstatement
|
|
285
292
|
} else {
|
|
286
|
-
|
|
287
|
-
// this is an update, remove old digest
|
|
288
|
-
this.delete(logicalPath);
|
|
289
|
-
} else {
|
|
290
|
-
// normal addition
|
|
291
|
-
}
|
|
293
|
+
// normal addition
|
|
292
294
|
this.manifest[digest] = [path.join(this.head, this.contentDirectory, logicalPath)];
|
|
293
295
|
}
|
|
296
|
+
if (this.#byPath[logicalPath]) {
|
|
297
|
+
// this is an update, remove old digest
|
|
298
|
+
this.delete(logicalPath);
|
|
299
|
+
}
|
|
294
300
|
state[digest] = [logicalPath];
|
|
295
301
|
}
|
|
296
302
|
this.#byPath[logicalPath] = digest;
|
|
@@ -350,6 +356,10 @@ class OcflObjectInventoryMut extends OcflObjectInventory {
|
|
|
350
356
|
}
|
|
351
357
|
}
|
|
352
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Delete a file
|
|
361
|
+
* @param {string} logicalPath
|
|
362
|
+
*/
|
|
353
363
|
delete(logicalPath) {
|
|
354
364
|
let prefix = logicalPath.endsWith('/') ? logicalPath : logicalPath + '/';
|
|
355
365
|
let names = this.getDigest(logicalPath) ? [logicalPath] :
|
package/lib/object.js
CHANGED
|
@@ -11,6 +11,7 @@ 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');
|
|
14
15
|
const { OCFL_VERSION, OCFL_VERSIONS, INVENTORY_NAME, NAMASTE_PREFIX_OBJECT, NAMASTE_T } = require('./constants').OcflConstants;
|
|
15
16
|
|
|
16
17
|
class UPDATE_MODE extends Enum {
|
|
@@ -31,18 +32,86 @@ const DIGEST = OcflDigest.CONTENT;
|
|
|
31
32
|
* @property {string} [ocflVersion=c.OCFL_VERSION] - Ocfl version. Only applies to a newly created object.
|
|
32
33
|
* @property {OcflExtension[]} [extensions] - Reference to existing extensions defined outside of the object, such as in the storage root.
|
|
33
34
|
*/
|
|
34
|
-
/**
|
|
35
|
-
* @typedef {{logicalPath: string; digest: string; contentPath: string;}} FileRef
|
|
36
|
-
*/
|
|
37
35
|
/**
|
|
38
36
|
* @typedef {UPDATE_MODE | ('MERGE' | 'REPLACE') } ObjectUpdateMode
|
|
37
|
+
*
|
|
38
|
+
* @typedef {{logicalPath?: string, digest?: string, contentPath?: string, version?: string}} FileRef
|
|
39
|
+
* @typedef {{logicalPath: string, version?: string}} FileRefLogical
|
|
40
|
+
* @typedef {{digest: string}} FileRefDigest
|
|
41
|
+
* @typedef {{contentPath: string}} FileRefContent
|
|
39
42
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
43
|
+
|
|
44
|
+
class OcflObjectFile {
|
|
45
|
+
#ocflObject;
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param {OcflObjectImpl} ocflObject
|
|
49
|
+
* @param {FileRef} fileRef
|
|
50
|
+
*/
|
|
51
|
+
constructor(ocflObject, fileRef) {
|
|
52
|
+
this.#ocflObject = ocflObject;
|
|
53
|
+
// for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
|
|
54
|
+
// if (fileRef[key]) this[key] = fileRef[key];
|
|
55
|
+
// }
|
|
56
|
+
if (fileRef.logicalPath) this.logicalPath = fileRef.logicalPath;
|
|
57
|
+
if (fileRef.contentPath) this.contentPath = fileRef.contentPath;
|
|
58
|
+
if (fileRef.digest) this.digest = fileRef.digest;
|
|
59
|
+
if (fileRef.version) this.version = fileRef.version;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve contentPath given either the `logicalPath` and `version`, `digest`, or `contentPath`.
|
|
64
|
+
*/
|
|
65
|
+
async #resolveContentPath() {
|
|
66
|
+
let contentPath = this.contentPath;
|
|
67
|
+
if (!contentPath) {
|
|
68
|
+
let inv = await this.#ocflObject.getInventory();
|
|
69
|
+
let version = !this.version || this.version === 'latest' ? inv.head : this.version;
|
|
70
|
+
let digest = this.digest || inv.getDigest(this.logicalPath, version);
|
|
71
|
+
contentPath = inv.getContentPath(digest);
|
|
72
|
+
if (!contentPath) throw new Error(`Cannot find content "${this.toString()}" in the OCFL Object "${this.#ocflObject.id}" version "${this.version || 'latest'}"`);
|
|
73
|
+
}
|
|
74
|
+
return contentPath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
toString() {
|
|
78
|
+
let o = {};
|
|
79
|
+
for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
|
|
80
|
+
if (this[key]) o[key] = this[key];
|
|
81
|
+
}
|
|
82
|
+
return format('%O', o);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the file content as a buffer
|
|
87
|
+
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
88
|
+
|
|
89
|
+
* @return {Promise<Buffer>}
|
|
90
|
+
*/
|
|
91
|
+
async asBuffer(options) {
|
|
92
|
+
return this.#ocflObject.readFile(await this.#resolveContentPath(), options);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get the file content as a string
|
|
97
|
+
* @param {BufferEncoding} [encoding='utf8'] String encoding, default to utf8
|
|
98
|
+
* @return {Promise<string>}
|
|
99
|
+
*/
|
|
100
|
+
async asString(encoding = 'utf8') {
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
return this.asBuffer(encoding);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the file content as a stream
|
|
107
|
+
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
108
|
+
* @return {Promise<NodeJS.ReadableStream>}
|
|
109
|
+
*/
|
|
110
|
+
async asStream(options) {
|
|
111
|
+
return this.#ocflObject.createReadStream(await this.#resolveContentPath(), options);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
46
115
|
|
|
47
116
|
/**
|
|
48
117
|
* Interface class representing OCFL Object
|
|
@@ -66,8 +135,6 @@ class OcflObject {
|
|
|
66
135
|
|
|
67
136
|
toJSON() { return { root: this.root, id: this.id }; }
|
|
68
137
|
|
|
69
|
-
async inventory(id, dir) { }
|
|
70
|
-
|
|
71
138
|
/**
|
|
72
139
|
* If a cache exists, return the cached inventory. Otherwise read it from the storage and cache it.
|
|
73
140
|
* @param {string} [version] - Full name of the version, eg: v1, v2, v3
|
|
@@ -124,6 +191,7 @@ class OcflObject {
|
|
|
124
191
|
}
|
|
125
192
|
|
|
126
193
|
async load() {
|
|
194
|
+
this._inventory = {};
|
|
127
195
|
await this.getInventory();
|
|
128
196
|
//await this.loadExtensions();
|
|
129
197
|
return;
|
|
@@ -131,47 +199,32 @@ class OcflObject {
|
|
|
131
199
|
|
|
132
200
|
/**
|
|
133
201
|
* Iterate through the content files contained in the specified version.
|
|
134
|
-
* Returns an array of logical
|
|
202
|
+
* Returns an array of FileRef which consists of logical path, content path, and digest, in no particular order
|
|
135
203
|
* @param {string} [version]
|
|
136
|
-
* @return {Promise<Generator<
|
|
204
|
+
* @return {Promise<Generator<OcflObjectFile, void, unknown>>}
|
|
137
205
|
*/
|
|
138
206
|
async files(version) { throw new Error('Not Implemented'); }
|
|
139
207
|
|
|
140
208
|
/**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
* @param {*} [opt.options] - Options to be passed to underlying fs
|
|
148
|
-
* @return {Promise<Buffer>}
|
|
149
|
-
*/
|
|
150
|
-
async getAsBuffer(opt) { throw new Error('Not Implemented'); }
|
|
151
|
-
|
|
209
|
+
* Get a file by its logical path and version. If version is omitted, it defaults to the last version.
|
|
210
|
+
* @callback GetFileByLogical
|
|
211
|
+
* @param {string} logicalPath Logical path of the file, eg: 'test'
|
|
212
|
+
* @param {string} [version='latest'] The object version name, eg: 'v1', 'v2'. Default to 'latest'
|
|
213
|
+
* @returns {OcflObjectFile}
|
|
214
|
+
*/
|
|
152
215
|
/**
|
|
153
|
-
* Get
|
|
154
|
-
* @
|
|
155
|
-
* @param {
|
|
156
|
-
*
|
|
157
|
-
* @
|
|
158
|
-
* @param {string} [opt.version]
|
|
159
|
-
* @param {BufferEncoding} [opt.encoding]
|
|
160
|
-
* @return {Promise<string>}
|
|
216
|
+
* Get a file by either the [logical path and version], digest, or content path.
|
|
217
|
+
* @callback GetFileByOpt
|
|
218
|
+
* @param { FileRefLogical | FileRefDigest | FileRefContent } opt A choice of logical path (eg: 'test') and version (eg: 'v1'),
|
|
219
|
+
* digest of the file content, or content path (eg: 'v1/content/test')
|
|
220
|
+
* @returns {OcflObjectFile}
|
|
161
221
|
*/
|
|
162
|
-
async getAsString(opt) { throw new Error('Not Implemented'); }
|
|
163
|
-
|
|
164
222
|
/**
|
|
165
|
-
*
|
|
166
|
-
* @
|
|
167
|
-
* @param {string} [opt.logicalPath] - Logical path of the file, eg: `test`
|
|
168
|
-
* @param {string} [opt.contentPath] - Content path of the file, eg: `v1/content/test`
|
|
169
|
-
* @param {string} [opt.digest] - Digest of the file content
|
|
170
|
-
* @param {string} [opt.version] - Version name
|
|
171
|
-
* @param {*} [opt.options] - Options to be passed to underlying fs
|
|
172
|
-
* @return {Promise<NodeJS.ReadableStream>}
|
|
223
|
+
* Retrieve the content of a file either as string, buffer, or stream.
|
|
224
|
+
* @type { GetFileByLogical & GetFileByOpt }
|
|
173
225
|
*/
|
|
174
|
-
|
|
226
|
+
// @ts-ignore
|
|
227
|
+
getFile = function (opt, version) { }
|
|
175
228
|
|
|
176
229
|
/**
|
|
177
230
|
* Count the number of files contained in this object
|
|
@@ -187,7 +240,9 @@ class OcflObject {
|
|
|
187
240
|
* The existing directory may or may not be a valid OCFL Object.
|
|
188
241
|
* @return {Promise<boolean>}
|
|
189
242
|
*/
|
|
190
|
-
|
|
243
|
+
async exists() { throw new Error('Not Implemented'); }
|
|
244
|
+
|
|
245
|
+
//async inventory(version = 'latest') { }
|
|
191
246
|
|
|
192
247
|
}
|
|
193
248
|
|
|
@@ -298,18 +353,8 @@ class OcflObjectImpl extends OcflObject {
|
|
|
298
353
|
return t;
|
|
299
354
|
}
|
|
300
355
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
* @param {*} param0
|
|
304
|
-
* @returns
|
|
305
|
-
*/
|
|
306
|
-
async _resolveContentPath({ logicalPath = '', contentPath = '', digest = '', version = '' }) {
|
|
307
|
-
if (!contentPath) {
|
|
308
|
-
let inv = await this.getInventory();
|
|
309
|
-
version = version || inv.head;
|
|
310
|
-
contentPath = inv.getContentPath(digest || inv.getDigest(logicalPath, version));
|
|
311
|
-
}
|
|
312
|
-
return contentPath;
|
|
356
|
+
async createReadStream(filePath, options) {
|
|
357
|
+
return this.#store.createReadStream(path.join(this.root, filePath), options);
|
|
313
358
|
}
|
|
314
359
|
|
|
315
360
|
/**
|
|
@@ -321,54 +366,44 @@ class OcflObjectImpl extends OcflObject {
|
|
|
321
366
|
}} ReadFileFn
|
|
322
367
|
*/
|
|
323
368
|
/**
|
|
324
|
-
* Read a file inside an object.
|
|
325
|
-
* @
|
|
369
|
+
* Read a file inside an object.
|
|
370
|
+
* @type {ReadFileFn} abc
|
|
371
|
+
* @param {string} relPath - The file path relative to the object root.
|
|
326
372
|
* @param options - Options to be passed to the underlying method
|
|
327
|
-
* @type ReadFileFn
|
|
328
373
|
*/
|
|
329
|
-
readFile = async function (
|
|
330
|
-
return this.#store.readFile(path.join(this.root,
|
|
374
|
+
readFile = async function (relPath, options) {
|
|
375
|
+
return this.#store.readFile(path.join(this.root, relPath), options);
|
|
331
376
|
}
|
|
332
377
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
* @param {string} [opt.contentPath]
|
|
338
|
-
* @param {string} [opt.digest]
|
|
339
|
-
* @param {string} [opt.version]
|
|
340
|
-
* @param {*} [opt.options]
|
|
341
|
-
*/
|
|
342
|
-
async getAsBuffer(opt) {
|
|
343
|
-
let p = await this._resolveContentPath(opt);
|
|
344
|
-
if (!p) throw new Error(`Cannot find content "${opt.logicalPath || opt.contentPath || opt.digest}" in the OCFL Object "${this.#id}" version "${opt.version || 'latest'}"`);
|
|
345
|
-
return this.readFile(p, opt.options);
|
|
378
|
+
getFile = function (opt, version) {
|
|
379
|
+
var obj = this;
|
|
380
|
+
var opt_ = typeof opt === 'string' ? { logicalPath: opt, version: version } : opt;
|
|
381
|
+
return new OcflObjectFile(this, opt_);
|
|
346
382
|
}
|
|
347
383
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
384
|
+
// async _resolveContentPath({ logicalPath = '', contentPath = '', digest = '', version = '' }) {
|
|
385
|
+
// if (!contentPath) {
|
|
386
|
+
// let inv = await this.getInventory();
|
|
387
|
+
// version = version && version !== 'latest' ? version : inv.head;
|
|
388
|
+
// contentPath = inv.getContentPath(digest || inv.getDigest(logicalPath, version));
|
|
389
|
+
// }
|
|
390
|
+
// return contentPath;
|
|
391
|
+
// }
|
|
392
|
+
|
|
393
|
+
// async getAsBuffer(opt) {
|
|
394
|
+
// let p = await this._resolveContentPath(opt);
|
|
395
|
+
// if (!p) throw new Error(`Cannot find content "${opt.logicalPath || opt.contentPath || opt.digest}" in the OCFL Object "${this.#id}" version "${opt.version || 'latest'}"`);
|
|
396
|
+
// return this.readFile(p, opt.options);
|
|
397
|
+
// }
|
|
398
|
+
// async getAsString(opt) {
|
|
399
|
+
// // @ts-ignore
|
|
400
|
+
// return this.getAsBuffer({ ...opt, options: opt.encoding || 'utf8' });
|
|
401
|
+
// }
|
|
402
|
+
// async getAsStream(opt) {
|
|
403
|
+
// let p = await this._resolveContentPath(opt);
|
|
404
|
+
// if (!p) throw new Error(`Cannot find content "${opt.logicalPath || opt.contentPath || opt.digest}" in the OCFL Object "${this.#id}" version "${opt.version || 'latest'}"`);
|
|
405
|
+
// return this.createReadStream(p, opt.options);
|
|
406
|
+
// }
|
|
372
407
|
|
|
373
408
|
/**
|
|
374
409
|
* Copy all content of this Object to a directory in local filesystem
|
|
@@ -385,7 +420,13 @@ class OcflObjectImpl extends OcflObject {
|
|
|
385
420
|
*/
|
|
386
421
|
async files(version) {
|
|
387
422
|
let inv = await this.getInventory();
|
|
388
|
-
|
|
423
|
+
let o = this;
|
|
424
|
+
function* files() {
|
|
425
|
+
for (const f of inv.files(version)) {
|
|
426
|
+
yield new OcflObjectFile(o, f);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return files();
|
|
389
430
|
}
|
|
390
431
|
|
|
391
432
|
|
package/lib/storage.js
CHANGED
|
@@ -21,7 +21,8 @@ const DEFAULT_LAYOUT = HashedNTupleStorageLayout;
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
class OcflStorage {
|
|
24
|
-
static LAYOUT;
|
|
24
|
+
//static LAYOUT;
|
|
25
|
+
ocflVersion;
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Get the storage root
|
|
@@ -100,6 +101,7 @@ class OcflStorage {
|
|
|
100
101
|
async delete(id) {
|
|
101
102
|
return this.remove(id);
|
|
102
103
|
}
|
|
104
|
+
|
|
103
105
|
async purge(id) {
|
|
104
106
|
return this.remove(id);
|
|
105
107
|
}
|