@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/lib/utils.js CHANGED
@@ -6,24 +6,10 @@ const validation = require('./validation.js');
6
6
  const { OCFL_VERSIONS, NAMASTE_T } = require('./constants').OcflConstants;
7
7
 
8
8
 
9
- /**
10
- * @typedef {import('./store').OcflStore} OcflStore
11
- */
9
+ /** @typedef {import('./store').OcflStore} OcflStore */
10
+ /** @typedef {import('./types.js').OcflVersion} OcflVersion */
11
+
12
12
 
13
- /**
14
- *
15
- * @param {*} data
16
- */
17
- function dataSourceAsIterable(data) {
18
- let d = data;
19
- if (ArrayBuffer.isView(data) && data.buffer) {
20
- d = Buffer.from(data.buffer);
21
- }
22
- if (typeof data === 'string' || Buffer.isBuffer(data)) {
23
- d = [data];
24
- }
25
- return d;
26
- }
27
13
  //
28
14
  /**
29
15
  * @template T
@@ -69,7 +55,7 @@ async function isDirEmpty(store, dirPath) {
69
55
  * Check if there is any valid namaste in the rootPath and return the version number
70
56
  * @param {OcflStore} store
71
57
  * @param {string} prefix
72
- * @param {string} rootPath
58
+ * @param {string} rootPath
73
59
  */
74
60
  async function findNamasteVersion(store, prefix, rootPath) {
75
61
  let namastePath = path.join(rootPath, NAMASTE_T + prefix);
@@ -81,12 +67,57 @@ async function findNamasteVersion(store, prefix, rootPath) {
81
67
  } catch (error) {
82
68
  if (!error.errors || error.errors.some(e => e.code !== 'ENOENT')) throw error;
83
69
  }
84
- return version;
70
+ return /** @type {OcflVersion} */(version);
71
+ }
72
+
73
+ /** @typedef {ArrayLike<number> & { buffer: ArrayBuffer, byteLength: number, set(array: ArrayLike<number>, offset?: number): void }} TypedArray */
74
+ /**
75
+ * @param {any} obj
76
+ * @returns {obj is TypedArray}
77
+ */
78
+ function isTypedArray(obj) {
79
+ return ArrayBuffer.isView(obj) && !(obj instanceof DataView);
80
+ }
81
+
82
+ /**
83
+ *
84
+ * @param {ArrayBuffer|TypedArray} target
85
+ * @param {ArrayBuffer|TypedArray} source
86
+ * @returns {TypedArray}
87
+ */
88
+ function joinTypedArray(target, source) {
89
+ const sourceArray = isTypedArray(source) ? source : new Uint8Array(source);
90
+ const [targetBuffer, targetArray] = isTypedArray(target) ? [/** @type {ArrayBuffer}*/(target.buffer), target] : [target, new Uint8Array(target)];
91
+ const targetLength = targetArray.byteLength;
92
+ const totalSize = targetLength + sourceArray.byteLength;
93
+ let mergedArray = targetArray;
94
+ if (targetBuffer.resizable && targetBuffer.maxByteLength >= totalSize) {
95
+ targetBuffer.resize(totalSize);
96
+ } else {
97
+ mergedArray = new Uint8Array(totalSize);
98
+ mergedArray.set(targetArray, 0);
99
+ }
100
+ mergedArray.set(sourceArray, targetLength);
101
+ return mergedArray;
85
102
  }
86
103
 
104
+ /**
105
+ *
106
+ * @param {string} absOrRelPath
107
+ * @return {string}
108
+ */
109
+ // toAbsPath(absOrRelPath = '') {
110
+ // if (!path.isAbsolute(absOrRelPath)) return path.join(this.root, absOrRelPath);
111
+ // return absOrRelPath;
112
+ // }
113
+
114
+
115
+ const testSymbol = Symbol('testSymbol');
116
+
87
117
  module.exports = {
88
118
  parallelize,
89
- dataSourceAsIterable,
90
119
  isDirEmpty,
91
- findNamasteVersion
120
+ findNamasteVersion,
121
+ joinTypedArray,
122
+ testSymbol
92
123
  };
