@ocfl/ocfl 0.2.2 → 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 -77
- 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 +294 -101
- package/lib/object.js +287 -492
- package/lib/storage.js +23 -14
- package/lib/store.js +26 -18
- package/lib/transaction.js +177 -142
- package/lib/types.d.ts +75 -16
- package/lib/utils.js +52 -21
- package/lib/validation.js +1 -1
- package/package.json +11 -8
- package/lib/extensions/0001-digest-algorithms.js +0 -46
- package/lib/temp.js +0 -264
package/lib/object.js
CHANGED
|
@@ -3,317 +3,58 @@
|
|
|
3
3
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const validation = require('./validation.js');
|
|
6
|
-
const {
|
|
7
|
-
const { OcflObjectInventory, OcflObjectInventoryMut } = require('./inventory.js');
|
|
6
|
+
const { enumeration } = require('./enum.js');
|
|
7
|
+
const { OcflObjectInventory, OcflObjectInventoryMut, VersionNumber } = require('./inventory.js');
|
|
8
8
|
const { OcflDigest } = require('./digest.js');
|
|
9
|
-
const {
|
|
9
|
+
const { OcflObjectTransaction, OcflObjectTransactionImpl } = require('./transaction.js');
|
|
10
10
|
const { OcflStore } = require('./store.js');
|
|
11
11
|
const { OcflExtension } = require('./extension.js');
|
|
12
12
|
const { NotImplementedError } = require('./error.js');
|
|
13
|
-
const { parallelize,
|
|
13
|
+
const { parallelize, isDirEmpty, findNamasteVersion } = require('./utils');
|
|
14
|
+
const { OcflObjectFile } = require('./file.js');
|
|
14
15
|
const { OCFL_VERSION, OCFL_VERSIONS, INVENTORY_NAME, NAMASTE_PREFIX_OBJECT, NAMASTE_T } = require('./constants').OcflConstants;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
static MERGE = new this();
|
|
19
|
-
/** New version will assume that all existing files are removed first before adding new ones */
|
|
20
|
-
static REPLACE = new this();
|
|
21
|
-
};
|
|
17
|
+
const UPDATE_MODES = /** @type { const } */(['MERGE', 'REPLACE']);
|
|
18
|
+
const UPDATE_MODE = enumeration(UPDATE_MODES);
|
|
22
19
|
const DIGEST = OcflDigest.CONTENT;
|
|
23
20
|
|
|
24
21
|
/**
|
|
25
|
-
* @typedef {
|
|
26
|
-
* @
|
|
27
|
-
* @
|
|
28
|
-
* @property {('sha256' | 'sha512')} [digestAlgorithm] - Digest algorithm for content-addressing, must use either sha512 or sha256.
|
|
29
|
-
* @property {string} [id] - Identifier for the object. Only be used in a newly created object.
|
|
30
|
-
* @property {string} [contentDirectory='content'] - Content directory name. Only applies to a newly created object.
|
|
31
|
-
* @property {string} [ocflVersion=c.OCFL_VERSION] - Ocfl version. Only applies to a newly created object.
|
|
32
|
-
* @property {OcflExtension[]} [extensions] - Reference to existing extensions defined outside of the object, such as in the storage root.
|
|
33
|
-
*/
|
|
34
|
-
/**
|
|
35
|
-
* @typedef {UPDATE_MODE | ('MERGE' | 'REPLACE') } ObjectUpdateMode
|
|
36
|
-
*
|
|
37
|
-
* @typedef {Object} FileRef
|
|
38
|
-
* @property {string} [contentPath] - Actual path to the file location relative to the ocfl object root.
|
|
39
|
-
* @property {string} [logicalPath] - A path that represents a file’s location in the logical state of an object.
|
|
40
|
-
* @property {string} [digest] - An algorithmic characterization of the contents of a file conforming to a standard digest algorithm.
|
|
41
|
-
* @property {string} [version] - Version of the OCFL object.
|
|
42
|
-
* @property {string} [lastModified] - Last modified date of the file.
|
|
43
|
-
* @property {string} [size] - Size of the file in bytes.
|
|
22
|
+
* @typedef {(typeof UPDATE_MODES)[number]} UpdateModeStr
|
|
23
|
+
* @typedef { InstanceType<typeof UPDATE_MODE> | UpdateModeStr} UpdateMode
|
|
24
|
+
* @typedef {string} LogicalPath
|
|
44
25
|
* @typedef {{logicalPath: string, version?: string}} FileRefLogical
|
|
45
|
-
* @typedef {{digest: string}} FileRefDigest
|
|
46
|
-
* @typedef {{contentPath: string}} FileRefContent
|
|
26
|
+
* @typedef {{digest: string, version?: string}} FileRefDigest
|
|
27
|
+
* @typedef {{contentPath: string, version?: string}} FileRefContent
|
|
28
|
+
* @typedef {import('./types').Inventory} Inventory
|
|
29
|
+
* @typedef {import('./types').OcflVersion} OcflVersion
|
|
30
|
+
* @typedef {import('./types').OcflObjectConfig} OcflObjectConfig
|
|
31
|
+
*
|
|
47
32
|
*/
|
|
48
33
|
|
|
49
34
|
/**
|
|
50
|
-
*
|
|
51
|
-
*/
|
|
52
|
-
class OcflObjectFile {
|
|
53
|
-
#ocflObject;
|
|
54
|
-
#lastModified;
|
|
55
|
-
#size;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
*
|
|
59
|
-
* @param {OcflObjectImpl} ocflObject
|
|
60
|
-
* @param {FileRef} fileRef
|
|
61
|
-
*/
|
|
62
|
-
constructor(ocflObject, fileRef) {
|
|
63
|
-
this.#ocflObject = ocflObject;
|
|
64
|
-
// for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
|
|
65
|
-
// if (fileRef[key]) this[key] = fileRef[key];
|
|
66
|
-
// }
|
|
67
|
-
if (fileRef.logicalPath) this.logicalPath = fileRef.logicalPath;
|
|
68
|
-
if (fileRef.contentPath) this.contentPath = fileRef.contentPath;
|
|
69
|
-
if (fileRef.digest) this.digest = fileRef.digest;
|
|
70
|
-
if (fileRef.version) this.version = fileRef.version;
|
|
71
|
-
if (fileRef.lastModified != null) this.#lastModified = fileRef.lastModified;
|
|
72
|
-
if (fileRef.size != null) this.#size = fileRef.size;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Resolve contentPath given either the `logicalPath` and `version`, `digest`, or `contentPath`.
|
|
77
|
-
*/
|
|
78
|
-
async #resolveContentPath() {
|
|
79
|
-
let contentPath = this.contentPath;
|
|
80
|
-
if (!contentPath) {
|
|
81
|
-
let inv = await this.#ocflObject.getInventory();
|
|
82
|
-
if (!inv) throw new Error(`OCFL Object "${this.#ocflObject.id}" does not exist`);
|
|
83
|
-
let version = !this.version || this.version === 'latest' ? inv.head : this.version;
|
|
84
|
-
let digest = this.digest || inv.getDigest(this.logicalPath, version);
|
|
85
|
-
contentPath = inv.getContentPath(digest);
|
|
86
|
-
if (!contentPath) throw new Error(`Cannot find content "${this.toString()}" in the OCFL Object "${this.#ocflObject.id}" version "${this.version || 'latest'}"`);
|
|
87
|
-
}
|
|
88
|
-
return contentPath;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Returns the last modified time of the file, in millisecond since the UNIX epoch (January 1st, 1970 at Midnight).
|
|
93
|
-
*/
|
|
94
|
-
get lastModified() { return this.#lastModified }
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Returns the file size in bytes.
|
|
98
|
-
*/
|
|
99
|
-
get size() { return this.#size }
|
|
100
|
-
|
|
101
|
-
toString() {
|
|
102
|
-
let props = [];
|
|
103
|
-
for (const key in ['logicalPath', 'version', 'digest', 'contentPath']) {
|
|
104
|
-
if (this[key]) props.push(key + ": '" + this[key] + "'");
|
|
105
|
-
}
|
|
106
|
-
return `{ ${props.join(', ')} }`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get the file content as a buffer
|
|
111
|
-
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
112
|
-
|
|
113
|
-
* @return {Promise<Buffer>}
|
|
114
|
-
*/
|
|
115
|
-
async buffer(options) {
|
|
116
|
-
return this.#ocflObject.readFile(await this.#resolveContentPath(), options);
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* @deprecated
|
|
120
|
-
*/
|
|
121
|
-
async asBuffer(options) {
|
|
122
|
-
return this.buffer(options);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Get the file content as a string
|
|
127
|
-
* @param {BufferEncoding} [encoding='utf8'] String encoding, default to utf8
|
|
128
|
-
* @return {Promise<string>}
|
|
129
|
-
*/
|
|
130
|
-
async text(encoding = 'utf8') {
|
|
131
|
-
// @ts-ignore
|
|
132
|
-
return this.buffer(encoding);
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* @deprecated
|
|
136
|
-
*/
|
|
137
|
-
async asString(encoding = 'utf8') {
|
|
138
|
-
// @ts-ignore
|
|
139
|
-
return this.text(encoding);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Get the file content as a NodeJS stream
|
|
144
|
-
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
145
|
-
* @return {Promise<NodeJS.ReadableStream>}
|
|
146
|
-
* @deprecated
|
|
147
|
-
*/
|
|
148
|
-
async asStream(options) {
|
|
149
|
-
return this.#ocflObject.createReadStream(await this.#resolveContentPath(), options);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Get the file content as a ES Streams standard ReadableStream
|
|
154
|
-
* @param {*} [options] Options to be passed to underlying data store specific implementation
|
|
155
|
-
* @return {Promise<ReadableStream>}
|
|
156
|
-
*/
|
|
157
|
-
async stream(options) {
|
|
158
|
-
return this.#ocflObject.createReadable(await this.#resolveContentPath(), options);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Interface class representing OCFL Object
|
|
165
|
-
* @interface
|
|
35
|
+
* A class representing an OCFL Object allowing to read and write the content of an OCFL Object in the underlying store.
|
|
166
36
|
*/
|
|
167
37
|
class OcflObject {
|
|
168
38
|
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
* @return {string}
|
|
172
|
-
*/
|
|
173
|
-
get id() { throw new NotImplementedError(); }
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* The absolute path to the root of this OCFL Object
|
|
177
|
-
* @return {string}
|
|
178
|
-
*/
|
|
179
|
-
get root() { throw new NotImplementedError(); }
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* The absolute path to the temporary workspace of this OCFL Object
|
|
183
|
-
* @return {string}
|
|
184
|
-
*/
|
|
185
|
-
get workspace() { throw new NotImplementedError(); }
|
|
186
|
-
|
|
187
|
-
toString() { return `OcflObject { root: ${this.root}, id: ${this.id} }`; }
|
|
188
|
-
|
|
189
|
-
toJSON() { return { root: this.root, id: this.id }; }
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* If a cache exists, return the cached inventory. Otherwise read it from the storage and cache it.
|
|
193
|
-
* @param {string} [version] - Full name of the version, eg: v1, v2, v3
|
|
194
|
-
* @returns {Promise<OcflObjectInventory>}
|
|
195
|
-
*/
|
|
196
|
-
async getInventory(version = 'latest') { throw new NotImplementedError(); }
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Update the content files or directories as one transaction and commit the changes as a new version.
|
|
200
|
-
* If callback function `cb` is provided, all update operations can be done in the callback function
|
|
201
|
-
* and all changes are automatically commited at the end of the function.
|
|
202
|
-
* @param {function(OcflObjectTransaction):*} [updater]
|
|
203
|
-
* @param {ObjectUpdateMode} [mode=UPDATE_MODE.MERGE]
|
|
204
|
-
* @return {Promise<?OcflObjectTransaction>}
|
|
205
|
-
*/
|
|
206
|
-
async update(updater, mode) { throw new NotImplementedError(); }
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Import one or more content files or directories to the object
|
|
210
|
-
* in one transaction and commit the changes as a new version.
|
|
211
|
-
* Use this method to simplify copying all contents of a directory
|
|
212
|
-
* in local filesystem to the OCFL Object content directory
|
|
213
|
-
* @param {string|string[]|string[][]} content - The source path(s) or an array of a tuple [source, logical path]
|
|
214
|
-
* @param {ObjectUpdateMode} [mode=UPDATE_MODE.MERGE] - Update mode.
|
|
215
|
-
*/
|
|
216
|
-
async import(content, mode = UPDATE_MODE.MERGE) {
|
|
217
|
-
//let entries = /**@type {string[][]}*/(content);
|
|
218
|
-
if (typeof content === 'string') content = [[content, '']];
|
|
219
|
-
//else if (typeof content[0] === 'string') entries = /**@type {string[]}*/(content).map(src => [src, '']);
|
|
220
|
-
|
|
221
|
-
await this.update(async (t) => {
|
|
222
|
-
let res = await parallelize(/**@type {string[][]}*/(content), async (p) => {
|
|
223
|
-
if (typeof p === 'string') p = [p, ''];
|
|
224
|
-
let [source, target] = p;
|
|
225
|
-
return t.import(source, target);
|
|
226
|
-
});
|
|
227
|
-
let errors = [];
|
|
228
|
-
for (let i = 0; i < res.length; ++i) {
|
|
229
|
-
if (res[i] instanceof Error) errors.push(content[i][0]);
|
|
230
|
-
}
|
|
231
|
-
if (errors.length) {
|
|
232
|
-
let msg = "Cannot add the files: " + errors.join(', ');
|
|
233
|
-
throw new Error(msg);
|
|
234
|
-
}
|
|
235
|
-
}, mode);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Alias for import
|
|
240
|
-
* @see {@link OcflObject.import}
|
|
241
|
-
*/
|
|
242
|
-
async add(sourceDir, mode = UPDATE_MODE.MERGE) {
|
|
243
|
-
await this.import(sourceDir, mode);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async load() { throw new NotImplementedError(); }
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Iterate through the content files contained in the specified version.
|
|
250
|
-
* Returns an Iterator that contains FileRef which consists of logical path, content path, and digest, in no particular order.
|
|
251
|
-
* The iterator is implemented as a generator.
|
|
252
|
-
* @param {string} [version]
|
|
253
|
-
* @return {Promise<Generator<OcflObjectFile, void, unknown>>}
|
|
254
|
-
*/
|
|
255
|
-
async files(version) { throw new Error('Not Implemented'); }
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Get a file by its logical path and version. If version is omitted, it defaults to the last version.
|
|
259
|
-
* @callback GetFileByLogical
|
|
260
|
-
* @param {string} logicalPath Logical path of the file, eg: 'test'
|
|
261
|
-
* @param {string} [version='latest'] The object version name, eg: 'v1', 'v2'. Default to 'latest'
|
|
262
|
-
* @returns {OcflObjectFile}
|
|
263
|
-
*/
|
|
264
|
-
/**
|
|
265
|
-
* Get a file by either the [logical path and version], digest, or content path.
|
|
266
|
-
* @callback GetFileByOpt
|
|
267
|
-
* @param { FileRefLogical | FileRefDigest | FileRefContent } opt A choice of logical path (eg: 'test') and version (eg: 'v1'),
|
|
268
|
-
* digest of the file content, or content path (eg: 'v1/content/test')
|
|
269
|
-
* @returns {OcflObjectFile}
|
|
270
|
-
*/
|
|
271
|
-
/**
|
|
272
|
-
* Retrieve the content of a file either as string, buffer, or stream.
|
|
273
|
-
* @type { GetFileByLogical & GetFileByOpt }
|
|
274
|
-
*/
|
|
275
|
-
// @ts-ignore
|
|
276
|
-
getFile = function (opt, version) { };
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Count the number of files contained in this object
|
|
280
|
-
* @param {string} [version]
|
|
281
|
-
*/
|
|
282
|
-
async count(version) {
|
|
283
|
-
return [...await this.files(version)].length;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Check if the object root path points to an existing file or non-empty directory
|
|
288
|
-
* in the underlying backend store.
|
|
289
|
-
* The existing directory may or may not be a valid OCFL Object.
|
|
290
|
-
* @return {Promise<boolean>}
|
|
291
|
-
*/
|
|
292
|
-
async exists() { throw new Error('Not Implemented'); }
|
|
293
|
-
|
|
294
|
-
//async inventory(version = 'latest') { }
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Abstract class implementing {@link OcflObject}.
|
|
301
|
-
* This class provides common functionalities for the subclasses and
|
|
302
|
-
* at the same provide encapsulation emulating private and protected methods.
|
|
303
|
-
* @implements {OcflObject}
|
|
304
|
-
*/
|
|
305
|
-
class OcflObjectImpl extends OcflObject {
|
|
306
|
-
|
|
39
|
+
/** @type {OcflVersion} */
|
|
40
|
+
#ocflVersion;
|
|
307
41
|
/** @type {string} */
|
|
308
42
|
#root;
|
|
309
43
|
/** @type {string} */
|
|
310
44
|
#workspace;
|
|
311
45
|
/** @type {string} */
|
|
312
46
|
#id;
|
|
47
|
+
/** @type {('sha256' | 'sha512')} */
|
|
48
|
+
#digestAlgorithm;
|
|
313
49
|
/** @type {OcflExtension[]} */
|
|
314
50
|
#extensions;
|
|
315
51
|
/** @type {OcflStore} */
|
|
316
52
|
#store;
|
|
53
|
+
/**
|
|
54
|
+
* A default list of digest algorithms to be calculated for each file in this object and added to the fixity block.
|
|
55
|
+
* @type {string[]}
|
|
56
|
+
*/
|
|
57
|
+
fixityAlgorithms;
|
|
317
58
|
|
|
318
59
|
/**
|
|
319
60
|
* Create a new OCFL Object
|
|
@@ -321,38 +62,101 @@ class OcflObjectImpl extends OcflObject {
|
|
|
321
62
|
* @param {OcflStore} store
|
|
322
63
|
*/
|
|
323
64
|
constructor(config, store) {
|
|
324
|
-
super();
|
|
325
65
|
if (!store) throw new TypeError('[OcflObject] store is required.');
|
|
326
66
|
this.#store = store;
|
|
327
67
|
if (!config.root) throw new TypeError('[OcflObject] config.root is required.');
|
|
328
68
|
if (config.root === config.workspace) throw new Error('[OcflObject] config.root and config.workspace must be different.');
|
|
329
69
|
this.#root = path.resolve(config.root);
|
|
330
|
-
this
|
|
70
|
+
this.#ocflVersion = config.ocflVersion || /** @type {OcflVersion} */(OCFL_VERSION);
|
|
331
71
|
//this.contentVersion = null; // No content yet
|
|
332
72
|
this.#id = config.id;
|
|
333
73
|
this.#extensions = config.extensions;
|
|
334
|
-
this.digestAlgorithm = DIGEST.of(config.digestAlgorithm || 'sha512');
|
|
335
|
-
if (!this.digestAlgorithm) throw new Error('Invalid digest algorithm. Must be one of `sha256` or `sha512`.');
|
|
336
74
|
//this.fixityDigest = config.fixityDigest ?? this.digestAlgorithm;
|
|
75
|
+
this.digestAlgorithm = config.digestAlgorithm || 'sha512';
|
|
337
76
|
this.#workspace = config.workspace ? path.resolve(config.workspace) : undefined;
|
|
338
77
|
this.contentDirectory = config.contentDirectory ?? 'content';
|
|
339
78
|
/** @type Object.<string, OcflObjectInventory> */
|
|
340
79
|
this._inventory = {};
|
|
80
|
+
this.fixityAlgorithms = config.fixityAlgorithms;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** the digest algorithm used to identify files within the OCFL object */
|
|
84
|
+
get digestAlgorithm() { return this.#digestAlgorithm; }
|
|
85
|
+
set digestAlgorithm(algorithm) {
|
|
86
|
+
this.#digestAlgorithm = algorithm;
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
this.#digestAlgorithm = enumeration.of(DIGEST, algorithm || 'sha512')?.name;
|
|
89
|
+
if (!this.#digestAlgorithm) throw new Error('Invalid digest algorithm. Must be one of `sha256` or `sha512`.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Identifier of the OCFL Object
|
|
94
|
+
* @return {string}
|
|
95
|
+
*/
|
|
96
|
+
get id() { return this.inventory?.id || this.#id; }
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Latest version number of the OCFL Object
|
|
100
|
+
* @return {VersionNumber}
|
|
101
|
+
*/
|
|
102
|
+
get headVersion() {
|
|
103
|
+
const head = this.inventory?.head;
|
|
104
|
+
if (head) return VersionNumber.fromString(head);
|
|
341
105
|
}
|
|
342
|
-
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* All available version numbers in the OCFL Object
|
|
109
|
+
* @return {VersionNumber[]}
|
|
110
|
+
*/
|
|
111
|
+
get versions() {
|
|
112
|
+
return Object.keys(this.inventory?.versions || {}).map(v => VersionNumber.fromString(v));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The absolute path to the root of this OCFL Object
|
|
117
|
+
* @return {string}
|
|
118
|
+
*/
|
|
343
119
|
get root() { return this.#root; }
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The absolute path to the temporary workspace of this OCFL Object
|
|
123
|
+
* @return {string}
|
|
124
|
+
*/
|
|
344
125
|
get workspace() { return this.#workspace; }
|
|
345
126
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
127
|
+
/** The raw OCFL version number the object adheres to , eg 1.0, 1.1) */
|
|
128
|
+
get ocflVersion() { return this.#ocflVersion; }
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create a new backend-specific update transaction
|
|
132
|
+
* @param {OcflObjectInventoryMut} inventory
|
|
133
|
+
* @return {Promise<OcflObjectTransaction>}
|
|
134
|
+
*/
|
|
135
|
+
async #createTransaction(inventory) {
|
|
136
|
+
await this._validateObjectPath();
|
|
137
|
+
if (!this.workspace) await this._ensureNamaste();
|
|
138
|
+
let workspacePath = this.workspace || this.root;
|
|
139
|
+
return OcflObjectTransactionImpl.create(this, this.#store, inventory, workspacePath);
|
|
352
140
|
}
|
|
353
141
|
|
|
354
|
-
|
|
355
|
-
|
|
142
|
+
async _ensureNamaste() {
|
|
143
|
+
let prefix = NAMASTE_PREFIX_OBJECT;
|
|
144
|
+
let nv = await findNamasteVersion(this.#store, prefix, this.root);
|
|
145
|
+
if (nv === '') throw validation.createError(6, this.root);
|
|
146
|
+
if (nv) {
|
|
147
|
+
if (!this.ocflVersion) this.#ocflVersion = /** @type {OcflVersion} */(nv);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// namaste not found, check if object root is not empty
|
|
151
|
+
let files;
|
|
152
|
+
try {
|
|
153
|
+
files = await this.#store.readdir(this.root);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
}
|
|
156
|
+
if (files && files.length > 0) throw new Error('Cannot create an OCFL Object in a non-empty directory');
|
|
157
|
+
//create the namaste file
|
|
158
|
+
let filePath = path.join(this.root, NAMASTE_T + prefix + this.ocflVersion);
|
|
159
|
+
await this.#store.writeFile(filePath, prefix + this.ocflVersion + '\n', 'utf8');
|
|
356
160
|
}
|
|
357
161
|
|
|
358
162
|
/**
|
|
@@ -373,7 +177,7 @@ class OcflObjectImpl extends OcflObject {
|
|
|
373
177
|
let data = /** @type Inventory */(JSON.parse(datastr));
|
|
374
178
|
let [digest, actualDigest] = /** @type [string,string] */ (await Promise.all([
|
|
375
179
|
this.readFile(invPath + '.' + data.digestAlgorithm, 'utf8'),
|
|
376
|
-
OcflDigest.
|
|
180
|
+
OcflDigest.digest(data.digestAlgorithm, datastr)
|
|
377
181
|
]));
|
|
378
182
|
digest = digest.match(/[^\s]+/)?.[0];
|
|
379
183
|
if (digest !== actualDigest) throw new Error(`Inventory file ${this.root}/${invPath} digest mismatch: recorded=${digest} actual=${actualDigest}`);
|
|
@@ -381,33 +185,66 @@ class OcflObjectImpl extends OcflObject {
|
|
|
381
185
|
return new OcflObjectInventory(data);
|
|
382
186
|
}
|
|
383
187
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Set the latest version of inventory in the cache
|
|
190
|
+
* @param {*} inventory
|
|
191
|
+
* @param {*} version
|
|
192
|
+
*/
|
|
193
|
+
_setInventory(inventory, version='latest') {
|
|
194
|
+
//this._inventory['latest'] = this._inventory[inventory.head] = inventory;
|
|
195
|
+
this._inventory[version] = inventory;
|
|
196
|
+
if (version === 'latest') this._inventory[inventory.head] = inventory;
|
|
197
|
+
}
|
|
388
198
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
199
|
+
// all the parent directories cannot be an ocfl object
|
|
200
|
+
async _validateObjectPath() {
|
|
201
|
+
var p = this.root;
|
|
202
|
+
while (p !== '.' && p !== '/') {
|
|
203
|
+
p = path.dirname(p);
|
|
204
|
+
let nv = await findNamasteVersion(this.#store, NAMASTE_PREFIX_OBJECT, p);
|
|
205
|
+
if (nv) throw new Error(`Object root cannot be nested under another object ${p}`);
|
|
397
206
|
}
|
|
398
|
-
return;
|
|
399
207
|
}
|
|
400
208
|
|
|
401
209
|
/**
|
|
210
|
+
* If a cache exists, return the cached inventory. Otherwise read it from the storage and cache it.
|
|
211
|
+
* If the inventory file does not exist, eg in a newly created object, returns undefined.
|
|
212
|
+
* @param {string} [version] - Full name of the version, eg: v1, v2, v3
|
|
213
|
+
* @returns {Promise<OcflObjectInventory>}
|
|
214
|
+
*/
|
|
215
|
+
async getInventory(version = 'latest') {
|
|
216
|
+
if (!this._inventory[version]) {
|
|
217
|
+
let inv = await this._readInventory(version);
|
|
218
|
+
if (inv) this._setInventory(inv, version);
|
|
219
|
+
}
|
|
220
|
+
return this._inventory[version];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_baseInventory() {
|
|
224
|
+
return {
|
|
225
|
+
id: this.id,
|
|
226
|
+
digestAlgorithm: this.digestAlgorithm || 'sha512',
|
|
227
|
+
type: /** @type {Inventory["type"]} */(`https://ocfl.io/${this.ocflVersion}/spec/#inventory`),
|
|
228
|
+
contentDirectory: this.contentDirectory || 'content'
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Update the content files or directories as one transaction and commit the changes as a new version.
|
|
234
|
+
* After obtaining the OcflObjectTransaction instance, make sure to either call the
|
|
235
|
+
* {@link OcflObjectTransaction#commit} or {@link OcflObjectTransaction#rollback} method to complete the transaction.
|
|
236
|
+
* If callback function `cb` is provided, all update operations can be done in the callback function
|
|
237
|
+
* and all changes are automatically commited at the end of the function.
|
|
402
238
|
* @param {function(OcflObjectTransaction):Promise<*>} [updater]
|
|
403
|
-
* @param {
|
|
239
|
+
* @param {UpdateMode} [mode_]
|
|
240
|
+
* @return {Promise<?OcflObjectTransaction>}
|
|
404
241
|
*/
|
|
405
|
-
async update(updater,
|
|
406
|
-
|
|
407
|
-
if (!mode
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
242
|
+
async update(updater, mode_ = UPDATE_MODE.MERGE) {
|
|
243
|
+
const mode = enumeration.of(UPDATE_MODE, mode_);
|
|
244
|
+
if (!mode) throw new TypeError(`Invalid mode '${mode.toString()}'`);
|
|
245
|
+
const inv = await this.getInventory() ?? this._baseInventory();
|
|
246
|
+
const newInv = await OcflObjectInventory.newVersion(inv, mode === UPDATE_MODE.REPLACE);
|
|
247
|
+
const t = await this.#createTransaction(newInv);
|
|
411
248
|
if (typeof updater === 'function') {
|
|
412
249
|
try {
|
|
413
250
|
await updater(t);
|
|
@@ -422,75 +259,81 @@ class OcflObjectImpl extends OcflObject {
|
|
|
422
259
|
return t;
|
|
423
260
|
}
|
|
424
261
|
|
|
425
|
-
/** @deprecated */
|
|
426
|
-
async createReadStream(filePath, options) {
|
|
427
|
-
return this.#store.createReadStream(path.join(this.root, filePath), options);
|
|
428
|
-
}
|
|
429
262
|
|
|
430
|
-
|
|
431
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Import one or more content files or directories to the object
|
|
265
|
+
* in one transaction and commit the changes as a new version.
|
|
266
|
+
* Use this method to simplify copying all contents of a directory
|
|
267
|
+
* in local filesystem to the OCFL Object content directory
|
|
268
|
+
* @param {string|string[]|string[][]} content - The source path(s) or an array of a tuple [source, logical path]
|
|
269
|
+
* @param {UpdateMode} [mode=UPDATE_MODE.MERGE] - Update mode.
|
|
270
|
+
*/
|
|
271
|
+
async import(content, mode = UPDATE_MODE.MERGE) {
|
|
272
|
+
//let entries = /**@type {string[][]}*/(content);
|
|
273
|
+
if (typeof content === 'string') content = [[content, '']];
|
|
274
|
+
//else if (typeof content[0] === 'string') entries = /**@type {string[]}*/(content).map(src => [src, '']);
|
|
275
|
+
|
|
276
|
+
await this.update(async (t) => {
|
|
277
|
+
let res = await parallelize(/**@type {string[][]}*/(content), async (p) => {
|
|
278
|
+
if (typeof p === 'string') p = [p, ''];
|
|
279
|
+
let [source, target] = p;
|
|
280
|
+
return t.import(source, target);
|
|
281
|
+
});
|
|
282
|
+
let errors = [];
|
|
283
|
+
for (let i = 0; i < res.length; ++i) {
|
|
284
|
+
if (res[i] instanceof Error) errors.push(content[i][0]);
|
|
285
|
+
}
|
|
286
|
+
if (errors.length) {
|
|
287
|
+
let msg = "Cannot add the files: " + errors.join(', ');
|
|
288
|
+
throw new Error(msg);
|
|
289
|
+
}
|
|
290
|
+
}, mode);
|
|
432
291
|
}
|
|
433
292
|
|
|
434
|
-
/**
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
@typedef {{
|
|
438
|
-
(relPath: string, options?:({encoding?: null | undefined, flag?: OpenMode | undefined} & Abortable) | null): Promise<Buffer>
|
|
439
|
-
(relPath: string, options:({encoding: BufferEncoding, flag?: OpenMode | undefined} & Abortable)|BufferEncoding): Promise<string>
|
|
440
|
-
}} ReadFileFn
|
|
441
|
-
*/
|
|
442
|
-
/**
|
|
443
|
-
* Read a file inside an object.
|
|
444
|
-
* @type {ReadFileFn} abc
|
|
445
|
-
* @param {string} relPath - The file path relative to the object root.
|
|
446
|
-
* @param options - Options to be passed to the underlying method
|
|
293
|
+
/**
|
|
294
|
+
* Alias for import
|
|
295
|
+
* @see {@link OcflObject.import}
|
|
447
296
|
*/
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
getFile = function (opt, version) {
|
|
453
|
-
var obj = this;
|
|
454
|
-
var opt_ = typeof opt === 'string' ? { logicalPath: opt, version: version } : opt;
|
|
455
|
-
return new OcflObjectFile(this, opt_);
|
|
456
|
-
};
|
|
297
|
+
async add(sourceDir, mode = UPDATE_MODE.MERGE) {
|
|
298
|
+
await this.import(sourceDir, mode);
|
|
299
|
+
}
|
|
457
300
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Count the number of files contained in this object
|
|
303
|
+
* @param {string} [version]
|
|
304
|
+
*/
|
|
305
|
+
async count(version) {
|
|
306
|
+
let inv = await this.getInventory();
|
|
307
|
+
return [...inv.files(version)].length;
|
|
308
|
+
}
|
|
466
309
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
// return this.readFile(p, opt.options);
|
|
471
|
-
// }
|
|
472
|
-
// async getAsString(opt) {
|
|
473
|
-
// // @ts-ignore
|
|
474
|
-
// return this.getAsBuffer({ ...opt, options: opt.encoding || 'utf8' });
|
|
475
|
-
// }
|
|
476
|
-
// async getAsStream(opt) {
|
|
477
|
-
// let p = await this._resolveContentPath(opt);
|
|
478
|
-
// if (!p) throw new Error(`Cannot find content "${opt.logicalPath || opt.contentPath || opt.digest}" in the OCFL Object "${this.#id}" version "${opt.version || 'latest'}"`);
|
|
479
|
-
// return this.createReadStream(p, opt.options);
|
|
480
|
-
// }
|
|
310
|
+
async createReadable(filePath, options) {
|
|
311
|
+
return this.#store.createReadable(path.join(this.root, filePath), options);
|
|
312
|
+
}
|
|
481
313
|
|
|
482
314
|
/**
|
|
483
|
-
*
|
|
484
|
-
*
|
|
315
|
+
* Check if the object root path points to an existing file or non-empty directory
|
|
316
|
+
* in the underlying backend store.
|
|
317
|
+
* The existing directory may or may not be a valid OCFL Object.
|
|
318
|
+
* @return {Promise<boolean>}
|
|
485
319
|
*/
|
|
486
|
-
async export(targetDir, version) { }
|
|
487
|
-
|
|
488
320
|
async exists() {
|
|
489
321
|
return !(await isDirEmpty(this.#store, this.root));
|
|
490
322
|
}
|
|
491
323
|
|
|
492
324
|
/**
|
|
325
|
+
* Copy all content of this Object to a directory in local filesystem
|
|
326
|
+
* @param {string} targetDir
|
|
327
|
+
* @param {string} version
|
|
328
|
+
*/
|
|
329
|
+
async export(targetDir, version) { throw new Error('Not Implemented'); }
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Iterate through the content files contained in the specified version.
|
|
333
|
+
* Returns an Iterator that contains FileRef which consists of logical path, content path, and digest, in no particular order.
|
|
334
|
+
* The iterator is implemented as a generator.
|
|
493
335
|
* @param {string} [version]
|
|
336
|
+
* @return {Promise<Generator<OcflObjectFile, void, unknown>>}
|
|
494
337
|
*/
|
|
495
338
|
async files(version) {
|
|
496
339
|
let inv = await this.getInventory();
|
|
@@ -503,99 +346,90 @@ class OcflObjectImpl extends OcflObject {
|
|
|
503
346
|
return files();
|
|
504
347
|
}
|
|
505
348
|
|
|
506
|
-
|
|
507
349
|
/**
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
* @param {
|
|
511
|
-
*
|
|
350
|
+
* Get a file by either the [logical path and version], digest, or content path.
|
|
351
|
+
* If version is omitted, it defaults to the last version.
|
|
352
|
+
* @param { LogicalPath | FileRefLogical | FileRefDigest | FileRefContent } opt A choice of logical path (eg: 'test') and version (eg: 'v1'),
|
|
353
|
+
* digest of the file content, or content path (eg: 'v1/content/test')
|
|
354
|
+
* @param {string} [version='latest'] The object version name, eg: 'v1', 'v2'. Default to 'latest'. Only used if opt is a LogicalPath.
|
|
355
|
+
* @returns {OcflObjectFile}
|
|
512
356
|
*/
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
};
|
|
524
|
-
if (this.contentDirectory !== 'content') data.contentDirectory = this.contentDirectory;
|
|
357
|
+
getFile(opt, version) {
|
|
358
|
+
const inv = this.inventory;
|
|
359
|
+
/** @type {any} */
|
|
360
|
+
let file = typeof opt === 'string' ? { logicalPath: opt, version: version } : { ...opt };
|
|
361
|
+
if (file.logicalPath) {
|
|
362
|
+
file = inv?.getFile(file.logicalPath, file.version);
|
|
363
|
+
} else if (file.digest) {
|
|
364
|
+
file = inv?.getFileByDigest(file.digest, file.version);
|
|
365
|
+
} else if (file.contentPath) {
|
|
366
|
+
file = inv?.getFileByContentPath(file.contentPath, file.version);
|
|
525
367
|
}
|
|
526
|
-
return
|
|
527
|
-
}
|
|
368
|
+
if (file) return new OcflObjectFile(this, file);
|
|
369
|
+
};
|
|
528
370
|
|
|
529
371
|
/**
|
|
530
|
-
*
|
|
531
|
-
* @param {string} absOrRelPath
|
|
532
|
-
* @return {string}
|
|
372
|
+
* Return the latest version of the cached data from inventory.json which can be read using {@link getInventory()}
|
|
533
373
|
*/
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
return absOrRelPath;
|
|
374
|
+
get inventory() {
|
|
375
|
+
return this._inventory.latest;
|
|
537
376
|
}
|
|
538
377
|
|
|
539
|
-
/**
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
* @
|
|
378
|
+
/**
|
|
379
|
+
* Reads the object inventory file and cache it in the memory.
|
|
380
|
+
* Optionally, preloads and reads all files metadata from the underlying storage.
|
|
381
|
+
* @param {object} [options]
|
|
382
|
+
* @param {boolean} [options.filesMetadata] Whether to read file metadata from the underlying storage.
|
|
543
383
|
*/
|
|
544
|
-
async
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
384
|
+
async load({ filesMetadata } = {}) {
|
|
385
|
+
this._inventory = {}; // reset inventory cache
|
|
386
|
+
const inv = await this.getInventory();
|
|
387
|
+
//await this.loadExtensions();
|
|
388
|
+
if (inv && filesMetadata) {
|
|
389
|
+
//load file metadata from underlying storage
|
|
390
|
+
const metadata = inv.metadata = {};
|
|
391
|
+
for await (const file of await this.#store.list(this.root, { recursive: true })) {
|
|
392
|
+
const contentPath = file.path;
|
|
393
|
+
metadata[contentPath] = {
|
|
394
|
+
size: file.size,
|
|
395
|
+
lastModified: file.lastModified.getTime()
|
|
396
|
+
}
|
|
397
|
+
}
|
|
553
398
|
}
|
|
554
|
-
await this._validateObjectPath();
|
|
555
|
-
let createdDir = await this.#store.mkdir(workspacePath);
|
|
556
|
-
if (!this.workspace) await this._ensureNamaste();
|
|
557
|
-
return createTransactionProxy(new OcflObjectTransactionImpl(this, this.#store, inventory, workspaceVersionPath, createdDir));
|
|
558
399
|
}
|
|
559
400
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Read the content of a file inside an object.
|
|
403
|
+
* @param {string} relPath - The file path relative to the object root.
|
|
404
|
+
* @param {*} options - Options to be passed to the underlying method
|
|
405
|
+
*/
|
|
406
|
+
async readFile(relPath, options) {
|
|
407
|
+
return this.#store.readFile(path.join(this.root, relPath), options);
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
toString() { return `OcflObject { root: ${this.root}, id: ${this.id} }`; }
|
|
411
|
+
|
|
412
|
+
toJSON() { return { root: this.root, id: this.id }; }
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* @implements {OcflObject}
|
|
420
|
+
*/
|
|
421
|
+
class OcflObjectImpl extends OcflObject {
|
|
422
|
+
|
|
423
|
+
// async _resolveContentPath({ logicalPath = '', contentPath = '', digest = '', version = '' }) {
|
|
424
|
+
// if (!contentPath) {
|
|
425
|
+
// let inv = await this.getInventory();
|
|
426
|
+
// version = version && version !== 'latest' ? version : inv.head;
|
|
427
|
+
// contentPath = inv.getContentPath(digest || inv.getDigest(logicalPath, version));
|
|
428
|
+
// }
|
|
429
|
+
// return contentPath;
|
|
430
|
+
// }
|
|
579
431
|
|
|
580
|
-
// all the parent directories cannot be an ocfl object
|
|
581
|
-
async _validateObjectPath() {
|
|
582
|
-
var p = this.root;
|
|
583
|
-
while (p !== '.' && p !== '/' ) {
|
|
584
|
-
p = path.dirname(p);
|
|
585
|
-
let nv = await findNamasteVersion(this.#store, NAMASTE_PREFIX_OBJECT, p);
|
|
586
|
-
if (nv) throw new Error(`Object root cannot be nested under another object ${p}`);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
432
|
async isObject(aPath) { }
|
|
591
|
-
getVersionString(i) { }
|
|
592
|
-
async determineVersion() { }
|
|
593
|
-
nameVersion(version) {
|
|
594
|
-
return 'ocfl_object_' + version;
|
|
595
|
-
}
|
|
596
|
-
async digest_dir(dir) { }
|
|
597
|
-
async hash_file(p) { }
|
|
598
|
-
async removeEmptyDirectories(folder) { }
|
|
599
433
|
static status(rootPath) { }
|
|
600
434
|
static exists(rootPath) { }
|
|
601
435
|
|
|
@@ -609,48 +443,9 @@ class OcflObjectImpl extends OcflObject {
|
|
|
609
443
|
};
|
|
610
444
|
|
|
611
445
|
|
|
612
|
-
const objectInterface = new OcflObject();
|
|
613
|
-
const proxyHandler = {
|
|
614
|
-
/**
|
|
615
|
-
* @param {OcflObject} target
|
|
616
|
-
* @param {string} prop
|
|
617
|
-
*/
|
|
618
|
-
get(target, prop, receiver) {
|
|
619
|
-
if (prop in objectInterface) {
|
|
620
|
-
let p = target[prop];
|
|
621
|
-
if (typeof p === 'function') {
|
|
622
|
-
return p.bind(target);
|
|
623
|
-
} else {
|
|
624
|
-
return p;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
/**
|
|
631
|
-
* Return any OcflObject implementation as OcflObject.
|
|
632
|
-
* @param {OcflObject} impl
|
|
633
|
-
*/
|
|
634
|
-
function createObjectProxy(impl) {
|
|
635
|
-
return new Proxy(impl, proxyHandler);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Create an OCFL Object
|
|
640
|
-
* @param {OcflObjectConfig} config
|
|
641
|
-
* @return {OcflObject}
|
|
642
|
-
*/
|
|
643
|
-
function createObject(config) {
|
|
644
|
-
throw new Error('Not Implemented');
|
|
645
|
-
}
|
|
646
|
-
|
|
647
446
|
module.exports = {
|
|
648
447
|
OcflObject,
|
|
649
|
-
OcflObjectImpl,
|
|
650
448
|
OcflObjectInventory,
|
|
651
449
|
//OcflObjectTransaction,
|
|
652
|
-
OcflObjectTransactionImpl
|
|
653
|
-
createTransactionProxy,
|
|
654
|
-
createObjectProxy,
|
|
655
|
-
createObject
|
|
450
|
+
OcflObjectTransactionImpl
|
|
656
451
|
};
|