@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/storage.js
CHANGED
|
@@ -5,19 +5,23 @@ const validation = require('./validation.js');
|
|
|
5
5
|
const { NotImplementedError } = require("./error");
|
|
6
6
|
const { OcflStorageLayout } = require("./extension");
|
|
7
7
|
const { HashedNTupleStorageLayout } = require("./extensions/0004-hashed-n-tuple-storage-layout");
|
|
8
|
-
const { OcflObject
|
|
8
|
+
const { OcflObject } = require("./object");
|
|
9
9
|
const { isDirEmpty, findNamasteVersion } = require("./utils.js");
|
|
10
10
|
const { OCFL_VERSION, OCFL_VERSIONS, OCFL_LAYOUT,
|
|
11
11
|
EXTENSIONS_DIR, EXTENSION_CONFIG,
|
|
12
12
|
NAMASTE_PREFIX_STORAGE, NAMASTE_PREFIX_OBJECT, NAMASTE_T } = require('./constants').OcflConstants;
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const { OcflDigest } = require('./digest');
|
|
15
14
|
|
|
16
15
|
const DEFAULT_LAYOUT = HashedNTupleStorageLayout;
|
|
17
16
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/** @typedef {import('./store').OcflStore} OcflStore */
|
|
18
|
+
/** @typedef {import('./types.js').OcflExtensionConfig} OcflExtensionConfig */
|
|
19
|
+
/** @typedef {import('./types.js').OcflVersion} OcflVersion */
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* An abstraction to represent the OCFL Storage Root, which is
|
|
24
|
+
* a base directory used to store OCFL Objects, identified by a [NAMASTE] file “0=ocfl_1.1”.
|
|
21
25
|
*/
|
|
22
26
|
|
|
23
27
|
class OcflStorage {
|
|
@@ -120,13 +124,13 @@ class OcflStorage {
|
|
|
120
124
|
* @property {('sha256' | 'sha512')} [digestAlgorithm] - Digest algorithm for content-addressing, must use either sha512 or sha256
|
|
121
125
|
* @property {string} [contentDirectory='content'] - Content directory name. Only applies to a newly created object.
|
|
122
126
|
* @property {string} [ocflVersion=c.OCFL_VERSION] - Ocfl version. Only applies to a newly created object.
|
|
127
|
+
* @property {string[]} [fixityAlgorithms] - A default list of digest algorithm names to be added to the fixity block of objects under this storage.
|
|
123
128
|
*/
|
|
124
129
|
|
|
125
130
|
/**
|
|
126
131
|
* General implementation of {@link OcflStorage} that can uses different datastore backends.
|
|
127
132
|
* This class provides common functionalities for the subclasses and
|
|
128
133
|
* at the same provide encapsulation emulating private and protected methods.
|
|
129
|
-
* @implements {OcflStorage}
|
|
130
134
|
*/
|
|
131
135
|
class OcflStorageImpl extends OcflStorage {
|
|
132
136
|
|
|
@@ -156,7 +160,9 @@ class OcflStorageImpl extends OcflStorage {
|
|
|
156
160
|
if (this.#workspace && this.#workspace.startsWith(this.#root + path.sep) && !this.#workspace.startsWith(this.#root + path.sep + 'extensions' + path.sep)) {
|
|
157
161
|
throw new Error('[OcflStorage] config.workspace cannot be the same as or a subpath of config.root');
|
|
158
162
|
}
|
|
159
|
-
|
|
163
|
+
if (layout) {
|
|
164
|
+
this.#layout = layout instanceof OcflStorageLayout ? layout : this.#createLayout(layout);
|
|
165
|
+
}
|
|
160
166
|
this.ocflVersion = ocflVersion || OCFL_VERSION;
|
|
161
167
|
this.#objectConfig = objectConfig;
|
|
162
168
|
}
|
|
@@ -192,15 +198,14 @@ class OcflStorageImpl extends OcflStorage {
|
|
|
192
198
|
if (typeof opt === 'string') id = opt;
|
|
193
199
|
else ({ id, root } = opt);
|
|
194
200
|
let relObjectRoot = root || this.objectRoot(id);
|
|
195
|
-
return new
|
|
201
|
+
return new OcflObject({
|
|
196
202
|
id,
|
|
197
203
|
root: path.join(this.root, relObjectRoot),
|
|
198
204
|
// @todo:use extension workspace if not defined
|
|
199
205
|
workspace: this.#workspace ? path.join(this.#workspace, relObjectRoot) : undefined,
|
|
200
|
-
ocflVersion: this.ocflVersion,
|
|
206
|
+
ocflVersion: /** @type {OcflVersion} */(this.ocflVersion),
|
|
201
207
|
...this.#objectConfig
|
|
202
|
-
},
|
|
203
|
-
this.#store);
|
|
208
|
+
}, this.#store);
|
|
204
209
|
}
|
|
205
210
|
|
|
206
211
|
async exists() {
|
|
@@ -229,7 +234,8 @@ class OcflStorageImpl extends OcflStorage {
|
|
|
229
234
|
} catch (error) {
|
|
230
235
|
}
|
|
231
236
|
this.#layout = this.#createLayout(layoutConfig || layoutName);
|
|
232
|
-
|
|
237
|
+
const layoutDigestAlgorithm = this.#layout.get('digestAlgorithm');
|
|
238
|
+
if (layoutDigestAlgorithm) await OcflDigest.initSync(layoutDigestAlgorithm);
|
|
233
239
|
//@todo: load storage level extensions
|
|
234
240
|
|
|
235
241
|
return this;
|
|
@@ -256,6 +262,8 @@ class OcflStorageImpl extends OcflStorage {
|
|
|
256
262
|
if (!this.#layout) {
|
|
257
263
|
this.#layout = new DEFAULT_LAYOUT();
|
|
258
264
|
}
|
|
265
|
+
const layoutDigestAlgorithm = this.#layout.get('digestAlgorithm');
|
|
266
|
+
if (layoutDigestAlgorithm) await OcflDigest.initSync(layoutDigestAlgorithm);
|
|
259
267
|
let layout = { extension: this.#layout.name, description: this.#layout.description };
|
|
260
268
|
await this.#store.writeFile(path.join(this.root, OCFL_LAYOUT), JSON.stringify(layout, null, 2), 'utf8');
|
|
261
269
|
if (this.#layout.config) {
|
|
@@ -269,6 +277,9 @@ class OcflStorageImpl extends OcflStorage {
|
|
|
269
277
|
return this;
|
|
270
278
|
}
|
|
271
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Allow async iteration over all OCFL objects in this storage.
|
|
282
|
+
*/
|
|
272
283
|
[Symbol.asyncIterator]() {
|
|
273
284
|
let store = this.#store;
|
|
274
285
|
let workspace = this.#workspace;
|
|
@@ -297,10 +308,10 @@ class OcflStorageImpl extends OcflStorage {
|
|
|
297
308
|
if (nv) {
|
|
298
309
|
//ocfl object exists
|
|
299
310
|
// inventory exists
|
|
300
|
-
let object = new
|
|
311
|
+
let object = new OcflObject({
|
|
301
312
|
root: basePath,
|
|
302
313
|
workspace: workspace ? path.join(workspace, path.relative(root, basePath)) : undefined,
|
|
303
|
-
ocflVersion: nv,
|
|
314
|
+
ocflVersion: /** @type {OcflVersion} */(nv),
|
|
304
315
|
...config
|
|
305
316
|
}, store);
|
|
306
317
|
//await object.load();
|
package/lib/store.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const {
|
|
3
|
-
const { parallelize, dataSourceAsIterable } = require('./utils');
|
|
2
|
+
const { parallelize, joinTypedArray } = require('./utils');
|
|
4
3
|
|
|
5
4
|
const emptyOptions = {};
|
|
6
5
|
|
|
@@ -9,6 +8,7 @@ const emptyOptions = {};
|
|
|
9
8
|
* such as local file system and cloud storage
|
|
10
9
|
*/
|
|
11
10
|
class OcflStore {
|
|
11
|
+
/** @type {Map<Object, OcflStore>} */
|
|
12
12
|
static instances;
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -52,6 +52,7 @@ class OcflStore {
|
|
|
52
52
|
* @param {string} filePath - Absolute path
|
|
53
53
|
* @param {*} options
|
|
54
54
|
* @return {Promise<import('fs').ReadStream>}
|
|
55
|
+
* @deprecated Use {@link createReadable()} instead
|
|
55
56
|
*/
|
|
56
57
|
async createReadStream(filePath, options) { throw new Error('Not Implemented'); }
|
|
57
58
|
|
|
@@ -60,30 +61,50 @@ class OcflStore {
|
|
|
60
61
|
* @param {string} relPath
|
|
61
62
|
* @param {*} options
|
|
62
63
|
* @return {Promise<import('fs').WriteStream>}
|
|
64
|
+
* @deprecated Use {@link createWritable()} instead
|
|
63
65
|
*/
|
|
64
66
|
async createWriteStream(relPath, options) { throw new Error('Not Implemented'); }
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Create a ReadableStream to get the content of a file
|
|
70
|
+
* @param {string} filePath - Absolute path
|
|
71
|
+
* @param {*} options
|
|
72
|
+
* @return {Promise<ReadableStream>}
|
|
73
|
+
*/
|
|
74
|
+
async createReadable(filePath, options) { throw new Error('Not Implemented'); }
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a WritableStream that writes data to {@link relPath}.
|
|
78
|
+
* @param {string} relPath
|
|
79
|
+
* @param {*} options
|
|
80
|
+
* @return {Promise<WritableStream>}
|
|
81
|
+
*/
|
|
82
|
+
async createWritable(relPath, options) { throw new Error('Not Implemented'); }
|
|
83
|
+
|
|
66
84
|
/**
|
|
67
85
|
* Provide a common interface to read a file inside an object.
|
|
68
86
|
* A concrete subclass MAY implement this method to provide read access to its underlying storage backend.
|
|
69
|
-
* The default implementation uses the {@linkcode OcflStore#
|
|
87
|
+
* The default implementation uses the {@linkcode OcflStore#createReadable} method.
|
|
70
88
|
* @param {string} filePath
|
|
71
89
|
* @param {*} [options]
|
|
72
90
|
*/
|
|
73
91
|
async readFile(filePath, options) {
|
|
74
|
-
|
|
75
|
-
let
|
|
92
|
+
const rs = await this.createReadable(filePath, options);
|
|
93
|
+
let buffer = (typeof options === 'string' || options.encoding) ? '' : new Uint8Array(new ArrayBuffer(0, { maxByteLength: 16 * 1024 }));
|
|
76
94
|
for await (const chunk of rs) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
if (typeof chunk === 'string') {
|
|
96
|
+
if (typeof buffer !== 'string') buffer = new TextDecoder().decode(buffer);
|
|
97
|
+
buffer += chunk;
|
|
98
|
+
} else {
|
|
99
|
+
if (typeof buffer === 'string') buffer = new TextEncoder().encode(buffer);
|
|
100
|
+
// @ts-ignore
|
|
101
|
+
buffer = joinTypedArray(buffer, chunk);
|
|
102
|
+
}
|
|
83
103
|
}
|
|
104
|
+
return buffer;
|
|
84
105
|
}
|
|
85
106
|
|
|
86
|
-
/** @typedef {string|Buffer|
|
|
107
|
+
/** @typedef {string|Buffer|Uint8Array|DataView|AsyncIterable|Iterable|ReadableStream} WriteFileData */
|
|
87
108
|
/**
|
|
88
109
|
* Provide a common interface to write a file inside an object.
|
|
89
110
|
* A concrete subclass MAY implement this method to provide write access to its underlying storage backend.
|
|
@@ -93,10 +114,15 @@ class OcflStore {
|
|
|
93
114
|
* @param {Object|string} options
|
|
94
115
|
*/
|
|
95
116
|
async writeFile(filePath, data, options) {
|
|
96
|
-
var source = dataSourceAsIterable(data);
|
|
97
117
|
await this.mkdir(path.dirname(filePath));
|
|
98
|
-
const target = await this.
|
|
99
|
-
|
|
118
|
+
const target = await this.createWritable(filePath, options);
|
|
119
|
+
if (data instanceof ReadableStream) {
|
|
120
|
+
await data.pipeTo(target);
|
|
121
|
+
} else {
|
|
122
|
+
const w = target.getWriter();
|
|
123
|
+
await w.write(data);
|
|
124
|
+
await w.close();
|
|
125
|
+
}
|
|
100
126
|
}
|
|
101
127
|
|
|
102
128
|
/**
|
|
@@ -106,7 +132,7 @@ class OcflStore {
|
|
|
106
132
|
* @param {string} target - destination filename of the copy operation
|
|
107
133
|
*/
|
|
108
134
|
async copyFile(source, target) {
|
|
109
|
-
await this.writeFile(target, await this.
|
|
135
|
+
await this.writeFile(target, await this.createReadable(source));
|
|
110
136
|
}
|
|
111
137
|
|
|
112
138
|
/**
|
|
@@ -125,6 +151,18 @@ class OcflStore {
|
|
|
125
151
|
*/
|
|
126
152
|
async readdir(filePath, options) { throw new Error('Not Implemented'); }
|
|
127
153
|
|
|
154
|
+
/**
|
|
155
|
+
* List all files contained in a directory including the metadata such as size and last modified date.
|
|
156
|
+
* The returned file.path is a relative path to the dirPath
|
|
157
|
+
* @param {string} dirPath
|
|
158
|
+
* @param {object} [options]
|
|
159
|
+
* @param {BufferEncoding} [options.encoding]
|
|
160
|
+
* @param {number} [options.bufferSize]
|
|
161
|
+
* @param {boolean} [options.recursive]
|
|
162
|
+
* @return {Promise<AsyncIterableIterator<{name: string, path: string, size: number, lastModified: Date}>> }
|
|
163
|
+
*/
|
|
164
|
+
async list(dirPath, { encoding='utf8', bufferSize=32, recursive=false} = {}) { throw new Error('Not Implemented'); }
|
|
165
|
+
|
|
128
166
|
/**
|
|
129
167
|
* Recursively create a directory if the storage backend supports it, otherwise do nothing
|
|
130
168
|
* @param {string} filePath - The directory path
|