@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/index.js +72 -97
- package/index.mjs +3 -3
- package/lib/digest.js +245 -76
- package/lib/enum.js +73 -75
- package/lib/extension.js +4 -16
- package/lib/extensions/0003-hash-and-id-n-tuple-storage-layout.js +5 -3
- package/lib/extensions/0004-hashed-n-tuple-storage-layout.js +6 -4
- package/lib/extensions/0009-digest-algorithms.js +18 -0
- package/lib/extensions/index.js +1 -1
- package/lib/file.js +129 -0
- package/lib/index.js +0 -12
- package/lib/inventory.js +297 -136
- package/lib/object.js +293 -434
- package/lib/storage.js +26 -15
- package/lib/store.js +54 -16
- package/lib/transaction.js +177 -142
- package/lib/types.d.ts +110 -9
- package/lib/utils.js +52 -21
- package/lib/validation.js +1 -1
- package/package.json +16 -10
- package/lib/extensions/0001-digest-algorithms.js +0 -41
- package/lib/temp.js +0 -264
package/lib/enum.js
CHANGED
|
@@ -1,92 +1,90 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Enumeration tool for working with a set of options
|
|
3
|
-
* @module
|
|
3
|
+
* @module enumeration
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
// const COLOR = (c => {
|
|
7
|
+
// let r = /** @type {{[key in (typeof c)[number]]: string}} */({});
|
|
8
|
+
// return r;
|
|
9
|
+
// })(/** @type {const} */(['red', 'green', 'blue']));
|
|
10
|
+
// COLOR.blue
|
|
11
|
+
const constantsSymbol = Symbol('constants');
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
|
-
* Create enumeration type by
|
|
8
|
-
*
|
|
9
|
-
* @
|
|
14
|
+
* Create enumeration type by passing the list of constants as arguments of string
|
|
15
|
+
* @template {readonly string[]} const T
|
|
16
|
+
* @param {T} constants
|
|
10
17
|
*/
|
|
11
|
-
class Enum {
|
|
12
|
-
/** @type {string} */
|
|
13
|
-
#name;
|
|
14
|
-
/** @type {V} */
|
|
15
|
-
#value;
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
function enumeration(constants) {
|
|
20
|
+
class Enum {
|
|
21
|
+
/** @type {string} */
|
|
22
|
+
#name;
|
|
23
|
+
/** @type {number} */
|
|
24
|
+
#ordinal;
|
|
25
|
+
constructor(name, ordinal) {
|
|
26
|
+
this.#name = name;
|
|
27
|
+
this.#ordinal = ordinal;
|
|
28
|
+
//Object.defineProperty(this, 'name', { writable: false, enumerable: true, value: name });
|
|
29
|
+
//Object.defineProperty(this, 'ordinal', { writable: false, enumerable: true, value: ordinal });
|
|
25
30
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* @param {Enum} instance
|
|
31
|
-
*/
|
|
32
|
-
static has(instance) {
|
|
33
|
-
for (let name in this) {
|
|
34
|
-
if (this[name] === instance) return true;
|
|
31
|
+
get name() { return this.#name }
|
|
32
|
+
get ordinal() { return this.#ordinal }
|
|
33
|
+
toString() {
|
|
34
|
+
return this.name;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
static ofOrdinal(index, c=this) {
|
|
40
|
-
return c['_enums'][index];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static freeze(c=this) {
|
|
44
|
-
return Object.freeze(c);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
static size(c=this) {
|
|
48
|
-
return c['_enums'].length;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
static [Symbol.iterator]() {
|
|
52
|
-
return this['_enums'][Symbol.iterator]();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {V} [value]
|
|
57
|
-
*/
|
|
58
|
-
constructor(value) {
|
|
59
|
-
let c = this.constructor;
|
|
60
|
-
if (!Object.hasOwn(c, '_enums')) {
|
|
61
|
-
let p = Object.getPrototypeOf(c);
|
|
62
|
-
Object.defineProperty(c, '_enums', { value: p['_enums']?.slice(0) ?? [] });
|
|
36
|
+
/** @returns {Iterator<string>} */
|
|
37
|
+
static [Symbol.iterator]() {
|
|
38
|
+
return this[constantsSymbol][Symbol.iterator]();
|
|
63
39
|
}
|
|
64
|
-
this.#value = value;
|
|
65
|
-
this.ordinal = c['_enums'].length;
|
|
66
|
-
c['_enums'].push(this);
|
|
67
|
-
Object.freeze(this);
|
|
68
40
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return this.#name;
|
|
41
|
+
/** @typedef { typeof Enum & {[key in T[number]]: Enum} } EnumType */
|
|
42
|
+
let count = 0;
|
|
43
|
+
for (let c of constants) {
|
|
44
|
+
Object.defineProperty(Enum, c, {
|
|
45
|
+
value: Object.freeze(new Enum(c, count++)),
|
|
46
|
+
writable: false,
|
|
47
|
+
enumerable: true
|
|
48
|
+
});
|
|
81
49
|
}
|
|
50
|
+
Object.defineProperty(Enum, constantsSymbol, {
|
|
51
|
+
value: constants,
|
|
52
|
+
writable: false,
|
|
53
|
+
enumerable: false
|
|
54
|
+
});
|
|
55
|
+
return /** @type {EnumType} */(Object.freeze(Enum));
|
|
56
|
+
}
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Return an existing constant that represents the specified value
|
|
60
|
+
* @type {{<T extends abstract new() => any>(type: T, value: any): InstanceType<T>}}
|
|
61
|
+
*/
|
|
62
|
+
enumeration.of = function (type, value) {
|
|
63
|
+
if (value instanceof type && type[value.name]) return value;
|
|
64
|
+
else if (typeof value === 'string') {
|
|
65
|
+
return type[/**@type {string}*/(value)] || (() => {
|
|
66
|
+
for (let name in type) {
|
|
67
|
+
if (name.toUpperCase() === value.toUpperCase()) return type[name];
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
85
70
|
}
|
|
71
|
+
}
|
|
86
72
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Return an existing constant based on the index
|
|
75
|
+
* @template T
|
|
76
|
+
* @param {new() => T} type
|
|
77
|
+
* @param {number} index
|
|
78
|
+
* @return {T}
|
|
79
|
+
*/
|
|
80
|
+
enumeration.at = function (type, index) {
|
|
81
|
+
return type[type[constantsSymbol][index]];
|
|
82
|
+
}
|
|
90
83
|
|
|
84
|
+
enumeration.size = function (type) {
|
|
85
|
+
return type[constantsSymbol].length;
|
|
91
86
|
}
|
|
92
|
-
|
|
87
|
+
|
|
88
|
+
exports.enumeration = enumeration;
|
|
89
|
+
exports.default = { ...enumeration };
|
|
90
|
+
//export default { ...enumeration };
|
package/lib/extension.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
//const {} = require('./object');
|
|
2
|
-
//const digest = require('./digest');
|
|
3
|
-
|
|
4
1
|
const { NotImplementedError } = require("./error");
|
|
5
2
|
|
|
6
3
|
/** Index by Registered extension name */
|
|
@@ -10,8 +7,6 @@ const extensionByClassName = {};
|
|
|
10
7
|
|
|
11
8
|
const storageLayout = {};
|
|
12
9
|
|
|
13
|
-
/** @typedef {{ extensionName?: string;[key: string]: any; }} OcflExtensionConfig */
|
|
14
|
-
|
|
15
10
|
// class OcflExtensionConfig {
|
|
16
11
|
// /** User defined config */
|
|
17
12
|
// definedConfig;
|
|
@@ -20,6 +15,7 @@ const storageLayout = {};
|
|
|
20
15
|
// }
|
|
21
16
|
|
|
22
17
|
// }
|
|
18
|
+
/** @typedef {import('../index.js').StorageLayout} StorageLayout */
|
|
23
19
|
|
|
24
20
|
/**
|
|
25
21
|
* @template {{}} C
|
|
@@ -76,16 +72,6 @@ class OcflExtension {
|
|
|
76
72
|
extensionClass.setup(require('./index.js'));
|
|
77
73
|
}
|
|
78
74
|
|
|
79
|
-
/**
|
|
80
|
-
* @template {typeof OcflExtension} T
|
|
81
|
-
* @param {OcflExtensionConfig} [config]
|
|
82
|
-
* @this {T}
|
|
83
|
-
* @return {InstanceType<T>}
|
|
84
|
-
*/
|
|
85
|
-
static create2(config){
|
|
86
|
-
if (!config?.extensionName || this.NAME === config.extensionName) return /**@type {InstanceType<T>}*/(new this(config));
|
|
87
|
-
return OcflExtension.class(config.extensionName).create2(config);
|
|
88
|
-
}
|
|
89
75
|
/**
|
|
90
76
|
* Create an instance of the extension
|
|
91
77
|
* @type {<T extends typeof OcflExtension>(this: T, config?: OcflExtensionConfig) => InstanceType<T>}
|
|
@@ -99,9 +85,9 @@ class OcflExtension {
|
|
|
99
85
|
|
|
100
86
|
static get forObject() { return false; }
|
|
101
87
|
static get forStorage() { return false; }
|
|
102
|
-
static setup(ocfl) { };
|
|
103
88
|
static get NAME() { return '0000-example-extension' }
|
|
104
89
|
static get DESCRIPTION() { return 'Example extension' }
|
|
90
|
+
static async setup(ocfl) { };
|
|
105
91
|
|
|
106
92
|
/**
|
|
107
93
|
*
|
|
@@ -192,6 +178,8 @@ class OcflStorageLayout extends OcflExtension {
|
|
|
192
178
|
*/
|
|
193
179
|
constructor(config, defaultConfig) { super(config, defaultConfig); }
|
|
194
180
|
|
|
181
|
+
async validate() {}
|
|
182
|
+
|
|
195
183
|
/**
|
|
196
184
|
* Map an object identifier to a path
|
|
197
185
|
* @param {string} id - The identifier of the OCFL Object
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { OcflStorageLayout } = require("../extension");
|
|
2
2
|
const { OcflDigest } = require("../digest");
|
|
3
|
+
const { enumeration } = require("../enum");
|
|
3
4
|
const path = require("path");
|
|
4
5
|
|
|
5
6
|
function encodeIdentifier(id) {
|
|
@@ -31,10 +32,11 @@ class HashAndIdNTupleStorageLayout extends OcflStorageLayout {
|
|
|
31
32
|
constructor(config) {
|
|
32
33
|
super(config, DefaultConfig);
|
|
33
34
|
let c = this.parameters;
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const algo = enumeration.of(OcflDigest.FIXITY, c.digestAlgorithm)?.name;
|
|
36
|
+
if (!algo) throw new Error('Invalid digestAlgorithm');
|
|
37
|
+
let digestLength = OcflDigest.getHexDigestLength(algo);
|
|
36
38
|
let p = c.numberOfTuples * c.tupleSize
|
|
37
|
-
if (p >
|
|
39
|
+
if (p > digestLength) throw new Error('Product of numberOfTuples and tupleSize is greater than the number of characters in the hex encoded digest.');
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { OcflStorageLayout } = require("../extension");
|
|
2
2
|
const { OcflDigest } = require("../digest");
|
|
3
3
|
const path = require("path");
|
|
4
|
+
const { enumeration } = require("../enum");
|
|
4
5
|
|
|
5
6
|
const DefaultConfig = {
|
|
6
7
|
extensionName: '0004-hashed-n-tuple-storage-layout',
|
|
@@ -28,11 +29,12 @@ class HashedNTupleStorageLayout extends OcflStorageLayout {
|
|
|
28
29
|
constructor(config) {
|
|
29
30
|
super(config, DefaultConfig);
|
|
30
31
|
let c = this.parameters;
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const algo = enumeration.of(OcflDigest.FIXITY, c.digestAlgorithm)?.name;
|
|
33
|
+
if (!algo) throw new Error('Invalid digestAlgorithm');
|
|
34
|
+
let digestLength = OcflDigest.getHexDigestLength(algo);
|
|
33
35
|
let p = c.numberOfTuples * c.tupleSize
|
|
34
|
-
if (p >
|
|
35
|
-
else if (p ===
|
|
36
|
+
if (p > digestLength) throw new Error('Product of numberOfTuples and tupleSize is greater than the number of characters in the hex encoded digest.');
|
|
37
|
+
else if (p === digestLength && c.shortObjectRoot) throw new Error('shortObjectRoot cannot be set to true');
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
const {OcflExtension} = require("../extension");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Notes: this implementation does nothing because the 0009 extension is implemented directly in the OcflDigest class.
|
|
7
|
+
*/
|
|
8
|
+
class DigestAlgorithm extends OcflExtension {
|
|
9
|
+
static get NAME() { return '0009-digest-algorithms' }
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {import('ocfl')} ocfl
|
|
14
|
+
*/
|
|
15
|
+
static async setup(ocfl) {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { DigestAlgorithm };
|
package/lib/extensions/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
module.exports = {
|
|
3
|
-
...require('./
|
|
3
|
+
...require('./0009-digest-algorithms'),
|
|
4
4
|
...require('./0002-flat-direct-storage-layout'),
|
|
5
5
|
...require('./0003-hash-and-id-n-tuple-storage-layout'),
|
|
6
6
|
...require('./0004-hashed-n-tuple-storage-layout'),
|
package/lib/file.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} FileRef
|
|
3
|
+
* @property {string} [contentPath] - Actual path to the file location relative to the ocfl object root.
|
|
4
|
+
* @property {string} [logicalPath] - A path that represents a file’s location in the logical state of an object.
|
|
5
|
+
* @property {string} [digest] - An algorithmic characterization of the contents of a file conforming to a standard digest algorithm.
|
|
6
|
+
* @property {string} [version] - Version of the OCFL object.
|
|
7
|
+
* @property {string} [lastModified] - Last modified date of the file.
|
|
8
|
+
* @property {string} [size] - Size of the file in bytes.
|
|
9
|
+
* @property {Object.<string, string>} [fixity] - Fixity information of the file.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represent a file in an OCFL object
|
|
14
|
+
*/
|
|
15
|
+
class OcflObjectFile {
|
|
16
|
+
#ocflObject;
|
|
17
|
+
#lastModified;
|
|
18
|
+
#size;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {import('./object').OcflObject} ocflObject
|
|
23
|
+
* @param {FileRef} fileRef
|
|
24
|
+
*/
|
|
25
|
+
constructor(ocflObject, fileRef) {
|
|
26
|
+
this.#ocflObject = ocflObject;
|
|
27
|
+
// for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
|
|
28
|
+
// if (fileRef[key]) this[key] = fileRef[key];
|
|
29
|
+
// }
|
|
30
|
+
if (fileRef.logicalPath) this.logicalPath = fileRef.logicalPath;
|
|
31
|
+
if (fileRef.contentPath) this.contentPath = fileRef.contentPath;
|
|
32
|
+
if (fileRef.digest) this.digest = fileRef.digest;
|
|
33
|
+
if (fileRef.version) this.version = fileRef.version;
|
|
34
|
+
if (fileRef.lastModified != null) this.#lastModified = fileRef.lastModified;
|
|
35
|
+
if (fileRef.size != null) this.#size = fileRef.size;
|
|
36
|
+
if (fileRef.fixity != null) this.fixity = fileRef.fixity;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolve contentPath given either the `logicalPath` and `version`, `digest`, or `contentPath`.
|
|
41
|
+
*/
|
|
42
|
+
async #resolveContentPath() {
|
|
43
|
+
let contentPath = this.contentPath;
|
|
44
|
+
if (!contentPath) {
|
|
45
|
+
let inv = await this.#ocflObject.getInventory();
|
|
46
|
+
if (!inv) throw new Error(`OCFL Object "${this.#ocflObject.id}" does not exist`);
|
|
47
|
+
let version = !this.version || this.version === 'latest' ? inv.head : this.version;
|
|
48
|
+
let digest = this.digest || inv.getDigest(this.logicalPath, version);
|
|
49
|
+
contentPath = inv.getContentPath(digest);
|
|
50
|
+
if (!contentPath) throw new Error(`Cannot find content "${this.toString()}" in the OCFL Object "${this.#ocflObject.id}" version "${this.version || 'latest'}"`);
|
|
51
|
+
}
|
|
52
|
+
return contentPath;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the last modified time of the file, in millisecond since the UNIX epoch (January 1st, 1970 at Midnight).
|
|
57
|
+
*/
|
|
58
|
+
get lastModified() { return this.#lastModified }
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns the file size in bytes.
|
|
62
|
+
*/
|
|
63
|
+
get size() { return this.#size }
|
|
64
|
+
|
|
65
|
+
toString() {
|
|
66
|
+
let props = [];
|
|
67
|
+
for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
|
|
68
|
+
if (this[key]) props.push(key + ": '" + this[key] + "'");
|
|
69
|
+
}
|
|
70
|
+
return `{ ${props.join(', ')} }`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get the file content as a buffer
|
|
75
|
+
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
76
|
+
|
|
77
|
+
* @return {Promise<Buffer>}
|
|
78
|
+
*/
|
|
79
|
+
async buffer(options) {
|
|
80
|
+
return this.#ocflObject.readFile(await this.#resolveContentPath(), options);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @deprecated Use {@link buffer()}
|
|
85
|
+
*/
|
|
86
|
+
async asBuffer(options) {
|
|
87
|
+
return this.buffer(options);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the file content as a string
|
|
92
|
+
* @param {BufferEncoding} [encoding='utf8'] String encoding, default to utf8
|
|
93
|
+
* @return {Promise<string>}
|
|
94
|
+
*/
|
|
95
|
+
async text(encoding = 'utf8') {
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
return this.buffer(encoding);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* @deprecated Use {@link text()}
|
|
101
|
+
*/
|
|
102
|
+
async asString(encoding = 'utf8') {
|
|
103
|
+
// @ts-ignore
|
|
104
|
+
return this.text(encoding);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the file content as a NodeJS stream
|
|
109
|
+
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
110
|
+
* @return {Promise<ReadableStream>}
|
|
111
|
+
* @deprecated Use {@link stream()}
|
|
112
|
+
*/
|
|
113
|
+
async asStream(options) {
|
|
114
|
+
return this.#ocflObject.createReadable(await this.#resolveContentPath(), options);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the file content as a ES Streams standard ReadableStream
|
|
119
|
+
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
120
|
+
* @return {Promise<ReadableStream>}
|
|
121
|
+
*/
|
|
122
|
+
async stream(options) {
|
|
123
|
+
return this.#ocflObject.createReadable(await this.#resolveContentPath(), options);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
OcflObjectFile
|
|
129
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
1
|
// this is an internal index.js to avoid circular dependencies problem
|
|
2
|
-
// const { OcflStorage, OcflStorageImpl } = require('./storage.js');
|
|
3
|
-
// const { OcflObject,
|
|
4
|
-
// OcflObjectImpl,
|
|
5
|
-
// OcflObjectInventory,
|
|
6
|
-
// OcflObjectTransactionImpl,
|
|
7
|
-
// createObject,
|
|
8
|
-
// createObjectProxy,
|
|
9
|
-
// createTransactionProxy } = require('./object.js');
|
|
10
|
-
// const { OcflConstants } = require('./constants.js');
|
|
11
|
-
// const { OcflDigest } = require('./digest.js');
|
|
12
|
-
// const { OcflExtension } = require('./extension.js');
|
|
13
|
-
|
|
14
2
|
module.exports = {
|
|
15
3
|
...require('./constants.js'),
|
|
16
4
|
...require('./digest.js'),
|