@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 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} [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]
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
- /** @type {Object.<string, typeof OcflStorageLayout>} */
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 };
@@ -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 non-storage specific absolute path to the root of this OCFL Object
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.workspace = config.workspace ? path.resolve(config.workspace) : undefined;
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('corrupted file');
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
- let files = await this.#store.readdir(this.root);
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.1.2",
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
- "clean-jsdoc-theme": "^3.3.4",
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": [ "index.js", "index.mjs", "lib", "resources" ]
61
+ "files": [
62
+ "index.js",
63
+ "index.mjs",
64
+ "lib",
65
+ "resources"
66
+ ]
63
67
  }