@ocfl/ocfl 0.1.2 → 0.2.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 +6 -5
- package/lib/extension.js +3 -3
- package/lib/extensions/000N-path-direct-storage-layout.js +61 -0
- package/lib/extensions/index.js +2 -1
- package/lib/object.js +34 -13
- package/lib/types.d.ts +9 -0
- package/package.json +11 -7
package/index.js
CHANGED
|
@@ -21,11 +21,12 @@ const { OcflConstants, OcflDigest, OcflExtension, OcflStorageLayout, OcflStore,
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* @typedef {Object} StorageLayout
|
|
24
|
-
* @property {typeof extensions.FlatDirectStorageLayout}
|
|
25
|
-
* @property {typeof extensions.FlatOmitPrefixStorageLayout}
|
|
26
|
-
* @property {typeof extensions.HashAndIdNTupleStorageLayout}
|
|
27
|
-
* @property {typeof extensions.HashedNTupleStorageLayout}
|
|
28
|
-
* @property {typeof extensions.NTupleOmitPrefixStorageLayout}
|
|
24
|
+
* @property {typeof extensions.FlatDirectStorageLayout} FlatDirectStorageLayout
|
|
25
|
+
* @property {typeof extensions.FlatOmitPrefixStorageLayout} FlatOmitPrefixStorageLayout
|
|
26
|
+
* @property {typeof extensions.HashAndIdNTupleStorageLayout} HashAndIdNTupleStorageLayout
|
|
27
|
+
* @property {typeof extensions.HashedNTupleStorageLayout} HashedNTupleStorageLayout
|
|
28
|
+
* @property {typeof extensions.NTupleOmitPrefixStorageLayout} NTupleOmitPrefixStorageLayout
|
|
29
|
+
* @property {typeof extensions.PathDirectStorageLayout} PathDirectStorageLayout
|
|
29
30
|
*/
|
|
30
31
|
/** @typedef {import('./lib/extension.js').OcflExtensionConfig} OcflExtensionConfig */
|
|
31
32
|
|
package/lib/extension.js
CHANGED
|
@@ -7,7 +7,7 @@ const { NotImplementedError } = require("./error");
|
|
|
7
7
|
const extensionByName = {};
|
|
8
8
|
/** Index by extension class name */
|
|
9
9
|
const extensionByClassName = {};
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
const storageLayout = {};
|
|
12
12
|
|
|
13
13
|
/** @typedef {{ extensionName?: string;[key: string]: any; }} OcflExtensionConfig */
|
|
@@ -110,7 +110,7 @@ class OcflExtension {
|
|
|
110
110
|
*/
|
|
111
111
|
constructor(config, defaultConfig) {
|
|
112
112
|
/** @type {C} */
|
|
113
|
-
this.parameters = defaultConfig ? Object.create(/**@type{object}*/(defaultConfig)) : {};
|
|
113
|
+
this.parameters = typeof defaultConfig === 'object' ? Object.create(/**@type{object}*/(defaultConfig)) : {};
|
|
114
114
|
if (typeof config === 'object') {
|
|
115
115
|
for (let k in config) {
|
|
116
116
|
if (k in defaultConfig) this.parameters[k] = config[k];
|
|
@@ -157,7 +157,7 @@ class OcflExtension {
|
|
|
157
157
|
class OcflStorageLayout extends OcflExtension {
|
|
158
158
|
|
|
159
159
|
static get layout() {
|
|
160
|
-
return storageLayout;
|
|
160
|
+
return /** @type StorageLayout */(storageLayout);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/**
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { OcflStorageLayout } = require("../extension");
|
|
2
|
+
const { OcflDigest } = require("../digest");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const DefaultConfig = {
|
|
6
|
+
extensionName: '000N-path-direct-storage-layout',
|
|
7
|
+
omitScheme: false,
|
|
8
|
+
/** @type {any} */
|
|
9
|
+
replace: [],
|
|
10
|
+
suffix: '/__object__'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** @typedef {Partial<typeof DefaultConfig>} PathDirectStorageLayoutConfig */
|
|
14
|
+
/**
|
|
15
|
+
* @extends {OcflStorageLayout<typeof DefaultConfig>}
|
|
16
|
+
*/
|
|
17
|
+
class PathDirectStorageLayout extends OcflStorageLayout {
|
|
18
|
+
static get NAME() { return DefaultConfig.extensionName; }
|
|
19
|
+
static get DESCRIPTION() {
|
|
20
|
+
return "OCFL object identifiers are used directly as the path.";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @param {PathDirectStorageLayoutConfig} [config]
|
|
26
|
+
*/
|
|
27
|
+
constructor(config) {
|
|
28
|
+
super(config, DefaultConfig);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} id
|
|
33
|
+
* @return {string}
|
|
34
|
+
*/
|
|
35
|
+
map(id) {
|
|
36
|
+
var suffix = this.parameters.suffix;
|
|
37
|
+
if (suffix && id.endsWith(suffix)) throw new Error(`Identifier cannot end with '${suffix}'`);
|
|
38
|
+
var p = id ?? '';
|
|
39
|
+
for (const r of this.parameters.replace) {
|
|
40
|
+
p = p.replace(r[0], r[1]);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
let u = new URL(p);
|
|
44
|
+
let parts = [];
|
|
45
|
+
if (!this.parameters.omitScheme && u.protocol !== 'file:') {
|
|
46
|
+
parts.push(u.protocol.replace(':', ''));
|
|
47
|
+
}
|
|
48
|
+
if (u.hostname) {
|
|
49
|
+
parts.push(u.hostname.replace(',', '_').replace(';', '/'));
|
|
50
|
+
}
|
|
51
|
+
p = path.join(parts.join('_'), u.pathname + u.search + u.hash);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
}
|
|
54
|
+
p = p.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
55
|
+
return path.join(p, this.parameters.suffix);
|
|
56
|
+
//if (/^\w+:/.test(id)) {
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { PathDirectStorageLayout };
|
package/lib/extensions/index.js
CHANGED
|
@@ -6,7 +6,8 @@ module.exports = {
|
|
|
6
6
|
...require('./0004-hashed-n-tuple-storage-layout'),
|
|
7
7
|
// ...require('./0005-mutable-head'),
|
|
8
8
|
...require('./0006-flat-omit-prefix-storage-layout'),
|
|
9
|
-
...require('./0007-n-tuple-omit-prefix-storage-layout')
|
|
9
|
+
...require('./0007-n-tuple-omit-prefix-storage-layout'),
|
|
10
|
+
...require('./000N-path-direct-storage-layout')
|
|
10
11
|
//...require('./0008-schema-registry')
|
|
11
12
|
};
|
|
12
13
|
//const { DigestAlgorithm } = require('./0001-digest-algorithms.js');
|
package/lib/object.js
CHANGED
|
@@ -123,13 +123,19 @@ class OcflObject {
|
|
|
123
123
|
* Identifier of the OCFL Object
|
|
124
124
|
* @return {string}
|
|
125
125
|
*/
|
|
126
|
-
get id() { throw new NotImplementedError() }
|
|
126
|
+
get id() { throw new NotImplementedError(); }
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
* The
|
|
129
|
+
* The absolute path to the root of this OCFL Object
|
|
130
130
|
* @return {string}
|
|
131
131
|
*/
|
|
132
|
-
get root() { throw new NotImplementedError() }
|
|
132
|
+
get root() { throw new NotImplementedError(); }
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The absolute path to the temporary workspace of this OCFL Object
|
|
136
|
+
* @return {string}
|
|
137
|
+
*/
|
|
138
|
+
get workspace() { throw new NotImplementedError(); }
|
|
133
139
|
|
|
134
140
|
toString() { return `OcflObject { root: ${this.root}, id: ${this.id} }`; }
|
|
135
141
|
|
|
@@ -140,7 +146,7 @@ class OcflObject {
|
|
|
140
146
|
* @param {string} [version] - Full name of the version, eg: v1, v2, v3
|
|
141
147
|
* @returns {Promise<OcflObjectInventory>}
|
|
142
148
|
*/
|
|
143
|
-
async getInventory(version = 'latest') { throw new NotImplementedError() }
|
|
149
|
+
async getInventory(version = 'latest') { throw new NotImplementedError(); }
|
|
144
150
|
|
|
145
151
|
/**
|
|
146
152
|
* Update the content files or directories as one transaction and commit the changes as a new version.
|
|
@@ -150,7 +156,7 @@ class OcflObject {
|
|
|
150
156
|
* @param {ObjectUpdateMode} [mode=UPDATE_MODE.MERGE]
|
|
151
157
|
* @return {Promise<?OcflObjectTransaction>}
|
|
152
158
|
*/
|
|
153
|
-
async update(updater, mode) { throw new NotImplementedError() }
|
|
159
|
+
async update(updater, mode) { throw new NotImplementedError(); }
|
|
154
160
|
|
|
155
161
|
/**
|
|
156
162
|
* Import one or more content files or directories to the object
|
|
@@ -225,7 +231,7 @@ class OcflObject {
|
|
|
225
231
|
* @type { GetFileByLogical & GetFileByOpt }
|
|
226
232
|
*/
|
|
227
233
|
// @ts-ignore
|
|
228
|
-
getFile = function (opt, version) { }
|
|
234
|
+
getFile = function (opt, version) { };
|
|
229
235
|
|
|
230
236
|
/**
|
|
231
237
|
* Count the number of files contained in this object
|
|
@@ -259,6 +265,8 @@ class OcflObjectImpl extends OcflObject {
|
|
|
259
265
|
/** @type {string} */
|
|
260
266
|
#root;
|
|
261
267
|
/** @type {string} */
|
|
268
|
+
#workspace;
|
|
269
|
+
/** @type {string} */
|
|
262
270
|
#id;
|
|
263
271
|
/** @type {OcflExtension[]} */
|
|
264
272
|
#extensions;
|
|
@@ -284,13 +292,14 @@ class OcflObjectImpl extends OcflObject {
|
|
|
284
292
|
this.digestAlgorithm = DIGEST.of(config.digestAlgorithm || 'sha512');
|
|
285
293
|
if (!this.digestAlgorithm) throw new Error('Invalid digest algorithm. Must be one of `sha256` or `sha512`.');
|
|
286
294
|
//this.fixityDigest = config.fixityDigest ?? this.digestAlgorithm;
|
|
287
|
-
this
|
|
295
|
+
this.#workspace = config.workspace ? path.resolve(config.workspace) : undefined;
|
|
288
296
|
this.contentDirectory = config.contentDirectory ?? 'content';
|
|
289
297
|
/** @type Object.<string, OcflObjectInventory> */
|
|
290
298
|
this._inventory = {};
|
|
291
299
|
}
|
|
292
300
|
get id() { return this._inventory?.latest?.id || this.#id; }
|
|
293
301
|
get root() { return this.#root; }
|
|
302
|
+
get workspace() { return this.#workspace; }
|
|
294
303
|
|
|
295
304
|
async getInventory(version = 'latest') {
|
|
296
305
|
if (!this._inventory[version]) {
|
|
@@ -325,7 +334,7 @@ class OcflObjectImpl extends OcflObject {
|
|
|
325
334
|
OcflDigest.digestAsync(data.digestAlgorithm, datastr)
|
|
326
335
|
]));
|
|
327
336
|
digest = digest.match(/[^\s]+/)?.[0];
|
|
328
|
-
if (digest !== actualDigest) throw new Error(
|
|
337
|
+
if (digest !== actualDigest) throw new Error(`Inventory file ${this.root}/${invPath} digest mismatch: recorded=${digest} actual=${actualDigest}`);
|
|
329
338
|
//return new OcflObjectInventory({ data, digest });
|
|
330
339
|
return new OcflObjectInventory(data);
|
|
331
340
|
}
|
|
@@ -374,13 +383,13 @@ class OcflObjectImpl extends OcflObject {
|
|
|
374
383
|
*/
|
|
375
384
|
readFile = async function (relPath, options) {
|
|
376
385
|
return this.#store.readFile(path.join(this.root, relPath), options);
|
|
377
|
-
}
|
|
386
|
+
};
|
|
378
387
|
|
|
379
388
|
getFile = function (opt, version) {
|
|
380
389
|
var obj = this;
|
|
381
390
|
var opt_ = typeof opt === 'string' ? { logicalPath: opt, version: version } : opt;
|
|
382
391
|
return new OcflObjectFile(this, opt_);
|
|
383
|
-
}
|
|
392
|
+
};
|
|
384
393
|
|
|
385
394
|
// async _resolveContentPath({ logicalPath = '', contentPath = '', digest = '', version = '' }) {
|
|
386
395
|
// if (!contentPath) {
|
|
@@ -478,6 +487,7 @@ class OcflObjectImpl extends OcflObject {
|
|
|
478
487
|
// workspace dir exists, abort update
|
|
479
488
|
throw new Error('Uncommitted changes detected. Object is being updated by other process or there has been a failed update attempt');
|
|
480
489
|
}
|
|
490
|
+
await this._validateObjectPath();
|
|
481
491
|
let createdDir = await this.#store.mkdir(workspacePath);
|
|
482
492
|
if (!this.workspace) await this._ensureNamaste();
|
|
483
493
|
return createTransactionProxy(new OcflObjectTransactionImpl(this, this.#store, inventory, workspaceVersionPath, createdDir));
|
|
@@ -492,16 +502,27 @@ class OcflObjectImpl extends OcflObject {
|
|
|
492
502
|
return;
|
|
493
503
|
}
|
|
494
504
|
// namaste not found, check if object root is not empty
|
|
505
|
+
let files;
|
|
495
506
|
try {
|
|
496
|
-
|
|
497
|
-
if (files.length > 0) throw new Error('Cannot create an OCFL Object in a non-empty directory');
|
|
507
|
+
files = await this.#store.readdir(this.root);
|
|
498
508
|
} catch (error) {
|
|
499
509
|
}
|
|
510
|
+
if (files && files.length > 0) throw new Error('Cannot create an OCFL Object in a non-empty directory');
|
|
500
511
|
//create the namaste file
|
|
501
512
|
let filePath = path.join(this.root, NAMASTE_T + prefix + this.ocflVersion);
|
|
502
513
|
await this.#store.writeFile(filePath, prefix + this.ocflVersion + '\n', 'utf8');
|
|
503
514
|
}
|
|
504
515
|
|
|
516
|
+
// all the parent directories cannot be an ocfl object
|
|
517
|
+
async _validateObjectPath() {
|
|
518
|
+
var p = this.root;
|
|
519
|
+
while (p !== '.' && p !== '/' ) {
|
|
520
|
+
p = path.dirname(p);
|
|
521
|
+
let nv = await findNamasteVersion(this.#store, NAMASTE_PREFIX_OBJECT, p);
|
|
522
|
+
if (nv) throw new Error(`Object root cannot be nested under another object ${p}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
505
526
|
async isObject(aPath) { }
|
|
506
527
|
getVersionString(i) { }
|
|
507
528
|
async determineVersion() { }
|
|
@@ -540,7 +561,7 @@ const proxyHandler = {
|
|
|
540
561
|
}
|
|
541
562
|
}
|
|
542
563
|
}
|
|
543
|
-
}
|
|
564
|
+
};
|
|
544
565
|
|
|
545
566
|
/**
|
|
546
567
|
* Return any OcflObject implementation as OcflObject.
|
package/lib/types.d.ts
CHANGED
|
@@ -42,3 +42,12 @@ interface ReadFile {
|
|
|
42
42
|
| BufferEncoding
|
|
43
43
|
): Promise<string>;
|
|
44
44
|
}
|
|
45
|
+
|
|
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
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ocfl/ocfl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Oxford Common File Layout (OCFL) JS library",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
@@ -33,11 +33,13 @@
|
|
|
33
33
|
"Peter Sefton (https://orcid.org/0000-0002-3545-944X)"
|
|
34
34
|
],
|
|
35
35
|
"license": "GPL-3.0-or-later",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"blake2": "^4.1.1"
|
|
38
|
+
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"@types/mocha": "^9.1.1",
|
|
38
41
|
"@types/node": "^17.0.33",
|
|
39
|
-
"
|
|
40
|
-
"jsdoc": "^3.6.10",
|
|
42
|
+
"typedoc": "^0.23.15",
|
|
41
43
|
"mocha": "^10.0.0"
|
|
42
44
|
},
|
|
43
45
|
"mocha": {
|
|
@@ -48,9 +50,6 @@
|
|
|
48
50
|
],
|
|
49
51
|
"recursive": true
|
|
50
52
|
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"blake2": "^4.1.1"
|
|
53
|
-
},
|
|
54
53
|
"bugs": {
|
|
55
54
|
"url": "https://github.com/Arkisto-Platform/ocfl-js/issues"
|
|
56
55
|
},
|
|
@@ -59,5 +58,10 @@
|
|
|
59
58
|
"lib": "lib",
|
|
60
59
|
"test": "test"
|
|
61
60
|
},
|
|
62
|
-
"files": [
|
|
61
|
+
"files": [
|
|
62
|
+
"index.js",
|
|
63
|
+
"index.mjs",
|
|
64
|
+
"lib",
|
|
65
|
+
"resources"
|
|
66
|
+
]
|
|
63
67
|
}
|