@miso.ai/server-commons 0.6.5-beta.0 → 0.6.5-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.
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/store.js +126 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -14,3 +14,4 @@ export * as yargs from './yargs.js';
|
|
|
14
14
|
export { default as Resolution } from './resolution.js';
|
|
15
15
|
export { default as TaskQueue } from './task-queue.js';
|
|
16
16
|
export { default as RateLimitingQueue } from './rate-limiting-queue.js';
|
|
17
|
+
export { default as HashStore } from './store.js';
|
package/src/store.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { Transform } from 'stream';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_FLUSH_THRESHOLD = 100;
|
|
6
|
+
|
|
7
|
+
export default class HashStore {
|
|
8
|
+
|
|
9
|
+
constructor({ file, hashFn, flushThreshold = DEFAULT_FLUSH_THRESHOLD } = {}) {
|
|
10
|
+
if (!file) {
|
|
11
|
+
throw new Error('File path is required');
|
|
12
|
+
}
|
|
13
|
+
if (!hashFn) {
|
|
14
|
+
throw new Error('Hash function is required');
|
|
15
|
+
}
|
|
16
|
+
this._file = file;
|
|
17
|
+
this._hashFn = hashFn;
|
|
18
|
+
this._flushThreshold = flushThreshold;
|
|
19
|
+
this._hashes = undefined;
|
|
20
|
+
this._pending = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async purge() {
|
|
24
|
+
// peek data length
|
|
25
|
+
const length = (await this._read()).length;
|
|
26
|
+
|
|
27
|
+
this._hashes = new Set();
|
|
28
|
+
// delete file
|
|
29
|
+
try {
|
|
30
|
+
await fs.unlink(this._file);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (err.code !== 'ENOENT') {
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async load() {
|
|
41
|
+
this._hashes = new Set(await this._read());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get() {
|
|
45
|
+
return this._hashes;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
contains(item) {
|
|
49
|
+
return this._hashes.has(this._hashFn(item));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async add(...items) {
|
|
53
|
+
for (const item of items) {
|
|
54
|
+
const hash = this._hashFn(item);
|
|
55
|
+
if (!this._hashes.has(hash)) {
|
|
56
|
+
this._hashes.add(hash);
|
|
57
|
+
this._pending.push(hash);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (this._pending.length >= this._flushThreshold) {
|
|
61
|
+
await this.flush();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async flush() {
|
|
66
|
+
if (this._pending.length === 0) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await this._mkdir();
|
|
70
|
+
await fs.appendFile(this._file, this._pending.join('\n') + '\n');
|
|
71
|
+
this._pending = [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
exclusionStream() {
|
|
75
|
+
return new HashStoreFilterTransform(this, { mode: 'exclude' });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
dedupeStream() {
|
|
79
|
+
return new HashStoreFilterTransform(this, { mode: 'dedupe' });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async _mkdir() {
|
|
83
|
+
const dir = resolve(this._file, '..');
|
|
84
|
+
await fs.mkdir(dir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async _read() {
|
|
88
|
+
try {
|
|
89
|
+
const content = await fs.readFile(this._file, 'utf-8');
|
|
90
|
+
return content.split('\n').map(v => v.trim()).filter(v => v);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err.code !== 'ENOENT') {
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class HashStoreFilterTransform extends Transform {
|
|
102
|
+
|
|
103
|
+
constructor(store, { mode } = {}) {
|
|
104
|
+
super({ objectMode: true });
|
|
105
|
+
this._store = store;
|
|
106
|
+
switch (mode) {
|
|
107
|
+
case 'exclude':
|
|
108
|
+
case 'dedupe':
|
|
109
|
+
break;
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`Unrecognized mode: ${mode}`);
|
|
112
|
+
}
|
|
113
|
+
this._mode = mode;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_transform(item, _, next) {
|
|
117
|
+
if (!this._store.contains(item)) {
|
|
118
|
+
this.push(item);
|
|
119
|
+
if (this._mode === 'dedupe') {
|
|
120
|
+
this._store.add(item);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
next();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|