@ocfl/ocfl 0.4.0 → 1.0.0-beta.2

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.
Files changed (138) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +24 -9
  3. package/dist/constants.d.ts +15 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +14 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/digest.d.ts +58 -0
  8. package/dist/digest.d.ts.map +1 -0
  9. package/dist/digest.js +204 -0
  10. package/dist/digest.js.map +1 -0
  11. package/dist/error.d.ts +5 -0
  12. package/dist/error.d.ts.map +1 -0
  13. package/dist/error.js +9 -0
  14. package/dist/error.js.map +1 -0
  15. package/dist/extension.d.ts +82 -0
  16. package/dist/extension.d.ts.map +1 -0
  17. package/dist/extension.js +169 -0
  18. package/dist/extension.js.map +1 -0
  19. package/dist/extensions/0002-flat-direct-storage-layout.d.ts +12 -0
  20. package/dist/extensions/0002-flat-direct-storage-layout.d.ts.map +1 -0
  21. package/dist/extensions/0002-flat-direct-storage-layout.js +34 -0
  22. package/dist/extensions/0002-flat-direct-storage-layout.js.map +1 -0
  23. package/dist/extensions/0003-hash-and-id-n-tuple-storage-layout.d.ts +15 -0
  24. package/dist/extensions/0003-hash-and-id-n-tuple-storage-layout.d.ts.map +1 -0
  25. package/dist/extensions/0003-hash-and-id-n-tuple-storage-layout.js +49 -0
  26. package/dist/extensions/0003-hash-and-id-n-tuple-storage-layout.js.map +1 -0
  27. package/dist/extensions/0004-hashed-n-tuple-storage-layout.d.ts +17 -0
  28. package/dist/extensions/0004-hashed-n-tuple-storage-layout.d.ts.map +1 -0
  29. package/dist/extensions/0004-hashed-n-tuple-storage-layout.js +54 -0
  30. package/dist/extensions/0004-hashed-n-tuple-storage-layout.js.map +1 -0
  31. package/dist/extensions/0006-flat-omit-prefix-storage-layout.d.ts +6 -0
  32. package/dist/extensions/0006-flat-omit-prefix-storage-layout.d.ts.map +1 -0
  33. package/dist/extensions/0006-flat-omit-prefix-storage-layout.js +8 -0
  34. package/dist/extensions/0006-flat-omit-prefix-storage-layout.js.map +1 -0
  35. package/dist/extensions/0007-n-tuple-omit-prefix-storage-layout.d.ts +6 -0
  36. package/dist/extensions/0007-n-tuple-omit-prefix-storage-layout.d.ts.map +1 -0
  37. package/dist/extensions/0007-n-tuple-omit-prefix-storage-layout.js +8 -0
  38. package/dist/extensions/0007-n-tuple-omit-prefix-storage-layout.js.map +1 -0
  39. package/dist/extensions/0009-digest-algorithms.d.ts +9 -0
  40. package/dist/extensions/0009-digest-algorithms.d.ts.map +1 -0
  41. package/dist/extensions/0009-digest-algorithms.js +11 -0
  42. package/dist/extensions/0009-digest-algorithms.js.map +1 -0
  43. package/dist/extensions/000N-path-direct-storage-layout.d.ts +16 -0
  44. package/dist/extensions/000N-path-direct-storage-layout.d.ts.map +1 -0
  45. package/dist/extensions/000N-path-direct-storage-layout.js +46 -0
  46. package/dist/extensions/000N-path-direct-storage-layout.js.map +1 -0
  47. package/dist/extensions/index.d.ts +23 -0
  48. package/dist/extensions/index.d.ts.map +1 -0
  49. package/dist/extensions/index.js +8 -0
  50. package/dist/extensions/index.js.map +1 -0
  51. package/dist/file.d.ts +87 -0
  52. package/dist/file.d.ts.map +1 -0
  53. package/dist/file.js +118 -0
  54. package/dist/file.js.map +1 -0
  55. package/dist/index.d.ts +61 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +73 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/inventory.d.ts +155 -0
  60. package/dist/inventory.d.ts.map +1 -0
  61. package/dist/inventory.js +591 -0
  62. package/dist/inventory.js.map +1 -0
  63. package/dist/object.d.ts +188 -0
  64. package/dist/object.d.ts.map +1 -0
  65. package/dist/object.js +425 -0
  66. package/dist/object.js.map +1 -0
  67. package/dist/storage.d.ts +94 -0
  68. package/dist/storage.d.ts.map +1 -0
  69. package/dist/storage.js +302 -0
  70. package/dist/storage.js.map +1 -0
  71. package/dist/store.d.ts +100 -0
  72. package/dist/store.d.ts.map +1 -0
  73. package/dist/store.js +157 -0
  74. package/dist/store.js.map +1 -0
  75. package/dist/transaction.d.ts +155 -0
  76. package/dist/transaction.d.ts.map +1 -0
  77. package/dist/transaction.js +394 -0
  78. package/dist/transaction.js.map +1 -0
  79. package/dist/types.d.ts +107 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +2 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/utils.d.ts +24 -0
  84. package/dist/utils.d.ts.map +1 -0
  85. package/dist/utils.js +85 -0
  86. package/dist/utils.js.map +1 -0
  87. package/dist/validation.d.ts +32 -0
  88. package/dist/validation.d.ts.map +1 -0
  89. package/dist/validation.js +31 -0
  90. package/dist/validation.js.map +1 -0
  91. package/package.json +31 -51
  92. package/src/constants.ts +16 -0
  93. package/src/digest.ts +258 -0
  94. package/src/error.ts +9 -0
  95. package/src/extension.ts +204 -0
  96. package/{lib/extensions/0002-flat-direct-storage-layout.js → src/extensions/0002-flat-direct-storage-layout.ts} +15 -22
  97. package/src/extensions/0003-hash-and-id-n-tuple-storage-layout.ts +56 -0
  98. package/src/extensions/0004-hashed-n-tuple-storage-layout.ts +59 -0
  99. package/src/extensions/0006-flat-omit-prefix-storage-layout.ts +9 -0
  100. package/src/extensions/0007-n-tuple-omit-prefix-storage-layout.ts +9 -0
  101. package/src/extensions/0009-digest-algorithms.ts +12 -0
  102. package/src/extensions/000N-path-direct-storage-layout.ts +51 -0
  103. package/src/extensions/index.ts +24 -0
  104. package/src/file.ts +161 -0
  105. package/src/index.ts +92 -0
  106. package/src/inventory.ts +634 -0
  107. package/src/object.ts +473 -0
  108. package/src/storage.ts +342 -0
  109. package/src/store.ts +199 -0
  110. package/src/transaction.ts +455 -0
  111. package/{lib/types.d.ts → src/types.ts} +28 -74
  112. package/src/utils.ts +94 -0
  113. package/src/validation.ts +46 -0
  114. package/index.js +0 -129
  115. package/index.mjs +0 -18
  116. package/lib/constants.js +0 -24
  117. package/lib/digest.js +0 -307
  118. package/lib/enum.js +0 -90
  119. package/lib/error.js +0 -19
  120. package/lib/extension.js +0 -194
  121. package/lib/extensions/0003-hash-and-id-n-tuple-storage-layout.js +0 -60
  122. package/lib/extensions/0004-hashed-n-tuple-storage-layout.js +0 -63
  123. package/lib/extensions/0005-mutable-head.js +0 -0
  124. package/lib/extensions/0006-flat-omit-prefix-storage-layout.js +0 -7
  125. package/lib/extensions/0007-n-tuple-omit-prefix-storage-layout.js +0 -7
  126. package/lib/extensions/0008-schema-registry.js +0 -0
  127. package/lib/extensions/0009-digest-algorithms.js +0 -18
  128. package/lib/extensions/000N-path-direct-storage-layout.js +0 -61
  129. package/lib/extensions/index.js +0 -13
  130. package/lib/file.js +0 -147
  131. package/lib/index.js +0 -13
  132. package/lib/inventory.js +0 -564
  133. package/lib/object.js +0 -499
  134. package/lib/storage.js +0 -338
  135. package/lib/store.js +0 -191
  136. package/lib/transaction.js +0 -448
  137. package/lib/utils.js +0 -122
  138. package/lib/validation.js +0 -174
@@ -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
- };