package/lib/validation.js CHANGED
@@ -136,7 +136,7 @@ function getWarning(id) {
136
136
 
137
137
  function genValObj(arr, id) {
138
138
  return {
139
- code: 'E' + ('' + id).padStart(3, 0),
139
+ code: 'E' + ('' + id).padStart(3, '0'),
140
140
  description: arr[id - 1]
141
141
  };
142
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ocfl/ocfl",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Oxford Common File Layout (OCFL) JS library",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "scripts": {
17
17
  "test": "mocha",
18
- "build-docs": "jsdoc -c jsdoc.json"
18
+ "build-docs": "typedoc --plugin typedoc-plugin-missing-exports"
19
19
  },
20
20
  "keywords": [
21
21
  "ocfl",
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "repository": {
27
27
  "type": "git",
28
- "url": "git+https://github.com/Arkisto-Platform/ocfl-js.git"
28
+ "url": "git+https://github.com/Language-Research-Technology/ocfl-js.git"
29
29
  },
30
30
  "author": "Alvin Sebastian (https://orcid.org/0000-0002-9086-1722)",
31
31
  "contributors": [
@@ -34,13 +34,16 @@
34
34
  ],
35
35
  "license": "GPL-3.0-or-later",
36
36
  "dependencies": {
37
- "blake2": "^4.1.1"
37
+ "hash-wasm": "^4.12.0",
38
+ "js-sha512": "^0.9.0"
38
39
  },
39
40
  "devDependencies": {
40
- "@types/mocha": "^9.1.1",
41
- "@types/node": "^17.0.33",
42
- "typedoc": "^0.23.15",
43
- "mocha": "^10.0.0"
41
+ "@types/mocha": "^10.0.10",
42
+ "@types/node": "^24",
43
+ "memfs": "^4.50.0",
44
+ "mocha": "^11.7.5",
45
+ "typedoc": "^0.28.15",
46
+ "typedoc-plugin-missing-exports": "^4.1.2"
44
47
  },
45
48
  "mocha": {
46
49
  "extension": [
@@ -51,9 +54,12 @@
51
54
  "recursive": true
52
55
  },
53
56
  "bugs": {
54
- "url": "https://github.com/Arkisto-Platform/ocfl-js/issues"
57
+ "url": "https://github.com/Language-Research-Technology/ocfl-js/issues"
58
+ },
59
+ "homepage": "https://github.com/Language-Research-Technology/ocfl-js#readme",
60
+ "engines": {
61
+ "node": ">=18.0.0"
55
62
  },
56
- "homepage": "https://github.com/Arkisto-Platform/ocfl-js#readme",
57
63
  "directories": {
58
64
  "lib": "lib",
59
65
  "test": "test"
@@ -1,41 +0,0 @@
1
- //@ts-check
2
-
3
- const {OcflExtension} = require("../extension");
4
- const crypto = require('crypto');
5
- const blake2 = require('blake2');
6
-
7
- class DigestAlgorithm extends OcflExtension {
8
- static get NAME() { return '0001-digest-algorithms' }
9
-
10
- // static create() {
11
- // if (!instance) instance = new DigestAlgorithm();
12
- // return instance;
13
- // }
14
- /**
15
- *
16
- * @param {import('ocfl')} ocfl
17
- */
18
- static setup(ocfl) {
19
- let {OcflDigest} = ocfl;
20
- let { FIXITY } = OcflDigest;
21
- class FIXITY_EX extends FIXITY {
22
- static blake2b160 = new this('blake2b-160');
23
- static blake2b256 = new this('blake2b-256');
24
- static blake2b384 = new this('blake2b-384');
25
- static ['sha512-256'] = new this('sha512/256');
26
- };
27
- OcflDigest.FIXITY = FIXITY_EX;
28
- //console.log(OcflDigest.FIXITY);
29
- for (let len of [160, 256, 384]) {
30
- OcflDigest.algorithms['blake2b-' + len] = function() {
31
- return blake2.createHash('blake2b', {digestLength: len/8});
32
- }
33
- }
34
- OcflDigest.algorithms['sha512/256'] = function() {
35
- return crypto.createHash('sha512-256');
36
- }
37
- //console.log(OcflDigest.algorithms);
38
- }
39
- }
40
-
41
- module.exports = { DigestAlgorithm };
package/lib/temp.js DELETED
@@ -1,264 +0,0 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const hasha = require('hasha');
4
- const uuidv5 = require('uuid/v5');
5
- const _ = require('lodash');
6
- const DIGEST_ALGORITHM = 'sha512';
7
-
8
- class OcflObject {
9
-
10
-
11
- constructor(path) {
12
- this.path = path;
13
- this.ocflVersion = '1.0';
14
- this.contentVersion = null; // No content yet
15
- this.id = null; // Not set yet
16
- this.DIGEST_ALGORITHM = DIGEST_ALGORITHM;
17
-
18
- }
19
-
20
- async writeInventories(inv, version) {
21
- const main_inv = await fs.writeJson(path.join(this.path, 'inventory.json'), inv, { spaces: 2 });
22
- const version_inv = await fs.writeJson(path.join(this.path, version, 'inventory.json'), inv, { spaces: 2 });
23
- const inv_hash = await this.hash_file(path.join(this.path, 'inventory.json'))
24
- const digest_file_v1 = await fs.writeFile(path.join(this.path, version, 'inventory.json.' + DIGEST_ALGORITHM), inv_hash + " inventory.json");
25
- const digest_file = await fs.writeFile(path.join(this.path, 'inventory.json.' + DIGEST_ALGORITHM), inv_hash + " inventory.json");
26
-
27
- }
28
-
29
- // addContent is passed an id and a callback - this takes one argument, the
30
- // directory into which content is to be written.
31
- // This is called for both new and merged versions: the incoming version is
32
- // written as v1 of a new object in the deposit directory, and if it's not the
33
- // first version it's then merged with the existing most recent version
34
-
35
- async addContent(id, writeContent) {
36
- // Copy files into v1
37
-
38
- const version = "v1" // Always a fresh start as we're not touching an existing repo object
39
- const versionPath = path.join(this.path, version, "content");
40
- await fs.ensureDir(versionPath);
41
- await writeContent(versionPath);
42
-
43
- // Make an inventory
44
- const inv = await this.inventory(id, versionPath);
45
-
46
- // Put the inventory in the root AND version dir
47
- await this.writeInventories(inv, version)
48
- this.contentVersion = await this.determineVersion();
49
- this.id = id;
50
- }
51
-
52
- // preserving the old interface here
53
-
54
- async importDir(id, sourceDir) {
55
- await this.addContent(id, async (targetDir) => {
56
- await fs.copy(sourceDir, targetDir);
57
- })
58
- }
59
-
60
-
61
- async create(path) {
62
- // Creates a blank object with a content dir but no content at <path>
63
- if (this.path) {
64
- throw new Error("This object has already been initialized at: " + this.path)
65
- }
66
- this.path = path;
67
- const stats = await fs.stat(this.path);
68
- if (await fs.pathExists(this.path) && stats.isDirectory()) {
69
- const readDir = await fs.readdir(this.path);
70
- if (readDir.length <= 0) { // empty so initialise an object here
71
- const generateNamaste = await this.generateNamaste(this.path, this.ocflVersion);
72
- } else {
73
- throw new Error('can\'t initialise an object here as there are already files')
74
- }
75
- } else {
76
- //else if it doesnt it dies
77
- throw new Error('directory does not exist');
78
- }
79
- }
80
-
81
- async load(path) {
82
- // Tries to load an existing object residing at <path>
83
- if (this.path) {
84
- throw new Error("This object has already been initialized at: " + this.path)
85
- }
86
- this.path = path;
87
- const stats = await fs.stat(this.path);
88
- if (await fs.pathExists(this.path) && stats.isDirectory()) {
89
-
90
- const ocflVersion = await this.isObject(this.path);
91
- if (!ocflVersion) {
92
- throw new Error('can\'t initialise an object here as there are already files')
93
- }
94
- } else {
95
- throw new Error(path + ' does not exist or is not a directory');
96
- }
97
- }
98
-
99
- getVersionString(i) {
100
- // Make a version name as per the SHOULD in the spec v1..vn
101
- // TODO have an option for zero padding
102
- return "v" + i
103
- }
104
-
105
- async getInventory() {
106
- const inventoryPath = path.join(this.path, "inventory.json");
107
- if (await fs.exists(inventoryPath)) {
108
- return await JSON.parse(fs.readFileSync(inventoryPath));
109
- }
110
- else {
111
- return null;
112
- }
113
- }
114
-
115
- async determineVersion() {
116
- const inv = await this.getInventory();
117
- if (inv) {
118
- return inv.head;
119
- }
120
- else {
121
- return null;
122
- }
123
- // Here's not how to do it:
124
- /* var version = 0;
125
- const dirContents = await fs.readdir(this.path);
126
- for (let f of dirContents.filter(function(d){return d.match(/^v\d+$/)})){
127
- const v = parseInt(f.replace("v",""));
128
- if (v > version) {
129
- version = v;
130
- }
131
- }
132
- return version;10. */
133
- // Look at each dir that matches v\d+
134
-
135
- }
136
-
137
- async isObject(aPath) {
138
- // TODO: Check if this content root with NAMASTE and returns ocfl version
139
- // 0=ocfl_object_1.0
140
- // looks at path and see if the content of the file is
141
- // TODO: Make this look for a namaste file beginning with 0=ocfl_object_ and extract the version
142
- const theFile = path.join(aPath, "0=" + this.nameVersion(this.ocflVersion));
143
- return await fs.pathExists(theFile);
144
- }
145
-
146
- nameVersion(version) {
147
- return 'ocfl_object_' + version;
148
- }
149
-
150
- async generateNamaste(aPath, version) {
151
- const fileName = '0=' + this.nameVersion(version);
152
- const thePath = path.join(aPath, fileName);
153
- const writeFile = await fs.writeFile(thePath, this.nameVersion(version));
154
- const contentDir = await fs.mkdir(path.join(aPath, "v1"));
155
- }
156
-
157
-
158
- async digest_dir(dir) {
159
- var items = {};
160
- const contents = await fs.readdir(dir);
161
- items = _.flatten(await Promise.all(contents.map(async (p1) => {
162
- const p = path.join(dir, p1);
163
- const stats = await fs.stat(p);
164
- if (stats.isDirectory()) {
165
- return await this.digest_dir(p);
166
- } else {
167
- const h = await this.hash_file(p);
168
- return [p, h];
169
- }
170
- })));
171
- return items;
172
- }
173
-
174
- async hash_file(p) {
175
- const hash = await hasha.fromFile(p, { algorithm: DIGEST_ALGORITHM })
176
- return hash;
177
- }
178
-
179
-
180
- async removeEmptyDirectories(folder) {
181
- // Remove empty directories
182
- // Adapted (nade async) from: https://gist.github.com/jakub-g/5903dc7e4028133704a4
183
- if (!folder) {
184
- folder = this.path;
185
- }
186
- const stats = await fs.stat(folder);
187
- var isDir = await stats.isDirectory();
188
- if (isDir) {
189
- var files = await fs.readdir(folder);
190
- if (files.length > 0) {
191
- for (let f of files) {
192
- var fullPath = path.join(folder, f);
193
- await this.removeEmptyDirectories(fullPath);
194
- }
195
- files = await fs.readdir(folder);
196
- }
197
- if (files.length == 0) {
198
- const rm = await fs.rmdir(folder);
199
- return;
200
- }
201
- }
202
- }
203
-
204
- async inventory(id, dir) {
205
- const versionId = "v1";
206
- const inv = {
207
- 'id': id,
208
- 'type': 'https://ocfl.io/1.0/spec/#inventory',
209
- 'digestAlgorithm': DIGEST_ALGORITHM,
210
- 'head': versionId,
211
- 'versions': {
212
-
213
- }
214
- };
215
- inv.versions[versionId] = {
216
- "created": new Date().toISOString(),
217
- "state": {}
218
- }
219
- // TODO Message and state keys in version
220
- var hashpairs = await this.digest_dir(dir);
221
- var versionState = inv.versions[versionId].state;
222
- inv['manifest'] = {};
223
- for (let i = 0; i < hashpairs.length; i += 2) {
224
- const thisHash = hashpairs[i + 1]
225
- const thisPath = path.relative(this.path, hashpairs[i])
226
- const versionPath = path.relative(path.join("v1", "content"), thisPath)
227
- if (!inv['manifest'][thisHash]) {
228
- //Store only ONE path
229
- inv['manifest'][thisHash] = [thisPath]
230
- }
231
- else {
232
- // TODO DELETE THIS FILE FROM THE OBJECT BEFORE WE STORE IT
233
- }
234
-
235
- if (versionState[thisHash]) {
236
- // Store all
237
- versionState[thisHash].push(versionPath)
238
- }
239
- else {
240
- versionState[thisHash] = [versionPath]
241
-
242
- }
243
-
244
- }
245
- return inv
246
- }
247
-
248
- async getFilePath(filePath, version) {
249
- const inv = await this.getInventory();
250
- if (!version) {
251
- version = inv.head;
252
- }
253
- const state = inv.versions[version].state;
254
- for (let hash of Object.keys(state)) {
255
- const aPath = state[hash];
256
- const findPath = _.find(aPath, (p) => p === filePath);
257
- if (findPath) {
258
- return inv.manifest[hash][0];
259
- }
260
- }
261
- throw new Error('cannot find file');
262
- }
263
-
264
- }