@ocfl/ocfl 0.4.0 → 1.0.0-beta.1
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/LICENSE +674 -0
- package/README.md +24 -9
- package/dist/constants.d.ts +15 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/digest.d.ts +58 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +204 -0
- package/dist/digest.js.map +1 -0
- package/dist/error.d.ts +5 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +9 -0
- package/dist/error.js.map +1 -0
- package/dist/extension.d.ts +83 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +170 -0
- package/dist/extension.js.map +1 -0
- package/dist/extensions/0002-flat-direct-storage-layout.d.ts +12 -0
- package/dist/extensions/0002-flat-direct-storage-layout.d.ts.map +1 -0
- package/dist/extensions/0002-flat-direct-storage-layout.js +34 -0
- package/dist/extensions/0002-flat-direct-storage-layout.js.map +1 -0
- package/dist/extensions/0003-hash-and-id-n-tuple-storage-layout.d.ts +15 -0
- package/dist/extensions/0003-hash-and-id-n-tuple-storage-layout.d.ts.map +1 -0
- package/dist/extensions/0003-hash-and-id-n-tuple-storage-layout.js +49 -0
- package/dist/extensions/0003-hash-and-id-n-tuple-storage-layout.js.map +1 -0
- package/dist/extensions/0004-hashed-n-tuple-storage-layout.d.ts +17 -0
- package/dist/extensions/0004-hashed-n-tuple-storage-layout.d.ts.map +1 -0
- package/dist/extensions/0004-hashed-n-tuple-storage-layout.js +54 -0
- package/dist/extensions/0004-hashed-n-tuple-storage-layout.js.map +1 -0
- package/dist/extensions/0006-flat-omit-prefix-storage-layout.d.ts +6 -0
- package/dist/extensions/0006-flat-omit-prefix-storage-layout.d.ts.map +1 -0
- package/dist/extensions/0006-flat-omit-prefix-storage-layout.js +8 -0
- package/dist/extensions/0006-flat-omit-prefix-storage-layout.js.map +1 -0
- package/dist/extensions/0007-n-tuple-omit-prefix-storage-layout.d.ts +6 -0
- package/dist/extensions/0007-n-tuple-omit-prefix-storage-layout.d.ts.map +1 -0
- package/dist/extensions/0007-n-tuple-omit-prefix-storage-layout.js +8 -0
- package/dist/extensions/0007-n-tuple-omit-prefix-storage-layout.js.map +1 -0
- package/dist/extensions/0009-digest-algorithms.d.ts +9 -0
- package/dist/extensions/0009-digest-algorithms.d.ts.map +1 -0
- package/dist/extensions/0009-digest-algorithms.js +11 -0
- package/dist/extensions/0009-digest-algorithms.js.map +1 -0
- package/dist/extensions/000N-path-direct-storage-layout.d.ts +16 -0
- package/dist/extensions/000N-path-direct-storage-layout.d.ts.map +1 -0
- package/dist/extensions/000N-path-direct-storage-layout.js +46 -0
- package/dist/extensions/000N-path-direct-storage-layout.js.map +1 -0
- package/dist/extensions/index.d.ts +23 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +8 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/file.d.ts +87 -0
- package/dist/file.d.ts.map +1 -0
- package/dist/file.js +118 -0
- package/dist/file.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/inventory.d.ts +155 -0
- package/dist/inventory.d.ts.map +1 -0
- package/dist/inventory.js +591 -0
- package/dist/inventory.js.map +1 -0
- package/dist/object.d.ts +188 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +425 -0
- package/dist/object.js.map +1 -0
- package/dist/storage.d.ts +94 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +302 -0
- package/dist/storage.js.map +1 -0
- package/dist/store.d.ts +100 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +157 -0
- package/dist/store.js.map +1 -0
- package/dist/transaction.d.ts +155 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +395 -0
- package/dist/transaction.js.map +1 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +24 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +85 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation.d.ts +32 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +31 -0
- package/dist/validation.js.map +1 -0
- package/package.json +31 -51
- package/src/constants.ts +16 -0
- package/src/digest.ts +258 -0
- package/src/error.ts +9 -0
- package/src/extension.ts +206 -0
- package/{lib/extensions/0002-flat-direct-storage-layout.js → src/extensions/0002-flat-direct-storage-layout.ts} +15 -22
- package/src/extensions/0003-hash-and-id-n-tuple-storage-layout.ts +56 -0
- package/src/extensions/0004-hashed-n-tuple-storage-layout.ts +59 -0
- package/src/extensions/0006-flat-omit-prefix-storage-layout.ts +9 -0
- package/src/extensions/0007-n-tuple-omit-prefix-storage-layout.ts +9 -0
- package/src/extensions/0009-digest-algorithms.ts +12 -0
- package/src/extensions/000N-path-direct-storage-layout.ts +51 -0
- package/src/extensions/index.ts +24 -0
- package/src/file.ts +161 -0
- package/src/index.ts +92 -0
- package/src/inventory.ts +634 -0
- package/src/object.ts +473 -0
- package/src/storage.ts +342 -0
- package/src/store.ts +199 -0
- package/src/transaction.ts +456 -0
- package/{lib/types.d.ts → src/types.ts} +28 -74
- package/src/utils.ts +94 -0
- package/src/validation.ts +46 -0
- package/index.js +0 -129
- package/index.mjs +0 -18
- package/lib/constants.js +0 -24
- package/lib/digest.js +0 -307
- package/lib/enum.js +0 -90
- package/lib/error.js +0 -19
- package/lib/extension.js +0 -194
- package/lib/extensions/0003-hash-and-id-n-tuple-storage-layout.js +0 -60
- package/lib/extensions/0004-hashed-n-tuple-storage-layout.js +0 -63
- package/lib/extensions/0005-mutable-head.js +0 -0
- package/lib/extensions/0006-flat-omit-prefix-storage-layout.js +0 -7
- package/lib/extensions/0007-n-tuple-omit-prefix-storage-layout.js +0 -7
- package/lib/extensions/0008-schema-registry.js +0 -0
- package/lib/extensions/0009-digest-algorithms.js +0 -18
- package/lib/extensions/000N-path-direct-storage-layout.js +0 -61
- package/lib/extensions/index.js +0 -13
- package/lib/file.js +0 -147
- package/lib/index.js +0 -13
- package/lib/inventory.js +0 -564
- package/lib/object.js +0 -499
- package/lib/storage.js +0 -338
- package/lib/store.js +0 -191
- package/lib/transaction.js +0 -448
- package/lib/utils.js +0 -122
- package/lib/validation.js +0 -174
package/lib/transaction.js
DELETED
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
/** Models a set of ocfl object modifying operations as a transaction that is as close as atomic as possible */
|
|
3
|
-
//todo: replace path module with a more generic path module that works in both node and browser
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { parallelize } = require('./utils');
|
|
6
|
-
const { OcflDigest } = require('./digest');
|
|
7
|
-
const { INVENTORY_NAME } = require('./constants').OcflConstants;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {import('./object').OcflObject} OcflObject
|
|
11
|
-
* @typedef {import('./store').OcflStore} OcflStore
|
|
12
|
-
* @typedef {import('./inventory').OcflObjectInventoryMut} OcflObjectInventoryMut
|
|
13
|
-
* @typedef {import('stream').Writable} Writable
|
|
14
|
-
* @typedef {import('stream').StreamOptions} StreamOptions
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A Transaction represent an atomic update on an OCFL Object.
|
|
19
|
-
* When a transaction is commited, all changes are saved as a new version.
|
|
20
|
-
* This is a wrapper class to hide all the storage specific implementation.
|
|
21
|
-
*/
|
|
22
|
-
class OcflObjectTransaction {
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Discard all the changes made in this transaction,
|
|
26
|
-
* @abstract
|
|
27
|
-
*/
|
|
28
|
-
async rollback() { throw new Error('Not Implemented'); }
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Commit all the changes made in this transaction to the actual OCFL Object.
|
|
32
|
-
* @abstract
|
|
33
|
-
* @param {Object} [options] - Commit options. message and user will be included in the version entry of the inventory.json
|
|
34
|
-
* @param {string} [options.message] - Freeform text used to record the rationale for creating the current version
|
|
35
|
-
* @param {Object} [options.user] - Identify the user or agent that created the current Version
|
|
36
|
-
* @param {string} options.user.name - Any readable name of the user, e.g., a proper name, user ID, agent ID.
|
|
37
|
-
* @param {string} [options.user.address] - A URI: either a mailto URI [RFC6068] with the e-mail address of the user or a URL to a personal identifier, e.g., an ORCID iD.
|
|
38
|
-
* @param {boolean} [options.force=false] - If true, force to commit. Use it after a purge.
|
|
39
|
-
*/
|
|
40
|
-
async commit(options) { throw new Error('Not Implemented'); }
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Completely remove a file path and corresponding content from all versions of an OCFL Object.
|
|
44
|
-
* This is a special case that is not supported as part of regular OCFL versioning operations.
|
|
45
|
-
* @param {*} logicalPath
|
|
46
|
-
*/
|
|
47
|
-
async purge(logicalPath) {
|
|
48
|
-
return await this.remove(logicalPath, { purge: true });
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Write data to a file at `logicalPath`
|
|
53
|
-
* @abstract
|
|
54
|
-
* @param {string} logicalPath - A path relative to the object content directory
|
|
55
|
-
* @param {string|Uint8Array|DataView|ArrayBuffer|ReadableStream} data - Data to be written to the file
|
|
56
|
-
* @param {Object|string} [options] - The same options as https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback
|
|
57
|
-
*/
|
|
58
|
-
async write(logicalPath, data, options) { throw new Error('Not Implemented'); }
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Import content by recursively copying a file or directory from local file system to the
|
|
62
|
-
* OCFL Object content. For example, `object.import('/home/john/data/b','data/b')` will
|
|
63
|
-
* copy `/home/john/data/b` from the local filesystem to the content `data/b` in the OFCL Object.
|
|
64
|
-
* Set target to empty string ('') to copy a directory content to the object's content root.
|
|
65
|
-
* If target is not specified, the base name of the source will be used.
|
|
66
|
-
* For examples:
|
|
67
|
-
* To copy '/src/data' directory as 'data' use import('/src/data', 'data').
|
|
68
|
-
* To copy all files inside '/src/data' directory to the logical root, use import('/src/data', '').
|
|
69
|
-
* @abstract
|
|
70
|
-
* @param {string} source - A path to a file or directory in the local filesystem to be copied
|
|
71
|
-
* @param {string} [target] - A logical path in the OCFL Object content.
|
|
72
|
-
*/
|
|
73
|
-
async import(source, target) { throw new Error('Not Implemented'); }
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Duplicate the logical path `source` as a different logical path `target`, both paths will then point
|
|
77
|
-
* to the same content path and bitstreams.
|
|
78
|
-
* @abstract
|
|
79
|
-
* @param {string} source - The logical path to be copied
|
|
80
|
-
* @param {string} target - The target logical path
|
|
81
|
-
*/
|
|
82
|
-
async copy(source, target) { throw new Error('Not Implemented'); }
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Changes the logical path of existing content. This is similar to move operation in file systems
|
|
86
|
-
* Unless renaming files that are not yet commited, this operation will NOT rename the actual file name referenced by the manifest.
|
|
87
|
-
* @abstract
|
|
88
|
-
* @param {*} source
|
|
89
|
-
* @param {*} target
|
|
90
|
-
*/
|
|
91
|
-
async rename(source, target) { throw new Error('Not Implemented'); }
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Alias of {@link rename()}
|
|
95
|
-
* @param {*} source
|
|
96
|
-
* @param {*} target
|
|
97
|
-
*/
|
|
98
|
-
async move(source, target) { return this.rename(source, target); }
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Remove a file or directory
|
|
102
|
-
* @abstract
|
|
103
|
-
* @param {string} logicalPath - A path relative to the object content directory
|
|
104
|
-
* @param {Object} [options]
|
|
105
|
-
* @param {boolean} [options.purge=false] - If true, completely remove the content from all versions
|
|
106
|
-
*/
|
|
107
|
-
async remove(logicalPath, options) { throw new Error('Not Implemented'); }
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Remove a file or directory. Alias of {@link remove()}
|
|
111
|
-
* @param {string} logicalPath - A path relative to the object content directory
|
|
112
|
-
* @param {Object} [options]
|
|
113
|
-
*/
|
|
114
|
-
async delete(logicalPath, options) { return this.remove(logicalPath, options); }
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Makes content from a version earlier than the previous version available in the current version of an OCFL Object.
|
|
118
|
-
* @abstract
|
|
119
|
-
* @param {string} logicalPath
|
|
120
|
-
* @param {string} versionName
|
|
121
|
-
* @return {Promise<number>}
|
|
122
|
-
*/
|
|
123
|
-
async reinstate(logicalPath, versionName) { throw new Error('Not Implemented'); }
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Create a NodeJS writable stream
|
|
127
|
-
* @abstract
|
|
128
|
-
* @param {string} logicalPath
|
|
129
|
-
* @param {BufferEncoding|StreamOptions} [options]
|
|
130
|
-
* @return {Promise<import('stream').Writable>}
|
|
131
|
-
* @deprecated Use {@link createWritable()} instead
|
|
132
|
-
*/
|
|
133
|
-
async createWriteStream(logicalPath, options) { throw new Error('Not Implemented'); }
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Create a writable stream
|
|
137
|
-
* @abstract
|
|
138
|
-
* @param {string} logicalPath
|
|
139
|
-
* @param {*} [options]
|
|
140
|
-
* @return {Promise<WritableStream>}
|
|
141
|
-
*/
|
|
142
|
-
async createWritable(logicalPath, options) { throw new Error('Not Implemented'); }
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Base class for any transaction implementation on different storage backend
|
|
148
|
-
*/
|
|
149
|
-
class OcflObjectTransactionImpl extends OcflObjectTransaction {
|
|
150
|
-
_contentRoot;
|
|
151
|
-
_object;
|
|
152
|
-
_inventory;
|
|
153
|
-
_committed = false;
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
*
|
|
157
|
-
* @param {OcflObject} ocflObject
|
|
158
|
-
* @param {OcflStore} ocflStore
|
|
159
|
-
* @param {OcflObjectInventoryMut} inventory
|
|
160
|
-
* @param {string} workspaceVersionPath
|
|
161
|
-
* @param {string} createdDir
|
|
162
|
-
*/
|
|
163
|
-
constructor(ocflObject, ocflStore, inventory, workspaceVersionPath, createdDir) {
|
|
164
|
-
super();
|
|
165
|
-
this._workspaceVersionPath = workspaceVersionPath;
|
|
166
|
-
this._contentRoot = path.join(workspaceVersionPath, inventory.contentDirectory);
|
|
167
|
-
this._object = ocflObject;
|
|
168
|
-
this._store = ocflStore;
|
|
169
|
-
this._inventory = inventory;
|
|
170
|
-
this._createdDir = createdDir;
|
|
171
|
-
/** @type {(Promise<any> & { done?: boolean})[]} */
|
|
172
|
-
this._queue = [];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Create a new transaction instance.
|
|
177
|
-
* @param {OcflObject} ocflObject
|
|
178
|
-
* @param {OcflStore} ocflStore
|
|
179
|
-
* @param {OcflObjectInventoryMut} inventory
|
|
180
|
-
* @param {string} workspacePath
|
|
181
|
-
*/
|
|
182
|
-
static async create(ocflObject, ocflStore, inventory, workspacePath) {
|
|
183
|
-
// @todo: validate existing object
|
|
184
|
-
// check existing
|
|
185
|
-
let workspaceVersionPath = path.join(workspacePath, inventory.head);
|
|
186
|
-
if (await ocflStore.exists(workspaceVersionPath)) {
|
|
187
|
-
// workspace dir exists, abort update
|
|
188
|
-
throw new Error('Uncommitted changes detected. Object is being updated by other process or there has been a failed update attempt');
|
|
189
|
-
}
|
|
190
|
-
await ocflStore.mkdir(workspacePath);
|
|
191
|
-
return new OcflObjectTransactionImpl(ocflObject, ocflStore, inventory, workspaceVersionPath, workspacePath);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
_getRealPath(logicalPath) {
|
|
195
|
-
let p = path.normalize(logicalPath);
|
|
196
|
-
if (p.startsWith('/') || p.startsWith('..')) throw new Error('logicalPath must be a relative path without `..`');
|
|
197
|
-
return path.join(this._contentRoot, p);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async rollback() {
|
|
201
|
-
if (!this._committed) {
|
|
202
|
-
await Promise.all(this._queue);
|
|
203
|
-
if (this._createdDir) await this._store.remove(this._createdDir);
|
|
204
|
-
this._committed = true;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async commit(options) {
|
|
209
|
-
// console.log('commit');
|
|
210
|
-
// console.log(this._inventory.toString());
|
|
211
|
-
if (!this._committed) {
|
|
212
|
-
let count = this._queue.filter(p => !p.done).length;
|
|
213
|
-
if (count > 0) {
|
|
214
|
-
await this.rollback();
|
|
215
|
-
throw new Error(`Unfinished operations detected (${count}). Make sure that updater callback is async function and there are no floating promises inside.`);
|
|
216
|
-
}
|
|
217
|
-
// if there is no changes, abort commit
|
|
218
|
-
if (!options?.force && !this._inventory.isChanged) {
|
|
219
|
-
return await this.rollback();
|
|
220
|
-
}
|
|
221
|
-
await this._commit(options);
|
|
222
|
-
this._object._setInventory(this._inventory);
|
|
223
|
-
this._committed = true;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async _commit(options = {}) {
|
|
229
|
-
const inventory = this._inventory;
|
|
230
|
-
const digestAlgorithm = inventory.digestAlgorithm;
|
|
231
|
-
// set commit info
|
|
232
|
-
inventory.created = (new Date()).toISOString();
|
|
233
|
-
if (options.message) inventory.message = options.message;
|
|
234
|
-
if (options.user) inventory.user = options.user;
|
|
235
|
-
|
|
236
|
-
let workspaceVersionPath = this._workspaceVersionPath;
|
|
237
|
-
let objectVersionPath = path.join(this._object.root, inventory.head);
|
|
238
|
-
/* eg: /workspace/aa/bb/cc/object-1/v1/inventory.json */
|
|
239
|
-
let invPath = path.join(workspaceVersionPath, INVENTORY_NAME);
|
|
240
|
-
/* eg: /workspace/aa/bb/cc/object-1/v1/inventory.json.sha512 */
|
|
241
|
-
let invDigestPath = invPath + '.' + digestAlgorithm;
|
|
242
|
-
|
|
243
|
-
// calculate digest
|
|
244
|
-
const inventoryContent = inventory.toString();
|
|
245
|
-
let digest = (await OcflDigest.digest(digestAlgorithm, inventoryContent)) + ' ' + INVENTORY_NAME;
|
|
246
|
-
|
|
247
|
-
// write inventory.json to workspace
|
|
248
|
-
await this._store.writeFile(invPath, inventoryContent);
|
|
249
|
-
await this._store.writeFile(invDigestPath, digest);
|
|
250
|
-
if (workspaceVersionPath !== objectVersionPath) {
|
|
251
|
-
try {
|
|
252
|
-
await this._object._ensureNamaste();
|
|
253
|
-
await this._store.move(workspaceVersionPath, objectVersionPath);
|
|
254
|
-
await this._store.remove(this._createdDir);
|
|
255
|
-
} catch (error) {
|
|
256
|
-
await this.rollback();
|
|
257
|
-
throw error;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
// replace root inventory
|
|
261
|
-
/* eg: /data/aa/bb/cc/object-1/inventory.json */
|
|
262
|
-
const rootInvPath = path.join(this._object.root, INVENTORY_NAME);
|
|
263
|
-
/* eg: /data/aa/bb/cc/object-1/v1/inventory.json */
|
|
264
|
-
invPath = path.join(objectVersionPath, INVENTORY_NAME);
|
|
265
|
-
invDigestPath = invPath + '.' + digestAlgorithm;
|
|
266
|
-
let rootInvDigestPath = rootInvPath + '.' + digestAlgorithm;
|
|
267
|
-
await this._store.copyFile(invPath, rootInvPath + '.tmp');
|
|
268
|
-
await Promise.all([
|
|
269
|
-
this._store.move(rootInvPath + '.tmp', rootInvPath),
|
|
270
|
-
this._store.copyFile(invDigestPath, rootInvDigestPath)]);
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
*
|
|
276
|
-
* @param {string} logicalPath
|
|
277
|
-
* @param {Object} options
|
|
278
|
-
* @return {Promise<WritableStream>}
|
|
279
|
-
*/
|
|
280
|
-
async createWritable(logicalPath, options) {
|
|
281
|
-
if (this._committed) throw new Error('Transaction already commited');
|
|
282
|
-
const digestAlgo = this._inventory.digestAlgorithm;
|
|
283
|
-
const digestAlgos = [digestAlgo, ...(this._object.fixityAlgorithms || [])];
|
|
284
|
-
const hs = await OcflDigest.createStream(digestAlgos);
|
|
285
|
-
const realPath = this._getRealPath(logicalPath);
|
|
286
|
-
const writer = (await this._store.createWritable(realPath, options)).getWriter();
|
|
287
|
-
//const hs = await OcflDigest.createStream(this._inventory.digestAlgorithm);
|
|
288
|
-
const inv = this._inventory;
|
|
289
|
-
const store = this._store;
|
|
290
|
-
const ws = new WritableStream({
|
|
291
|
-
write(chunk, controller) {
|
|
292
|
-
hs.update(chunk);
|
|
293
|
-
return writer.write(chunk);
|
|
294
|
-
},
|
|
295
|
-
async close() {
|
|
296
|
-
await writer.close();
|
|
297
|
-
//const digest = hs.digest('hex');
|
|
298
|
-
const {[digestAlgo]: digest, ...fixity} = hs.digest();
|
|
299
|
-
if (inv.getContentPath(digest)) await store.remove(realPath);
|
|
300
|
-
inv.add(logicalPath, digest, fixity);
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
/** @type {Promise<any> & { done?: boolean }} */
|
|
304
|
-
const p = writer.closed;
|
|
305
|
-
p.finally(() => { p.done = true; });
|
|
306
|
-
this._queue.push(p);
|
|
307
|
-
|
|
308
|
-
return ws;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async write(logicalPath, data, options) {
|
|
312
|
-
return this._transact(async () => {
|
|
313
|
-
let realPath = this._getRealPath(logicalPath);
|
|
314
|
-
const digestAlgo = this._inventory.digestAlgorithm;
|
|
315
|
-
const digestAlgos = [digestAlgo, ...(this._object.fixityAlgorithms || [])];
|
|
316
|
-
let digest, fixity;
|
|
317
|
-
if (typeof data === 'string' || ArrayBuffer.isView(data)) {
|
|
318
|
-
// if data is not stream, check digest first
|
|
319
|
-
if (ArrayBuffer.isView(data) && !(data instanceof Uint8Array || data instanceof Uint16Array || data instanceof Uint32Array)) {
|
|
320
|
-
data = new Uint8Array(data.buffer)
|
|
321
|
-
}
|
|
322
|
-
const hashes = await OcflDigest.digest(digestAlgos, data);
|
|
323
|
-
({[digestAlgo]: digest, ...fixity} = hashes);
|
|
324
|
-
if (!this._inventory.getContentPath(digest)) {
|
|
325
|
-
// no digest yet, write the file to storage backend
|
|
326
|
-
await this._store.writeFile(realPath, data, options);
|
|
327
|
-
}
|
|
328
|
-
} else if (data instanceof ReadableStream) {
|
|
329
|
-
// save the stream first
|
|
330
|
-
const hs = await OcflDigest.createStreamThrough(digestAlgos);
|
|
331
|
-
const rs = data.pipeThrough(hs);
|
|
332
|
-
await this._store.writeFile(realPath, rs, options);
|
|
333
|
-
({[digestAlgo]: digest, ...fixity} = hs.digest());
|
|
334
|
-
// already exists, delete temp file
|
|
335
|
-
if (this._inventory.getContentPath(digest)) await this._store.remove(realPath);
|
|
336
|
-
} else {
|
|
337
|
-
throw new TypeError('Unsupported data type ', data);
|
|
338
|
-
}
|
|
339
|
-
this._inventory.add(logicalPath, digest, fixity);
|
|
340
|
-
// console.log(this._inventory.toString());
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Import a single file from source. This is a helper method for {@link OcflObjectTransaction.import | import()} method.
|
|
346
|
-
* @param {*} source
|
|
347
|
-
* @param {*} target
|
|
348
|
-
*/
|
|
349
|
-
async importFile(source, target) {
|
|
350
|
-
if (!target) throw new TypeError('Target logical path must not be empty if source is a file.');
|
|
351
|
-
const realPath = this._getRealPath(target);
|
|
352
|
-
const rs = await this._store.createReadable(source);
|
|
353
|
-
const digestAlgo = this._inventory.digestAlgorithm;
|
|
354
|
-
const digestAlgos = [digestAlgo, ...(this._object.fixityAlgorithms || [])];
|
|
355
|
-
const {[digestAlgo]: digest, ...fixity} = await OcflDigest.digest(digestAlgos, rs);
|
|
356
|
-
|
|
357
|
-
if (!this._inventory.getContentPath(digest)) {
|
|
358
|
-
await this._store.copyFile(source, realPath);
|
|
359
|
-
}
|
|
360
|
-
this._inventory.add(target, digest, fixity);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async import(source, target) {
|
|
364
|
-
return this._transact(async () => {
|
|
365
|
-
target = target ?? path.basename(source);
|
|
366
|
-
let srcStat = await this._store.stat(source);
|
|
367
|
-
if (srcStat.isFile()) {
|
|
368
|
-
await this.importFile(source, target);
|
|
369
|
-
} else if (srcStat.isDirectory()) {
|
|
370
|
-
const files = await this._store.readdir(source);
|
|
371
|
-
await parallelize(files, async (filename) => {
|
|
372
|
-
await this.import(path.join(source, filename), path.join(target, filename));
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
async copy(source, target) {
|
|
379
|
-
return this._transact(async () => {
|
|
380
|
-
return this._inventory.copy(source, target);
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
async rename(source, target) {
|
|
385
|
-
return this._transact(async () => {
|
|
386
|
-
// if file already exists in the workspace, rename the actual file
|
|
387
|
-
try {
|
|
388
|
-
let realSource = this._getRealPath(source);
|
|
389
|
-
let realTarget = this._getRealPath(target);
|
|
390
|
-
await this._store.move(realSource, realTarget);
|
|
391
|
-
} catch (error) {
|
|
392
|
-
if (error.code !== 'ENOENT') throw error;
|
|
393
|
-
}
|
|
394
|
-
return this._inventory.rename(source, target);
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
async reinstate(logicalPath, versionName) {
|
|
399
|
-
//let digest = this._inventory.getDigestFromLogical(logicalPath);
|
|
400
|
-
let count = this._inventory.reinstate(logicalPath, versionName);
|
|
401
|
-
// if (count) {
|
|
402
|
-
// // ensure no existing file with the same logical path in the workspace
|
|
403
|
-
// try {
|
|
404
|
-
// await this._store.remove(this._getRealPath(logicalPath));
|
|
405
|
-
// } catch (error) {
|
|
406
|
-
// if (error.code !== 'ENOENT') throw error;
|
|
407
|
-
// }
|
|
408
|
-
// }
|
|
409
|
-
return count;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async remove(logicalPath, options) {
|
|
413
|
-
return this._transact(async () => {
|
|
414
|
-
if (options?.purge) {
|
|
415
|
-
// completely remove content from all versions
|
|
416
|
-
let ver = this._inventory.versionNumber.previous();
|
|
417
|
-
while (ver) {
|
|
418
|
-
this._inventory.delete(logicalPath, ver.toString());
|
|
419
|
-
ver = ver.previous();
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
if (this._inventory.delete(logicalPath)) {
|
|
423
|
-
return this._store.remove(this._getRealPath(logicalPath));
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* A function wrapper to provide safe mutating operation that
|
|
430
|
-
* will check if a transaction is already committed and track any unfinished promises.
|
|
431
|
-
* @template T
|
|
432
|
-
* @param {() => Promise<T>} op
|
|
433
|
-
*/
|
|
434
|
-
async _transact(op) {
|
|
435
|
-
if (this._committed) throw new Error('Transaction already commited');
|
|
436
|
-
/** @type {Promise<any> & { done?: boolean }} */
|
|
437
|
-
const p = op();
|
|
438
|
-
p.finally(() => { p.done = true; });
|
|
439
|
-
this._queue.push(p);
|
|
440
|
-
return p;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
module.exports = {
|
|
446
|
-
OcflObjectTransaction,
|
|
447
|
-
OcflObjectTransactionImpl
|
|
448
|
-
};
|
package/lib/utils.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
//const validation = require('./validation.js');
|
|
5
|
-
const { OCFL_VERSIONS, NAMASTE_T } = require('./constants').OcflConstants;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** @typedef {import('./store').OcflStore} OcflStore */
|
|
9
|
-
/** @typedef {import('./types.js').OcflVersion} OcflVersion */
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
/**
|
|
14
|
-
* @template T
|
|
15
|
-
* @param {T[]} inputStack - An array in which each element will be passed to the asyncFn function callback. This array will be consumed during the process.
|
|
16
|
-
* @param {function(T):Promise} asyncFn - An async function that will be run in parallel
|
|
17
|
-
* @param {number} count - Maximum number of concurrency
|
|
18
|
-
*/
|
|
19
|
-
async function parallelize(inputStack, asyncFn, count = 10) {
|
|
20
|
-
let promises = [];
|
|
21
|
-
let len = inputStack.length > count ? count : inputStack.length;
|
|
22
|
-
let result = [];
|
|
23
|
-
while (len--) {
|
|
24
|
-
promises.push((async () => {
|
|
25
|
-
let input;
|
|
26
|
-
while (inputStack.length) {
|
|
27
|
-
let input = inputStack.pop();
|
|
28
|
-
let index = inputStack.length;
|
|
29
|
-
try {
|
|
30
|
-
result[index] = await asyncFn(input);
|
|
31
|
-
} catch (error) {
|
|
32
|
-
result[index] = error;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
})().catch(err => { }));
|
|
36
|
-
}
|
|
37
|
-
await Promise.all(promises);
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function isDirEmpty(store, dirPath) {
|
|
42
|
-
let dir, de;
|
|
43
|
-
try {
|
|
44
|
-
dir = await store.opendir(dirPath);
|
|
45
|
-
de = await dir.read();
|
|
46
|
-
await dir.close();
|
|
47
|
-
} catch (error) {
|
|
48
|
-
return error.code === 'ENOENT';
|
|
49
|
-
}
|
|
50
|
-
return !de;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Check if there is any valid namaste in the rootPath and return the version number
|
|
55
|
-
* @param {OcflStore} store
|
|
56
|
-
* @param {string} prefix
|
|
57
|
-
* @param {string} rootPath
|
|
58
|
-
*/
|
|
59
|
-
async function findNamasteVersion(store, prefix, rootPath) {
|
|
60
|
-
let namastePath = rootPath + '/' + NAMASTE_T + prefix;
|
|
61
|
-
let version;
|
|
62
|
-
try {
|
|
63
|
-
version = await Promise.any(OCFL_VERSIONS.map(async (v) =>
|
|
64
|
-
/**@type {string}*/(await store.readFile(namastePath + v, 'utf8')) === prefix + v + '\n' ? v : null));
|
|
65
|
-
if (!version) return ''; // namaste exists but invalid
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (!error.errors || error.errors.some(e => e.code !== 'ENOENT')) throw error;
|
|
68
|
-
}
|
|
69
|
-
return /** @type {OcflVersion} */(version);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** @typedef {ArrayLike<number> & { buffer: ArrayBuffer, byteLength: number, set(array: ArrayLike<number>, offset?: number): void }} TypedArray */
|
|
73
|
-
/**
|
|
74
|
-
* @param {any} obj
|
|
75
|
-
* @returns {obj is TypedArray}
|
|
76
|
-
*/
|
|
77
|
-
function isTypedArray(obj) {
|
|
78
|
-
return ArrayBuffer.isView(obj) && !(obj instanceof DataView);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
*
|
|
83
|
-
* @param {ArrayBuffer|TypedArray} target
|
|
84
|
-
* @param {ArrayBuffer|TypedArray} source
|
|
85
|
-
* @returns {TypedArray}
|
|
86
|
-
*/
|
|
87
|
-
function joinTypedArray(target, source) {
|
|
88
|
-
const sourceArray = isTypedArray(source) ? source : new Uint8Array(source);
|
|
89
|
-
const [targetBuffer, targetArray] = isTypedArray(target) ? [/** @type {ArrayBuffer}*/(target.buffer), target] : [target, new Uint8Array(target)];
|
|
90
|
-
const targetLength = targetArray.byteLength;
|
|
91
|
-
const totalSize = targetLength + sourceArray.byteLength;
|
|
92
|
-
let mergedArray = targetArray;
|
|
93
|
-
if (targetBuffer.resizable && targetBuffer.maxByteLength >= totalSize) {
|
|
94
|
-
targetBuffer.resize(totalSize);
|
|
95
|
-
} else {
|
|
96
|
-
mergedArray = new Uint8Array(totalSize);
|
|
97
|
-
mergedArray.set(targetArray, 0);
|
|
98
|
-
}
|
|
99
|
-
mergedArray.set(sourceArray, targetLength);
|
|
100
|
-
return mergedArray;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
*
|
|
105
|
-
* @param {string} absOrRelPath
|
|
106
|
-
* @return {string}
|
|
107
|
-
*/
|
|
108
|
-
// toAbsPath(absOrRelPath = '') {
|
|
109
|
-
// if (!path.isAbsolute(absOrRelPath)) return path.join(this.root, absOrRelPath);
|
|
110
|
-
// return absOrRelPath;
|
|
111
|
-
// }
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const testSymbol = Symbol('testSymbol');
|
|
115
|
-
|
|
116
|
-
module.exports = {
|
|
117
|
-
parallelize,
|
|
118
|
-
isDirEmpty,
|
|
119
|
-
findNamasteVersion,
|
|
120
|
-
joinTypedArray,
|
|
121
|
-
testSymbol
|
|
122
|
-
};
|