@ocfl/ocfl 0.3.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.
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 +83 -0
  16. package/dist/extension.d.ts.map +1 -0
  17. package/dist/extension.js +170 -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 +395 -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 -50
  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 +206 -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 +456 -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 -129
  131. package/lib/index.js +0 -13
  132. package/lib/inventory.js +0 -564
  133. package/lib/object.js +0 -451
  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 -123
  138. package/lib/validation.js +0 -174
package/src/utils.ts ADDED
@@ -0,0 +1,94 @@
1
+ import { OcflConstants } from './constants.ts';
2
+ import type { OcflStore } from './store.ts';
3
+ import type { OcflVersion } from './types.ts';
4
+
5
+ const { OCFL_VERSIONS, NAMASTE_T } = OcflConstants;
6
+
7
+ function isErrnoException(error: unknown): error is NodeJS.ErrnoException {
8
+ return error instanceof Error && 'code' in error;
9
+ }
10
+
11
+ /**
12
+ * Run `asyncFn` over every element of `inputStack` with at most `count` calls in flight.
13
+ * Rejections are returned in the result array rather than thrown.
14
+ * @param inputStack - Consumed during the process.
15
+ * @param count - Maximum number of concurrency
16
+ */
17
+ async function parallelize<T, R>(inputStack: T[], asyncFn: (input: T) => Promise<R>, count = 10): Promise<(R | Error)[]> {
18
+ const promises: Promise<void>[] = [];
19
+ let len = Math.min(inputStack.length, count);
20
+ const result: (R | Error)[] = [];
21
+ while (len--) {
22
+ promises.push(
23
+ (async () => {
24
+ while (inputStack.length) {
25
+ const input = inputStack.pop() as T;
26
+ const index = inputStack.length;
27
+ try {
28
+ result[index] = await asyncFn(input);
29
+ } catch (error) {
30
+ result[index] = error instanceof Error ? error : new Error(String(error), { cause: error });
31
+ }
32
+ }
33
+ })().catch(() => {}),
34
+ );
35
+ }
36
+ await Promise.all(promises);
37
+ return result;
38
+ }
39
+
40
+ async function isDirEmpty(store: OcflStore, dirPath: string): Promise<boolean> {
41
+ let de: unknown;
42
+ try {
43
+ const dir = await store.opendir(dirPath);
44
+ de = await dir.read();
45
+ await dir.close();
46
+ } catch (error) {
47
+ return isErrnoException(error) && error.code === 'ENOENT';
48
+ }
49
+ return !de;
50
+ }
51
+
52
+ /**
53
+ * Check if there is any valid namaste in the rootPath and return the version number.
54
+ * @returns The version, `''` if a namaste exists but is invalid, or `undefined` if there is none.
55
+ */
56
+ async function findNamasteVersion(store: OcflStore, prefix: string, rootPath: string): Promise<OcflVersion | '' | undefined> {
57
+ const namastePath = `${rootPath}/${NAMASTE_T}${prefix}`;
58
+ let version: OcflVersion | null | undefined;
59
+ try {
60
+ version = await Promise.any(OCFL_VERSIONS.map(async (v) => ((await store.readFile(namastePath + v, 'utf8')) === `${prefix}${v}\n` ? v : null)));
61
+ if (!version) {
62
+ return '';
63
+ }
64
+ } catch (error) {
65
+ if (!(error instanceof AggregateError) || error.errors.some((e) => !isErrnoException(e) || e.code !== 'ENOENT')) {
66
+ throw error;
67
+ }
68
+ }
69
+ return version ?? undefined;
70
+ }
71
+
72
+ type TypedArray = ArrayLike<number> & { buffer: ArrayBufferLike; byteLength: number; set(array: ArrayLike<number>, offset?: number): void };
73
+
74
+ function isTypedArray(obj: unknown): obj is TypedArray {
75
+ return ArrayBuffer.isView(obj) && !(obj instanceof DataView);
76
+ }
77
+
78
+ function joinTypedArray(target: ArrayBuffer | TypedArray, source: ArrayBuffer | TypedArray): TypedArray {
79
+ const sourceArray = isTypedArray(source) ? source : new Uint8Array(source);
80
+ const [targetBuffer, targetArray] = isTypedArray(target) ? [target.buffer as ArrayBuffer, target] : [target, new Uint8Array(target)];
81
+ const targetLength = targetArray.byteLength;
82
+ const totalSize = targetLength + sourceArray.byteLength;
83
+ let mergedArray = targetArray;
84
+ if (targetBuffer.resizable && targetBuffer.maxByteLength >= totalSize) {
85
+ targetBuffer.resize(totalSize);
86
+ } else {
87
+ mergedArray = new Uint8Array(totalSize);
88
+ mergedArray.set(targetArray, 0);
89
+ }
90
+ mergedArray.set(sourceArray, targetLength);
91
+ return mergedArray;
92
+ }
93
+
94
+ export { findNamasteVersion, isDirEmpty, isErrnoException, joinTypedArray, parallelize };
@@ -0,0 +1,46 @@
1
+ // Messages for the OCFL 1.1 validation codes this library raises: https://ocfl.io/1.1/spec/validation-codes.html
2
+ const ERRORS = {
3
+ E007: 'The text contents of the [version declaration] file must be the same as dvalue, followed by a newline (\\n).',
4
+ E036: 'An OCFL Object Inventory MUST include the following keys: [id, type, digestAlgorithm, head].',
5
+ E040: '[head] MUST be the version directory name with the highest version number.',
6
+ E050: 'The keys of [the state JSON object] are digest values, each of which MUST correspond to an entry in the manifest of the inventory.',
7
+ E060: 'The digest sidecar file must contain the digest of the inventory file.',
8
+ E069: 'An OCFL Storage Root MUST contain a Root Conformance Declaration identifying it as such.',
9
+ E080: 'The text contents of [the OCFL version declaration file] MUST be the same as dvalue, followed by a newline (\\n).',
10
+ E082: 'OCFL Object Roots MUST be stored either as the terminal resource at the end of a directory storage hierarchy or as direct children of a containing OCFL Storage Root.',
11
+ };
12
+
13
+ type ErrorCode = keyof typeof ERRORS;
14
+
15
+ interface ValidationErrorOptions {
16
+ code: ErrorCode;
17
+ description: string;
18
+ path: string;
19
+ detail?: string | undefined;
20
+ }
21
+
22
+ class ValidationError extends Error {
23
+ declare code: ErrorCode;
24
+ declare description: string;
25
+ declare path: string;
26
+ declare detail?: string | undefined;
27
+
28
+ constructor(options: ValidationErrorOptions) {
29
+ super(`${options.code}: ${options.description} At ${options.path}${options.detail ? ` (${options.detail})` : ''}`);
30
+ this.name = 'ValidationError';
31
+ Object.assign(this, options);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @param code - OCFL validation code, eg 'E060'
37
+ * @param path - Path the problem was found at
38
+ * @param detail - Extra context appended to the message
39
+ */
40
+ export const createError = (code: ErrorCode, path: string, detail?: string) => {
41
+ const description = Object.hasOwn(ERRORS, code) ? ERRORS[code] : undefined;
42
+ if (!description) {
43
+ throw new RangeError(`Unknown OCFL validation code ${code}`);
44
+ }
45
+ return new ValidationError({ code, description, path, detail });
46
+ };
package/index.js DELETED
@@ -1,129 +0,0 @@
1
- /**
2
- * @module ocfl
3
- */
4
-
5
- const { OcflConstants, OcflDigest, OcflExtension, OcflStorageLayout, OcflStore,
6
- OcflObject, OcflObjectInventory, OcflObjectTransactionImpl,
7
- OcflStorage, OcflStorageImpl,
8
- extensions
9
- } = require('./lib/index.js');
10
-
11
- /**
12
- * @typedef {Object} StorageLayout
13
- * @property {typeof extensions.FlatDirectStorageLayout} FlatDirectStorageLayout
14
- * @property {typeof extensions.FlatOmitPrefixStorageLayout} FlatOmitPrefixStorageLayout
15
- * @property {typeof extensions.HashAndIdNTupleStorageLayout} HashAndIdNTupleStorageLayout
16
- * @property {typeof extensions.HashedNTupleStorageLayout} HashedNTupleStorageLayout
17
- * @property {typeof extensions.NTupleOmitPrefixStorageLayout} NTupleOmitPrefixStorageLayout
18
- * @property {typeof extensions.PathDirectStorageLayout} PathDirectStorageLayout
19
- */
20
-
21
- /** @typedef {import('./lib/types').OcflExtensionConfig} OcflExtensionConfig */
22
- /** @typedef {import('./lib/types').OcflObjectConfig} OcflObjectConfig */
23
- /** @typedef {import('./lib/types').OcflStorageConfig} OcflStorageConfig */
24
-
25
- /**
26
- * Create a storage layout from extension name or config
27
- * @param {string|OcflExtensionConfig} config - Layout name or config
28
- */
29
- function layout(config) {
30
- let name, c;
31
- if (typeof config === 'string') {
32
- name = config;
33
- } else {
34
- name = config.extensionName;
35
- c = config;
36
- }
37
- return OcflStorageLayout.class(name).create(c);
38
- }
39
- let storageLayout = Object.assign(layout, /** @type {StorageLayout} */(OcflStorageLayout.layout))
40
-
41
- /**
42
- * Creates a new OCFL core API with a specific backend store.
43
- * Any backend store implementation module must return the result of this method.
44
- * @param {typeof OcflStore} store
45
- * @param {SO} defaultOptions
46
- * @template SO
47
- * @returns Top level OCFL API interface that provides storage and object creation.
48
- */
49
- function implementOcfl(store, defaultOptions) {
50
- return {
51
- OCFL_VERSIONS: OcflConstants.OCFL_VERSIONS,
52
-
53
- /** Built-in storage layout classes */
54
- StorageLayout: OcflStorageLayout.layout,
55
-
56
- /**
57
- * Instantiate a storage layout from extension name or config
58
- * @param {string | OcflExtensionConfig} config - Layout name or config
59
- */
60
- storageLayout(config) {
61
- let name, c;
62
- if (typeof config === 'string') {
63
- name = config;
64
- } else {
65
- name = config.extensionName;
66
- c = config;
67
- }
68
- let cls = OcflStorageLayout.class(name)
69
- if (!cls) throw new Error(`Layout "${name}" is not supported.`);
70
- return cls.create(c);
71
- },
72
-
73
- /**
74
- * Instantiate an OCFL Object
75
- * @param {OcflObjectConfig} config
76
- * @param {SO} [storeOptions]
77
- */
78
- object(config, storeOptions = defaultOptions) {
79
- return new OcflObject(config, store.getInstance(storeOptions));
80
- },
81
-
82
- /**
83
- * Instantiate an OCFL Storage root
84
- * @param {OcflStorageConfig} config
85
- * @param {SO} [storeOptions]
86
- * @return {OcflStorage}
87
- */
88
- storage(config, storeOptions = defaultOptions) {
89
- return new OcflStorageImpl(config, store.getInstance(storeOptions));
90
- },
91
-
92
- /**
93
- * Create a new OCFL Storage
94
- * @param {OcflStorageConfig} config
95
- * @param {SO} [storeOptions]
96
- * @return {Promise<OcflStorage>}
97
- */
98
- async createStorage(config, storeOptions = defaultOptions) {
99
- return (new OcflStorageImpl(config, store.getInstance(storeOptions))).create();
100
- },
101
-
102
- /**
103
- * Load an existing OCFL Storage
104
- * @param {OcflStorageConfig} config
105
- * @param {SO} [storeOptions]
106
- * @return {Promise<OcflStorage>}
107
- */
108
- async loadStorage(config, storeOptions = defaultOptions) {
109
- return (new OcflStorageImpl(config, store.getInstance(storeOptions))).load();
110
- }
111
- }
112
- }
113
-
114
- module.exports = {
115
- OcflConstants,
116
- OcflDigest,
117
- OcflExtension, OcflStorageLayout,
118
- OcflStore,
119
- OcflObject,
120
- OcflObjectInventory,
121
- OcflObjectTransactionImpl,
122
- OcflStorage, OcflStorageImpl,
123
- implementOcfl,
124
- extensions
125
- };
126
-
127
- for (let name in extensions) {
128
- extensions[name].register();
129
- }
package/index.mjs DELETED
@@ -1,18 +0,0 @@
1
- //@ts-check
2
- // export * from './lib/storage.js';
3
- // export * from './lib/object.js';
4
- // export * from './lib/digest.js';
5
- //export * from './lib/extension.js';
6
- //export * from './lib/constants.js';
7
-
8
- export * from './index.js';
9
- import * as ocfl from './index.js';
10
- export default ocfl;
11
- //export const { DIGEST, DIGEST_FIXITY, INVENTORY_NAME, NAMASTE_PREFIX_OBJECT, NAMASTE_PREFIX_STORAGE, OCFL_VERSION } = ocfl
12
- // export const { OcflObject, OcflObjectInventory, OcflObjectTransaction, OcflObjectTransactionImpl,
13
- // OcflStorage, OcflStorageImpl,
14
- // createProxy, digestFrom, digestFromStream,
15
- // DIGEST, DIGEST_FIXITY, INVENTORY_NAME, NAMASTE_PREFIX_OBJECT, NAMASTE_PREFIX_STORAGE, OCFL_VERSION } = ocfl;
16
- //export const {} = ocfl;
17
- //export { default as ocfl } from './index.js';
18
- //export {Storage, Object} from './index.js';
package/lib/constants.js DELETED
@@ -1,24 +0,0 @@
1
- /**
2
- * @module
3
- * default constants
4
- */
5
- // module.exports = {
6
- // OCFL_VERSION: 1.1,
7
- // NAMASTE_PREFIX_OBJECT: '0=ocfl_object_',
8
- // NAMASTE_PREFIX_STORAGE: '0=ocfl_',
9
- // INVENTORY_NAME: 'inventory.json'
10
- // };
11
- const OcflConstants = {
12
- OCFL_VERSION: '1.1',
13
- OCFL_VERSIONS: Object.freeze(['1.1', '1.0']),
14
- OCFL_LAYOUT: 'ocfl_layout.json',
15
- NAMASTE_T: '0=',
16
- NAMASTE_PREFIX_OBJECT: 'ocfl_object_',
17
- NAMASTE_PREFIX_STORAGE: 'ocfl_',
18
- INVENTORY_NAME: 'inventory.json',
19
- EXTENSIONS_DIR: 'extensions',
20
- EXTENSION_CONFIG: 'config.json',
21
- DIGEST_ALGORITHM: 'sha512'
22
- };
23
-
24
- module.exports = { OcflConstants };
package/lib/digest.js DELETED
@@ -1,307 +0,0 @@
1
- //@ts-check
2
-
3
- const hashWasm = require('hash-wasm');
4
- const { sha512_256 } = require('js-sha512'); // fallback for sha512/256 algorithm, which is not included in hash-wasm
5
- //const worker = require('node:worker_threads');
6
- const { enumeration } = require('./enum.js');
7
- const { testSymbol } = require('./utils.js');
8
-
9
- const CONTENT = enumeration(['sha256', 'sha512']);
10
- const FIXITY = enumeration(['sha256', 'sha512', 'md5', 'sha1', 'blake2b-512', 'blake2b-160', 'blake2b-256', 'blake2b-384', 'sha512/256', 'size', 'crc32']);
11
-
12
- /**
13
- * @typedef {((outputType: "binary") => Uint8Array) & ((outputType?: "hex") => string)} IDigest
14
- * @typedef {((outputType: "binary") => {[key:string]: Uint8Array}) & ((outputType?: "hex") => {[key:string]: string})} IMultiDigest
15
- * @typedef {WritableStream & { update(data: IDataType): void, digest: IDigest }} WritableStreamHasher
16
- * @typedef {WritableStream & { update(data: IDataType): void, digest: IMultiDigest }} WritableStreamMultiHasher
17
- */
18
-
19
- /**
20
- * @type { Object.<string, CommonHasher[]> }
21
- */
22
- const hasherCache = {};
23
- /**
24
- * @type { Object.<string, CommonHasher> }
25
- */
26
- const hasherSyncCache = {};
27
- // for (let algo of FIXITY) {
28
- // algorithms[algo] = function () { return crypto.createHash(algo); }
29
- // }
30
-
31
- /** @type { WeakMap<CommonHasher, boolean>} */
32
- const hasherPending = new WeakMap();
33
-
34
- /** @type { {[key: string]: () => Promise<CommonHasher>} } */
35
- const hasherFactory = {};
36
-
37
- /**
38
- * Register a hash algorithm and it's factory function.
39
- * @param {string} name
40
- * @param {() => Promise<CommonHasher>} factory
41
- */
42
- function registerHasherFactory(name, factory) {
43
- if (typeof factory === 'function') {
44
- hasherFactory[name] = factory;
45
- }
46
- }
47
-
48
- function hasAlgorithm(name) {
49
- return name in hasherFactory;
50
- }
51
-
52
- (function init() {
53
- for (const algo of FIXITY) {
54
- if (algo.startsWith('blake2b')) {
55
- const bits = parseInt(algo.split('-')[1]);
56
- registerHasherFactory(algo, async () => hashWasm.createBLAKE2b(bits));
57
- } else if (algo === 'sha512/256') {
58
- registerHasherFactory(algo, async () => {
59
- let h = sha512_256.create();
60
- /** @type {CommonHasher} */
61
- const hasher = {
62
- digestSize: 32,
63
- init() { h = sha512_256.create(); return this; },
64
- update(data) { h.update(/** @type {any} */(data)); return this },
65
- // @ts-ignore
66
- digest(encoding) {
67
- if (encoding === 'binary') return new Uint8Array(h.digest());
68
- else return h.hex();
69
- }
70
- };
71
- return hasher;
72
- });
73
- } else if (algo === 'size') {
74
- registerHasherFactory(algo, async () => {
75
- let encoder;
76
- let size = 0;
77
- /** @type {CommonHasher} */
78
- const hasher = {
79
- digestSize: 1,
80
- init() {
81
- size = 0;
82
- encoder = new TextEncoder();
83
- return this;
84
- },
85
- update(data) {
86
- if (data) {
87
- if (typeof data === 'string') size += encoder.encode(data).length;
88
- else size += data.byteLength;
89
- }
90
- return this;
91
- },
92
- // @ts-ignore
93
- digest(encoding) {
94
- if (encoding === 'binary') return new Uint8Array(new BigUint64Array([BigInt(size)]).buffer);
95
- else return '' + size;
96
- }
97
- };
98
- return hasher;
99
- });
100
- } else {
101
- registerHasherFactory(algo, hashWasm['create' + algo.toUpperCase()]);
102
- }
103
- }
104
- })();
105
- // (async () => {
106
- // for (const name in hasherFactory) {
107
- // const factory = hasherFactory[name];
108
- // const h = await factory();
109
- // console.log(name);
110
- // console.log(h);
111
- // console.log(h.init().update('test').digest().length)
112
- // }
113
- // })();
114
-
115
- /**
116
- *
117
- * @param {*} hasher
118
- * @returns {hasher is MultiHasher}
119
- */
120
- function isMulti(hasher) {
121
- return hasher.multi;
122
- }
123
-
124
- /**
125
- * Create a hasher instance for the given algorithm or get it from cache if available.
126
- * @param {string} algorithm
127
- */
128
- async function createHasher(algorithm) {
129
- const cacheArray = hasherCache[algorithm];
130
- let hash = (cacheArray ?? []).find(hasher => !hasherPending.get(hasher));
131
- if (!hash) {
132
- const factory = hasherFactory[algorithm];
133
- if (!factory) throw new Error(`Unsupported digest algorithm: ${algorithm}`);
134
- hash = await factory();
135
- if (cacheArray) cacheArray.push(hash);
136
- else hasherCache[algorithm] = [hash];
137
- }
138
- hasherPending.set(hash, true);
139
- hash.init();
140
- return hash;
141
- }
142
-
143
- /**
144
- * Create a hasher instance for calculating multiple types of digest in one go.
145
- * @param {string|string[]} algorithms
146
- */
147
- async function createMultiHasher(algorithms) {
148
- const algoNames = Array.isArray(algorithms) ? (algorithms.length ? [...(new Set(algorithms))] : ['sha512']) : [algorithms];
149
- const hashers = await Promise.all(algoNames.map(createHasher));
150
- return /** @type { MultiHasher } */({
151
- multi: true,
152
- update(data) {
153
- for (const hash of hashers) {
154
- hash.update(data);
155
- }
156
- return this;
157
- },
158
- digest(encoding) {
159
- const result = {};
160
- for (let i = 0; i < hashers.length; i++) {
161
- hasherPending.set(hashers[i], false);
162
- result[algoNames[i]] = hashers[i].digest(encoding);
163
- }
164
- return result;
165
- }
166
- });
167
- }
168
-
169
- /**
170
- * Create a hash instance that implements WritableStream.
171
- * @overload
172
- * @param {string} algorithm One or more digest algorithm names
173
- * @return {Promise<WritableStreamHasher>}
174
- */
175
- /**
176
- * @overload
177
- * @param {string[]} algorithm One or more digest algorithm names
178
- * @return {Promise<WritableStreamMultiHasher>}
179
- */
180
- async function createStream(algorithm = 'sha512') {
181
- const hash = await (Array.isArray(algorithm) ? createMultiHasher(algorithm) : createHasher(algorithm));
182
- /** @type {any} */
183
- const ws = new WritableStream({
184
- write(chunk) {
185
- hash.update(chunk);
186
- }
187
- });
188
- ws.update = function (data) {
189
- hash.update(data);
190
- };
191
- ws.digest = function (encoding) {
192
- if (!isMulti(hash)) hasherPending.set(hash, false);
193
- return hash.digest(encoding);
194
- }
195
- return ws;
196
- }
197
-
198
- /** @typedef {TransformStream & { digest: IMultiDigest }} StreamThroughHash */
199
- /**
200
- * Create a pass through stream that pass data as it is and calculate the digest as data pass through.
201
- * @param {string|string[]} [algorithm]
202
- * @param {object} [options]
203
- * @return {Promise<StreamThroughHash>}
204
- */
205
- async function createStreamThrough(algorithm = 'sha512', options) {
206
- const hash = await (Array.isArray(algorithm) ? createMultiHasher(algorithm) : createHasher(algorithm));
207
- const { writableStrategy, readableStrategy } = options || {};
208
- //hash.setEncoding('hex');
209
- /** @type {any} */
210
- const ts = new TransformStream({
211
- transform(chunk, controller) {
212
- controller.enqueue(chunk);
213
- hash.update(chunk);
214
- }
215
- }, writableStrategy, readableStrategy);
216
- ts.digest = function (encoding) {
217
- if (!isMulti(hash)) hasherPending.set(hash, false);
218
- return hash.digest(encoding);
219
- }
220
- return ts;
221
- }
222
-
223
- /**
224
- *
225
- * @overload
226
- * @param {string[]} algorithm
227
- * @param {IDataType | ReadableStream} input
228
- * @return {Promise<{[key:string]: string}>}
229
- */
230
- /**
231
- * @overload
232
- * @param {string} algorithm
233
- * @param {IDataType | ReadableStream} input
234
- * @return {Promise<string>}
235
- */
236
- async function digest(algorithm, input) {
237
- if (input instanceof ReadableStream) {
238
- const hash = await createStream(algorithm);
239
- await input.pipeTo(hash);
240
- return hash.digest();
241
- } else {
242
- const hash = await (Array.isArray(algorithm) ? createMultiHasher(algorithm) : createHasher(algorithm));
243
- if (!isMulti(hash)) hasherPending.set(hash, false);
244
- return hash.update(input).digest();
245
- }
246
- }
247
-
248
- async function initSync(algorithm) {
249
- if (!hasherSyncCache[algorithm]) {
250
- const factory = hasherFactory[algorithm];
251
- if (!factory) throw new Error(`Unsupported digest algorithm: ${algorithm}`);
252
- const hasher = await factory();
253
- hasherSyncCache[algorithm] = hasher;
254
- }
255
- }
256
-
257
- /**
258
- * For this sync function to work correctly, it requires a call to initSync() to be awaited for each algorithm before calling this function.
259
- * @param {string} algorithm
260
- * @param {IDataType} input
261
- */
262
- function digestSync(algorithm, input) {
263
- const hasher = hasherSyncCache[algorithm];
264
- if (!hasher) throw new Error(`Hasher for algorithm ${algorithm} is not initialized. Please call and await initSync('${algorithm}') before calling this function.`);
265
- return hasher.init().update(input).digest();
266
- }
267
-
268
- const HEX_DIGEST_LENGTH = {
269
- 'md5': 32,
270
- 'sha1': 40,
271
- 'sha256': 64,
272
- 'sha512/256': 64,
273
- 'sha512': 128,
274
- 'blake2b-160': 40,
275
- 'blake2b-256': 64,
276
- 'blake2b-384': 96,
277
- 'blake2b-512': 128,
278
- 'crc32': 8,
279
- 'size': 1
280
- };
281
-
282
- function getHexDigestLength(algorithm) {
283
- return HEX_DIGEST_LENGTH[algorithm] || 0;
284
- }
285
-
286
- const OcflDigest = {
287
- CONTENT,
288
- FIXITY,
289
- // algorithms,
290
- createStream, createStreamThrough, digest,
291
- registerHasherFactory, hasAlgorithm, getHexDigestLength,
292
- digestSync, initSync
293
- };
294
-
295
- module.exports = {
296
- OcflDigest,
297
- [testSymbol]: {
298
- hasherCache
299
- }
300
- // DIGEST,
301
- // DIGEST_FIXITY,
302
- // digestSync,
303
- // digestAsync,
304
- // digestFromStream,
305
- // digestStream,
306
- // HashThrough
307
- };