@atlaspack/cache 3.1.1-canary.0 → 3.1.1-canary.100

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/CHANGELOG.md CHANGED
@@ -1,5 +1,122 @@
1
1
  # @atlaspack/cache
2
2
 
3
+ ## 3.2.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`5ded263`](https://github.com/atlassian-labs/atlaspack/commit/5ded263c7f11b866e8885b81c73e20dd060b25be)]:
8
+ - @atlaspack/feature-flags@2.18.3
9
+ - @atlaspack/fs@2.15.10
10
+ - @atlaspack/utils@2.15.3
11
+
12
+ ## 3.2.9
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [[`644b157`](https://github.com/atlassian-labs/atlaspack/commit/644b157dee72a871acc2d0facf0b87b8eea51956)]:
17
+ - @atlaspack/feature-flags@2.18.2
18
+ - @atlaspack/fs@2.15.9
19
+ - @atlaspack/utils@2.15.2
20
+
21
+ ## 3.2.8
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [[`ef3d622`](https://github.com/atlassian-labs/atlaspack/commit/ef3d6228f4e006702198a19c61e051d194d325cb), [`26aa9c5`](https://github.com/atlassian-labs/atlaspack/commit/26aa9c599d2be45ce1438a74c5fa22f39b9b554b), [`0501255`](https://github.com/atlassian-labs/atlaspack/commit/05012550da35b05ce7d356a8cc29311e7f9afdca)]:
26
+ - @atlaspack/logger@2.14.11
27
+ - @atlaspack/feature-flags@2.18.1
28
+ - @atlaspack/fs@2.15.8
29
+ - @atlaspack/utils@2.15.1
30
+
31
+ ## 3.2.7
32
+
33
+ ### Patch Changes
34
+
35
+ - Updated dependencies [[`10fbcfb`](https://github.com/atlassian-labs/atlaspack/commit/10fbcfbfa49c7a83da5d7c40983e36e87f524a75), [`85c52d3`](https://github.com/atlassian-labs/atlaspack/commit/85c52d3f7717b3c84a118d18ab98cfbfd71dcbd2), [`e39c6cf`](https://github.com/atlassian-labs/atlaspack/commit/e39c6cf05f7e95ce5420dbcea66f401b1cbd397c)]:
36
+ - @atlaspack/feature-flags@2.18.0
37
+ - @atlaspack/utils@2.15.0
38
+ - @atlaspack/fs@2.15.7
39
+
40
+ ## 3.2.6
41
+
42
+ ### Patch Changes
43
+
44
+ - Updated dependencies [[`73ea3c4`](https://github.com/atlassian-labs/atlaspack/commit/73ea3c4d85d4401fdd15abcbf988237e890e7ad3), [`b1b3693`](https://github.com/atlassian-labs/atlaspack/commit/b1b369317c66f8a431c170df2ebba4fa5b2e38ef)]:
45
+ - @atlaspack/feature-flags@2.17.0
46
+ - @atlaspack/fs@2.15.6
47
+ - @atlaspack/utils@2.14.11
48
+
49
+ ## 3.2.5
50
+
51
+ ### Patch Changes
52
+
53
+ - Updated dependencies [[`0999fb7`](https://github.com/atlassian-labs/atlaspack/commit/0999fb78da519a6c7582d212883e515fcf6c1252), [`35fdd4b`](https://github.com/atlassian-labs/atlaspack/commit/35fdd4b52da0af20f74667f7b8adfb2f90279b7c), [`6dd4ccb`](https://github.com/atlassian-labs/atlaspack/commit/6dd4ccb753541de32322d881f973d571dd57e4ca)]:
54
+ - @atlaspack/fs@2.15.5
55
+ - @atlaspack/rust@3.3.5
56
+ - @atlaspack/logger@2.14.10
57
+ - @atlaspack/utils@2.14.10
58
+
59
+ ## 3.2.4
60
+
61
+ ### Patch Changes
62
+
63
+ - [#583](https://github.com/atlassian-labs/atlaspack/pull/583) [`124b7ff`](https://github.com/atlassian-labs/atlaspack/commit/124b7fff44f71aac9fbad289a9a9509b3dfc9aaa) Thanks [@yamadapc](https://github.com/yamadapc)! - Fix problem where cache writes could start to fail during a V3 build
64
+
65
+ - Updated dependencies [[`124b7ff`](https://github.com/atlassian-labs/atlaspack/commit/124b7fff44f71aac9fbad289a9a9509b3dfc9aaa), [`e052521`](https://github.com/atlassian-labs/atlaspack/commit/e0525210850ed1606146eb86991049cf567c5dec), [`15c6d70`](https://github.com/atlassian-labs/atlaspack/commit/15c6d7000bd89da876bc590aa75b17a619a41896), [`e4d966c`](https://github.com/atlassian-labs/atlaspack/commit/e4d966c3c9c4292c5013372ae65b10d19d4bacc6), [`209692f`](https://github.com/atlassian-labs/atlaspack/commit/209692ffb11eae103a0d65c5e1118a5aa1625818), [`42a775d`](https://github.com/atlassian-labs/atlaspack/commit/42a775de8eec638ad188f3271964170d8c04d84b), [`29c2f10`](https://github.com/atlassian-labs/atlaspack/commit/29c2f106de9679adfb5afa04e1910471dc65a427), [`f4da1e1`](https://github.com/atlassian-labs/atlaspack/commit/f4da1e120e73eeb5e8b8927f05e88f04d6148c7b), [`1ef91fc`](https://github.com/atlassian-labs/atlaspack/commit/1ef91fcc863fdd2831511937083dbbc1263b3d9d)]:
66
+ - @atlaspack/rust@3.3.4
67
+ - @atlaspack/fs@2.15.4
68
+ - @atlaspack/feature-flags@2.16.0
69
+ - @atlaspack/logger@2.14.9
70
+ - @atlaspack/utils@2.14.9
71
+
72
+ ## 3.2.3
73
+
74
+ ### Patch Changes
75
+
76
+ - Updated dependencies [[`30f6017`](https://github.com/atlassian-labs/atlaspack/commit/30f60175ba4d272c5fc193973c63bc298584775b), [`3a3e8e7`](https://github.com/atlassian-labs/atlaspack/commit/3a3e8e7be9e2dffd7304436d792f0f595d59665a), [`1ab0a27`](https://github.com/atlassian-labs/atlaspack/commit/1ab0a275aeca40350415e2b03e7440d1dddc6228), [`b8a4ae8`](https://github.com/atlassian-labs/atlaspack/commit/b8a4ae8f83dc0a83d8b145c5f729936ce52080a3)]:
77
+ - @atlaspack/feature-flags@2.15.1
78
+ - @atlaspack/fs@2.15.3
79
+ - @atlaspack/rust@3.3.3
80
+ - @atlaspack/utils@2.14.8
81
+ - @atlaspack/logger@2.14.8
82
+
83
+ ## 3.2.2
84
+
85
+ ### Patch Changes
86
+
87
+ - Updated dependencies [[`a1773d2`](https://github.com/atlassian-labs/atlaspack/commit/a1773d2a62d0ef7805ac7524621dcabcc1afe929), [`556d6ab`](https://github.com/atlassian-labs/atlaspack/commit/556d6ab8ede759fa7f37fcd3f4da336ef1c55e8f)]:
88
+ - @atlaspack/feature-flags@2.15.0
89
+ - @atlaspack/logger@2.14.7
90
+ - @atlaspack/rust@3.3.2
91
+ - @atlaspack/fs@2.15.2
92
+ - @atlaspack/utils@2.14.7
93
+
94
+ ## 3.2.1
95
+
96
+ ### Patch Changes
97
+
98
+ - Updated dependencies [[`e0f5337`](https://github.com/atlassian-labs/atlaspack/commit/e0f533757bd1019dbd108a04952c87da15286e09)]:
99
+ - @atlaspack/feature-flags@2.14.4
100
+ - @atlaspack/rust@3.3.1
101
+ - @atlaspack/fs@2.15.1
102
+ - @atlaspack/utils@2.14.6
103
+ - @atlaspack/logger@2.14.6
104
+
105
+ ## 3.2.0
106
+
107
+ ### Minor Changes
108
+
109
+ - [#531](https://github.com/atlassian-labs/atlaspack/pull/531) [`d2c50c2`](https://github.com/atlassian-labs/atlaspack/commit/d2c50c2c020888b33bb25b8690d9320c2b69e2a6) Thanks [@yamadapc](https://github.com/yamadapc)! - Add way to iterate LMDB cache keys
110
+
111
+ ### Patch Changes
112
+
113
+ - Updated dependencies [[`11d6f16`](https://github.com/atlassian-labs/atlaspack/commit/11d6f16b6397dee2f217167e5c98b39edb63f7a7), [`e2ba0f6`](https://github.com/atlassian-labs/atlaspack/commit/e2ba0f69702656f3d1ce95ab1454e35062b13b39), [`d2c50c2`](https://github.com/atlassian-labs/atlaspack/commit/d2c50c2c020888b33bb25b8690d9320c2b69e2a6), [`46a90dc`](https://github.com/atlassian-labs/atlaspack/commit/46a90dccd019a26b222c878a92d23acc75dc67c5), [`4c17141`](https://github.com/atlassian-labs/atlaspack/commit/4c1714103dab2aa9039c488f381551d2b65d1d01)]:
114
+ - @atlaspack/feature-flags@2.14.3
115
+ - @atlaspack/rust@3.3.0
116
+ - @atlaspack/fs@2.15.0
117
+ - @atlaspack/utils@2.14.5
118
+ - @atlaspack/logger@2.14.5
119
+
3
120
  ## 3.1.0
4
121
 
5
122
  ### Minor Changes
package/lib/FSCache.js CHANGED
@@ -25,6 +25,20 @@ function _util() {
25
25
  };
26
26
  return data;
27
27
  }
28
+ function _rust() {
29
+ const data = require("@atlaspack/rust");
30
+ _rust = function () {
31
+ return data;
32
+ };
33
+ return data;
34
+ }
35
+ function _featureFlags() {
36
+ const data = require("@atlaspack/feature-flags");
37
+ _featureFlags = function () {
38
+ return data;
39
+ };
40
+ return data;
41
+ }
28
42
  function _logger() {
29
43
  const data = _interopRequireDefault(require("@atlaspack/logger"));
30
44
  _logger = function () {
@@ -62,6 +76,10 @@ class FSCache {
62
76
  await Promise.all(dirPromises);
63
77
  }
64
78
  _getCachePath(cacheId) {
79
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
80
+ const cleanId = (0, _rust().hashString)(cacheId);
81
+ return _path().default.join(this.dir, cleanId.slice(0, 2), cleanId.slice(2));
82
+ }
65
83
  return _path().default.join(this.dir, cacheId.slice(0, 2), cacheId.slice(2));
66
84
  }
67
85
  getStream(key) {
@@ -91,6 +109,9 @@ class FSCache {
91
109
  }
92
110
  }
93
111
  #getFilePath(key, index) {
112
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
113
+ return _path().default.join(this.dir, `${(0, _rust().hashString)(key)}-${index}`);
114
+ }
94
115
  return _path().default.join(this.dir, `${key}-${index}`);
95
116
  }
96
117
  async #unlinkChunks(key, index) {
@@ -26,16 +26,16 @@ function _rust() {
26
26
  };
27
27
  return data;
28
28
  }
29
- function _stream() {
30
- const data = _interopRequireDefault(require("stream"));
31
- _stream = function () {
29
+ function _fs() {
30
+ const data = _interopRequireDefault(require("fs"));
31
+ _fs = function () {
32
32
  return data;
33
33
  };
34
34
  return data;
35
35
  }
36
- function _path() {
37
- const data = _interopRequireDefault(require("path"));
38
- _path = function () {
36
+ function _ncp() {
37
+ const data = _interopRequireDefault(require("ncp"));
38
+ _ncp = function () {
39
39
  return data;
40
40
  };
41
41
  return data;
@@ -47,25 +47,42 @@ function _util() {
47
47
  };
48
48
  return data;
49
49
  }
50
- function _fs() {
50
+ function _stream() {
51
+ const data = _interopRequireDefault(require("stream"));
52
+ _stream = function () {
53
+ return data;
54
+ };
55
+ return data;
56
+ }
57
+ function _path() {
58
+ const data = _interopRequireDefault(require("path"));
59
+ _path = function () {
60
+ return data;
61
+ };
62
+ return data;
63
+ }
64
+ function _fs2() {
51
65
  const data = require("@atlaspack/fs");
52
- _fs = function () {
66
+ _fs2 = function () {
53
67
  return data;
54
68
  };
55
69
  return data;
56
70
  }
57
71
  var _package = _interopRequireDefault(require("../package.json"));
58
72
  var _FSCache = require("./FSCache");
73
+ function _logger() {
74
+ const data = require("@atlaspack/logger");
75
+ _logger = function () {
76
+ return data;
77
+ };
78
+ return data;
79
+ }
59
80
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
60
81
  // $FlowFixMe
82
+ const ncpAsync = (0, _util().promisify)(_ncp().default);
61
83
  class LmdbWrapper {
62
84
  constructor(lmdb) {
63
85
  this.lmdb = lmdb;
64
-
65
- // $FlowFixMe
66
- this[Symbol.dispose] = () => {
67
- this.lmdb.close();
68
- };
69
86
  }
70
87
  has(key) {
71
88
  return this.lmdb.hasSync(key);
@@ -80,7 +97,19 @@ class LmdbWrapper {
80
97
  const buffer = typeof value === 'string' ? Buffer.from(value) : value;
81
98
  await this.lmdb.put(key, buffer);
82
99
  }
83
- resetReadTxn() {}
100
+ *keys() {
101
+ const PAGE_SIZE = 10000000;
102
+ let currentKeys = this.lmdb.keysSync(0, PAGE_SIZE);
103
+ while (currentKeys.length > 0) {
104
+ for (const key of currentKeys) {
105
+ yield key;
106
+ }
107
+ currentKeys = this.lmdb.keysSync(currentKeys.length, PAGE_SIZE);
108
+ }
109
+ }
110
+ compact(targetPath) {
111
+ this.lmdb.compact(targetPath);
112
+ }
84
113
  }
85
114
  exports.LmdbWrapper = LmdbWrapper;
86
115
  function open(directory
@@ -94,9 +123,14 @@ function open(directory
94
123
  }
95
124
  const pipeline = (0, _util().promisify)(_stream().default.pipeline);
96
125
  class LMDBLiteCache {
126
+ /**
127
+ * Directory where we store raw files.
128
+ */
129
+
97
130
  constructor(cacheDir) {
98
- this.fs = new (_fs().NodeFS)();
131
+ this.fs = new (_fs2().NodeFS)();
99
132
  this.dir = cacheDir;
133
+ this.cacheFilesDirectory = _path().default.join(cacheDir, 'files');
100
134
  this.fsCache = new _FSCache.FSCache(this.fs, cacheDir);
101
135
  this.store = open(cacheDir, {
102
136
  name: 'parcel-cache',
@@ -115,6 +149,7 @@ class LMDBLiteCache {
115
149
  if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
116
150
  await this.fsCache.ensure();
117
151
  }
152
+ await this.fs.mkdirp(this.cacheFilesDirectory);
118
153
  return Promise.resolve();
119
154
  }
120
155
  serialize() {
@@ -139,10 +174,20 @@ class LMDBLiteCache {
139
174
  await this.setBlob(key, (0, _buildCache().serialize)(value));
140
175
  }
141
176
  getStream(key) {
142
- return this.fs.createReadStream(_path().default.join(this.dir, key));
177
+ if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
178
+ return this.fs.createReadStream(_path().default.join(this.dir, key));
179
+ }
180
+ return _fs().default.createReadStream(this.getFileKey(key));
143
181
  }
144
- setStream(key, stream) {
145
- return pipeline(stream, this.fs.createWriteStream(_path().default.join(this.dir, key)));
182
+ async setStream(key, stream) {
183
+ if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
184
+ return pipeline(stream, this.fs.createWriteStream(_path().default.join(this.dir, key)));
185
+ }
186
+ const filePath = this.getFileKey(key);
187
+ await _fs().default.promises.mkdir(_path().default.dirname(filePath), {
188
+ recursive: true
189
+ });
190
+ return pipeline(stream, _fs().default.createWriteStream(filePath));
146
191
  }
147
192
 
148
193
  // eslint-disable-next-line require-await
@@ -162,34 +207,27 @@ class LMDBLiteCache {
162
207
  getBuffer(key) {
163
208
  return Promise.resolve(this.store.get(key));
164
209
  }
165
- #getFilePath(key, index) {
166
- return _path().default.join(this.dir, `${key}-${index}`);
167
- }
168
210
  hasLargeBlob(key) {
169
211
  if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
170
212
  return this.fsCache.hasLargeBlob(key);
171
213
  }
172
- return this.has(key);
214
+ return _fs().default.promises.access(this.getFileKey(key), _fs().default.constants.F_OK).then(() => true).catch(() => false);
173
215
  }
174
-
175
- /**
176
- * @deprecated Use getBlob instead.
177
- */
178
216
  getLargeBlob(key) {
179
217
  if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
180
218
  return this.fsCache.getLargeBlob(key);
181
219
  }
182
- return Promise.resolve(this.getBlobSync(key));
220
+ return _fs().default.promises.readFile(this.getFileKey(key));
183
221
  }
184
-
185
- /**
186
- * @deprecated Use setBlob instead.
187
- */
188
- setLargeBlob(key, contents, options) {
222
+ async setLargeBlob(key, contents, options) {
189
223
  if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
190
224
  return this.fsCache.setLargeBlob(key, contents, options);
191
225
  }
192
- return this.setBlob(key, contents);
226
+ const targetPath = this.getFileKey(key);
227
+ await _fs().default.promises.mkdir(_path().default.dirname(targetPath), {
228
+ recursive: true
229
+ });
230
+ return _fs().default.promises.writeFile(targetPath, contents);
193
231
  }
194
232
 
195
233
  /**
@@ -201,12 +239,55 @@ class LMDBLiteCache {
201
239
  }
202
240
  return this.store.delete(key);
203
241
  }
204
- refresh() {
205
- // Reset the read transaction for the store. This guarantees that
206
- // the next read will see the latest changes to the store.
207
- // Useful in scenarios where reads and writes are multi-threaded.
208
- // See https://github.com/kriszyp/lmdb-js#resetreadtxn-void
209
- this.store.resetReadTxn();
242
+ keys() {
243
+ return this.store.keys();
244
+ }
245
+ async compact(targetPath) {
246
+ await _fs().default.promises.mkdir(targetPath, {
247
+ recursive: true
248
+ });
249
+ const files = await _fs().default.promises.readdir(this.dir);
250
+ // copy all files except data.mdb and lock.mdb to the target path (recursive)
251
+ for (const file of files) {
252
+ const filePath = _path().default.join(this.dir, file);
253
+ if (file === 'data.mdb' || file === 'lock.mdb') {
254
+ continue;
255
+ }
256
+ await ncpAsync(filePath, _path().default.join(targetPath, file));
257
+ }
258
+ this.store.compact(_path().default.join(targetPath, 'data.mdb'));
259
+ }
260
+ refresh() {}
261
+
262
+ /**
263
+ * Streams, packages are stored in files instead of LMDB.
264
+ *
265
+ * On this case, if a cache key happens to have a parent traversal, ../..
266
+ * it is treated specially
267
+ *
268
+ * That is, something/../something and something are meant to be different
269
+ * keys.
270
+ *
271
+ * Plus we do not want to store values outside of the cache directory.
272
+ */
273
+ getFileKey(key) {
274
+ const cleanKey = key.split('/').map(part => {
275
+ if (part === '..') {
276
+ return '$$__parent_dir$$';
277
+ }
278
+ return part;
279
+ }).join('/');
280
+ return _path().default.join(this.cacheFilesDirectory, cleanKey);
281
+ }
282
+ async clear() {
283
+ await (0, _logger().instrumentAsync)('LMDBLiteCache::clear', async () => {
284
+ const keys = await this.keys();
285
+ for (const key of keys) {
286
+ await this.store.delete(key);
287
+ }
288
+ await this.fs.rimraf(this.cacheFilesDirectory);
289
+ await this.fs.mkdirp(this.cacheFilesDirectory);
290
+ });
210
291
  }
211
292
  }
212
293
  exports.LMDBLiteCache = LMDBLiteCache;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@atlaspack/cache",
3
3
  "description": "Interface for defining caches and file-system, IDB and LMDB implementations.",
4
- "version": "3.1.1-canary.0+56fd82cd5",
4
+ "version": "3.1.1-canary.100+f609bf49f",
5
5
  "license": "(MIT OR Apache-2.0)",
6
6
  "type": "commonjs",
7
7
  "publishConfig": {
@@ -23,12 +23,13 @@
23
23
  "check-ts": "tsc --noEmit index.d.ts"
24
24
  },
25
25
  "dependencies": {
26
- "@atlaspack/build-cache": "2.13.3-canary.68+56fd82cd5",
27
- "@atlaspack/feature-flags": "2.14.1-canary.68+56fd82cd5",
28
- "@atlaspack/fs": "2.14.5-canary.0+56fd82cd5",
29
- "@atlaspack/logger": "2.14.5-canary.0+56fd82cd5",
30
- "@atlaspack/rust": "3.2.1-canary.0+56fd82cd5",
31
- "@atlaspack/utils": "2.14.5-canary.0+56fd82cd5"
26
+ "@atlaspack/build-cache": "2.13.3-canary.168+f609bf49f",
27
+ "@atlaspack/feature-flags": "2.14.1-canary.168+f609bf49f",
28
+ "@atlaspack/fs": "2.14.5-canary.100+f609bf49f",
29
+ "@atlaspack/logger": "2.14.5-canary.100+f609bf49f",
30
+ "@atlaspack/rust": "3.2.1-canary.100+f609bf49f",
31
+ "@atlaspack/utils": "2.14.5-canary.100+f609bf49f",
32
+ "ncp": "^2.0.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "idb": "^5.0.8"
@@ -36,5 +37,5 @@
36
37
  "browser": {
37
38
  "./src/IDBCache.js": "./src/IDBCache.browser.js"
38
39
  },
39
- "gitHead": "56fd82cd544c4c9096be6ebea8261e714435de09"
40
+ "gitHead": "f609bf49ffa3984c0ff81d4853a5c850aaee5fce"
40
41
  }
package/src/FSCache.js CHANGED
@@ -9,6 +9,8 @@ import stream from 'stream';
9
9
  import path from 'path';
10
10
  import {promisify} from 'util';
11
11
 
12
+ import {hashString} from '@atlaspack/rust';
13
+ import {getFeatureFlag} from '@atlaspack/feature-flags';
12
14
  import logger from '@atlaspack/logger';
13
15
  import {
14
16
  deserialize,
@@ -51,6 +53,10 @@ export class FSCache implements Cache {
51
53
  }
52
54
 
53
55
  _getCachePath(cacheId: string): FilePath {
56
+ if (getFeatureFlag('cachePerformanceImprovements')) {
57
+ const cleanId = hashString(cacheId);
58
+ return path.join(this.dir, cleanId.slice(0, 2), cleanId.slice(2));
59
+ }
54
60
  return path.join(this.dir, cacheId.slice(0, 2), cacheId.slice(2));
55
61
  }
56
62
 
@@ -90,6 +96,9 @@ export class FSCache implements Cache {
90
96
  }
91
97
 
92
98
  #getFilePath(key: string, index: number): string {
99
+ if (getFeatureFlag('cachePerformanceImprovements')) {
100
+ return path.join(this.dir, `${hashString(key)}-${index}`);
101
+ }
93
102
  return path.join(this.dir, `${key}-${index}`);
94
103
  }
95
104
 
@@ -10,17 +10,18 @@ import {Lmdb} from '@atlaspack/rust';
10
10
  import type {FilePath} from '@atlaspack/types';
11
11
  import type {Cache} from './types';
12
12
  import type {Readable, Writable} from 'stream';
13
-
13
+ import fs from 'fs';
14
+ import ncp from 'ncp';
15
+ import {promisify} from 'util';
14
16
  import stream from 'stream';
15
17
  import path from 'path';
16
- import {promisify} from 'util';
17
-
18
18
  import {NodeFS} from '@atlaspack/fs';
19
-
20
19
  // $FlowFixMe
21
20
  import packageJson from '../package.json';
22
-
23
21
  import {FSCache} from './FSCache';
22
+ import {instrumentAsync} from '@atlaspack/logger';
23
+
24
+ const ncpAsync = promisify(ncp);
24
25
 
25
26
  interface DBOpenOptions {
26
27
  name: string;
@@ -35,11 +36,6 @@ export class LmdbWrapper {
35
36
 
36
37
  constructor(lmdb: Lmdb) {
37
38
  this.lmdb = lmdb;
38
-
39
- // $FlowFixMe
40
- this[Symbol.dispose] = () => {
41
- this.lmdb.close();
42
- };
43
39
  }
44
40
 
45
41
  has(key: string): boolean {
@@ -60,7 +56,21 @@ export class LmdbWrapper {
60
56
  await this.lmdb.put(key, buffer);
61
57
  }
62
58
 
63
- resetReadTxn() {}
59
+ *keys(): Iterable<string> {
60
+ const PAGE_SIZE = 10000000;
61
+
62
+ let currentKeys = this.lmdb.keysSync(0, PAGE_SIZE);
63
+ while (currentKeys.length > 0) {
64
+ for (const key of currentKeys) {
65
+ yield key;
66
+ }
67
+ currentKeys = this.lmdb.keysSync(currentKeys.length, PAGE_SIZE);
68
+ }
69
+ }
70
+
71
+ compact(targetPath: string) {
72
+ this.lmdb.compact(targetPath);
73
+ }
64
74
  }
65
75
 
66
76
  export function open(
@@ -93,10 +103,15 @@ export class LMDBLiteCache implements Cache {
93
103
  dir: FilePath;
94
104
  store: LmdbWrapper;
95
105
  fsCache: FSCache;
106
+ /**
107
+ * Directory where we store raw files.
108
+ */
109
+ cacheFilesDirectory: FilePath;
96
110
 
97
111
  constructor(cacheDir: FilePath) {
98
112
  this.fs = new NodeFS();
99
113
  this.dir = cacheDir;
114
+ this.cacheFilesDirectory = path.join(cacheDir, 'files');
100
115
  this.fsCache = new FSCache(this.fs, cacheDir);
101
116
 
102
117
  this.store = open(cacheDir, {
@@ -117,6 +132,7 @@ export class LMDBLiteCache implements Cache {
117
132
  if (!getFeatureFlag('cachePerformanceImprovements')) {
118
133
  await this.fsCache.ensure();
119
134
  }
135
+ await this.fs.mkdirp(this.cacheFilesDirectory);
120
136
  return Promise.resolve();
121
137
  }
122
138
 
@@ -148,14 +164,24 @@ export class LMDBLiteCache implements Cache {
148
164
  }
149
165
 
150
166
  getStream(key: string): Readable {
151
- return this.fs.createReadStream(path.join(this.dir, key));
167
+ if (!getFeatureFlag('cachePerformanceImprovements')) {
168
+ return this.fs.createReadStream(path.join(this.dir, key));
169
+ }
170
+
171
+ return fs.createReadStream(this.getFileKey(key));
152
172
  }
153
173
 
154
- setStream(key: string, stream: Readable): Promise<void> {
155
- return pipeline(
156
- stream,
157
- this.fs.createWriteStream(path.join(this.dir, key)),
158
- );
174
+ async setStream(key: string, stream: Readable): Promise<void> {
175
+ if (!getFeatureFlag('cachePerformanceImprovements')) {
176
+ return pipeline(
177
+ stream,
178
+ this.fs.createWriteStream(path.join(this.dir, key)),
179
+ );
180
+ }
181
+
182
+ const filePath = this.getFileKey(key);
183
+ await fs.promises.mkdir(path.dirname(filePath), {recursive: true});
184
+ return pipeline(stream, fs.createWriteStream(filePath));
159
185
  }
160
186
 
161
187
  // eslint-disable-next-line require-await
@@ -179,31 +205,25 @@ export class LMDBLiteCache implements Cache {
179
205
  return Promise.resolve(this.store.get(key));
180
206
  }
181
207
 
182
- #getFilePath(key: string, index: number): string {
183
- return path.join(this.dir, `${key}-${index}`);
184
- }
185
-
186
208
  hasLargeBlob(key: string): Promise<boolean> {
187
209
  if (!getFeatureFlag('cachePerformanceImprovements')) {
188
210
  return this.fsCache.hasLargeBlob(key);
189
211
  }
190
- return this.has(key);
212
+
213
+ return fs.promises
214
+ .access(this.getFileKey(key), fs.constants.F_OK)
215
+ .then(() => true)
216
+ .catch(() => false);
191
217
  }
192
218
 
193
- /**
194
- * @deprecated Use getBlob instead.
195
- */
196
219
  getLargeBlob(key: string): Promise<Buffer> {
197
220
  if (!getFeatureFlag('cachePerformanceImprovements')) {
198
221
  return this.fsCache.getLargeBlob(key);
199
222
  }
200
- return Promise.resolve(this.getBlobSync(key));
223
+ return fs.promises.readFile(this.getFileKey(key));
201
224
  }
202
225
 
203
- /**
204
- * @deprecated Use setBlob instead.
205
- */
206
- setLargeBlob(
226
+ async setLargeBlob(
207
227
  key: string,
208
228
  contents: Buffer | string,
209
229
  options?: {|signal?: AbortSignal|},
@@ -211,7 +231,10 @@ export class LMDBLiteCache implements Cache {
211
231
  if (!getFeatureFlag('cachePerformanceImprovements')) {
212
232
  return this.fsCache.setLargeBlob(key, contents, options);
213
233
  }
214
- return this.setBlob(key, contents);
234
+
235
+ const targetPath = this.getFileKey(key);
236
+ await fs.promises.mkdir(path.dirname(targetPath), {recursive: true});
237
+ return fs.promises.writeFile(targetPath, contents);
215
238
  }
216
239
 
217
240
  /**
@@ -225,12 +248,64 @@ export class LMDBLiteCache implements Cache {
225
248
  return this.store.delete(key);
226
249
  }
227
250
 
228
- refresh(): void {
229
- // Reset the read transaction for the store. This guarantees that
230
- // the next read will see the latest changes to the store.
231
- // Useful in scenarios where reads and writes are multi-threaded.
232
- // See https://github.com/kriszyp/lmdb-js#resetreadtxn-void
233
- this.store.resetReadTxn();
251
+ keys(): Iterable<string> {
252
+ return this.store.keys();
253
+ }
254
+
255
+ async compact(targetPath: string): Promise<void> {
256
+ await fs.promises.mkdir(targetPath, {recursive: true});
257
+
258
+ const files = await fs.promises.readdir(this.dir);
259
+ // copy all files except data.mdb and lock.mdb to the target path (recursive)
260
+ for (const file of files) {
261
+ const filePath = path.join(this.dir, file);
262
+
263
+ if (file === 'data.mdb' || file === 'lock.mdb') {
264
+ continue;
265
+ }
266
+
267
+ await ncpAsync(filePath, path.join(targetPath, file));
268
+ }
269
+
270
+ this.store.compact(path.join(targetPath, 'data.mdb'));
271
+ }
272
+
273
+ refresh(): void {}
274
+
275
+ /**
276
+ * Streams, packages are stored in files instead of LMDB.
277
+ *
278
+ * On this case, if a cache key happens to have a parent traversal, ../..
279
+ * it is treated specially
280
+ *
281
+ * That is, something/../something and something are meant to be different
282
+ * keys.
283
+ *
284
+ * Plus we do not want to store values outside of the cache directory.
285
+ */
286
+ getFileKey(key: string): string {
287
+ const cleanKey = key
288
+ .split('/')
289
+ .map((part) => {
290
+ if (part === '..') {
291
+ return '$$__parent_dir$$';
292
+ }
293
+ return part;
294
+ })
295
+ .join('/');
296
+ return path.join(this.cacheFilesDirectory, cleanKey);
297
+ }
298
+
299
+ async clear(): Promise<void> {
300
+ await instrumentAsync('LMDBLiteCache::clear', async () => {
301
+ const keys = await this.keys();
302
+ for (const key of keys) {
303
+ await this.store.delete(key);
304
+ }
305
+
306
+ await this.fs.rimraf(this.cacheFilesDirectory);
307
+ await this.fs.mkdirp(this.cacheFilesDirectory);
308
+ });
234
309
  }
235
310
  }
236
311
 
@@ -1,20 +1,30 @@
1
1
  // @flow
2
+
3
+ import * as fs from 'fs';
2
4
  import * as path from 'path';
3
5
  import {tmpdir} from 'os';
4
6
  import {LMDBLiteCache} from '../src/index';
5
7
  import {deserialize, serialize} from 'v8';
6
8
  import assert from 'assert';
9
+ import {Worker} from 'worker_threads';
10
+ import {initializeMonitoring} from '@atlaspack/rust';
7
11
 
8
12
  const cacheDir = path.join(tmpdir(), 'lmdb-lite-cache-tests');
9
13
 
10
14
  describe('LMDBLiteCache', () => {
15
+ let cache;
16
+
17
+ beforeEach(async () => {
18
+ await fs.promises.rm(cacheDir, {recursive: true, force: true});
19
+ });
20
+
11
21
  it('can be constructed', async () => {
12
- const cache = new LMDBLiteCache(cacheDir);
22
+ cache = new LMDBLiteCache(cacheDir);
13
23
  await cache.ensure();
14
24
  });
15
25
 
16
26
  it('can retrieve keys', async () => {
17
- const cache = new LMDBLiteCache(cacheDir);
27
+ cache = new LMDBLiteCache(cacheDir);
18
28
  await cache.ensure();
19
29
  await cache.setBlob('key', Buffer.from(serialize({value: 42})));
20
30
  const buffer = await cache.getBlob('key');
@@ -23,11 +33,211 @@ describe('LMDBLiteCache', () => {
23
33
  });
24
34
 
25
35
  it('can retrieve keys synchronously', async () => {
26
- const cache = new LMDBLiteCache(cacheDir);
36
+ cache = new LMDBLiteCache(path.join(cacheDir, 'retrieve_keys_test'));
27
37
  await cache.ensure();
28
- cache.setBlob('key', Buffer.from(serialize({value: 42})));
38
+ await cache.setBlob('key', Buffer.from(serialize({value: 42})));
29
39
  const buffer = cache.getBlobSync('key');
30
40
  const result = deserialize(buffer);
31
41
  assert.equal(result.value, 42);
32
42
  });
43
+
44
+ it('can iterate over keys', async () => {
45
+ cache = new LMDBLiteCache(path.join(cacheDir, 'keys_test'));
46
+ await cache.ensure();
47
+ await cache.setBlob('key1', Buffer.from(serialize({value: 42})));
48
+ await cache.setBlob('key2', Buffer.from(serialize({value: 43})));
49
+ const keys = cache.keys();
50
+ assert.deepEqual(Array.from(keys), ['key1', 'key2']);
51
+ });
52
+
53
+ it('can compact databases', async () => {
54
+ cache = new LMDBLiteCache(path.join(cacheDir, 'compact_test'));
55
+ await cache.ensure();
56
+ await cache.setBlob('key1', Buffer.from(serialize({value: 42})));
57
+ await cache.setBlob('key2', Buffer.from(serialize({value: 43})));
58
+ await cache.compact(path.join(cacheDir, 'compact_test_compacted'));
59
+
60
+ cache = new LMDBLiteCache(path.join(cacheDir, 'compact_test_compacted'));
61
+ await cache.ensure();
62
+ const keys = cache.keys();
63
+ assert.deepEqual(Array.from(keys), ['key1', 'key2']);
64
+ });
65
+
66
+ describe('getFileKey', () => {
67
+ it('should return the correct key', () => {
68
+ const target = path.join(cacheDir, 'test-file-keys');
69
+ const cache = new LMDBLiteCache(target);
70
+ const key = cache.getFileKey('key');
71
+ assert.equal(key, path.join(target, 'files', 'key'));
72
+ });
73
+
74
+ it('should return the correct key for a key with a parent traversal', () => {
75
+ const target = path.join(cacheDir, 'test-parent-keys');
76
+ cache = new LMDBLiteCache(target);
77
+ const key = cache.getFileKey('../../key');
78
+ assert.equal(
79
+ key,
80
+ path.join(target, 'files', '$$__parent_dir$$/$$__parent_dir$$/key'),
81
+ );
82
+ });
83
+ });
84
+
85
+ it('can be closed and re-opened', async () => {
86
+ cache = new LMDBLiteCache(path.join(cacheDir, 'close_and_reopen_test'));
87
+ await cache.ensure();
88
+ await cache.setBlob('key', Buffer.from(serialize({value: 42})));
89
+ cache = new LMDBLiteCache(path.join(cacheDir, 'close_and_reopen_test'));
90
+ await cache.ensure();
91
+ const buffer = await cache.getBlob('key');
92
+ const result = deserialize(buffer);
93
+ assert.equal(result.value, 42);
94
+ });
95
+
96
+ it('should NOT fail when trying to open the same database twice', async () => {
97
+ const testDir = path.join(cacheDir, 'double_open_test');
98
+ const cache1 = new LMDBLiteCache(testDir);
99
+ await cache1.ensure();
100
+
101
+ assert.doesNotThrow(() => {
102
+ new LMDBLiteCache(testDir);
103
+ });
104
+ });
105
+
106
+ it('should NOT fail when trying to open after GC', async () => {
107
+ const testDir = path.join(cacheDir, 'gc_test');
108
+
109
+ let cache1 = new LMDBLiteCache(testDir);
110
+ await cache1.ensure();
111
+ await cache1.setBlob('key', Buffer.from(serialize({value: 42})));
112
+
113
+ cache1 = null;
114
+
115
+ if (global.gc) {
116
+ global.gc();
117
+ }
118
+
119
+ assert.doesNotThrow(() => {
120
+ new LMDBLiteCache(testDir);
121
+ });
122
+ });
123
+
124
+ it('should handle rapid open/close cycles', async () => {
125
+ const testDir = path.join(cacheDir, 'rapid_cycles_test');
126
+
127
+ for (let i = 0; i < 10; i++) {
128
+ const cache = new LMDBLiteCache(testDir);
129
+ await cache.ensure();
130
+ await cache.setBlob(`key${i}`, Buffer.from(serialize({value: i})));
131
+
132
+ await new Promise((resolve) => setTimeout(resolve, 10));
133
+ }
134
+
135
+ const finalCache = new LMDBLiteCache(testDir);
136
+ await finalCache.ensure();
137
+ const buffer = await finalCache.getBlob('key9');
138
+ const result = deserialize(buffer);
139
+ assert.equal(result.value, 9);
140
+ });
141
+
142
+ it('should work when there are multiple node.js worker threads accessing the same database', async function () {
143
+ this.timeout(40000);
144
+
145
+ try {
146
+ initializeMonitoring();
147
+ } catch (error) {
148
+ /* empty */
149
+ }
150
+
151
+ const testDir = path.join(cacheDir, 'worker_threads_test');
152
+
153
+ let cache = new LMDBLiteCache(testDir);
154
+ await cache.set('main_thread_key', {
155
+ mainThreadId: 0,
156
+ hello: 'world',
157
+ });
158
+ setTimeout(() => {
159
+ cache = null;
160
+
161
+ if (global.gc) {
162
+ global.gc();
163
+ }
164
+ }, Math.random() * 300);
165
+
166
+ const numWorkers = 10;
167
+
168
+ const workers = [];
169
+ const responsePromises = [];
170
+ for (let i = 0; i < numWorkers; i++) {
171
+ const worker = new Worker(path.join(__dirname, 'workerThreadsTest.js'), {
172
+ workerData: {
173
+ cacheDir: testDir,
174
+ },
175
+ });
176
+ workers.push(worker);
177
+
178
+ const responsePromise = new Promise((resolve, reject) => {
179
+ worker.addListener('error', (error: Error) => {
180
+ reject(error);
181
+ });
182
+ worker.addListener('message', (message) => {
183
+ resolve(message);
184
+ });
185
+ });
186
+
187
+ worker.addListener('message', (message) => {
188
+ // eslint-disable-next-line no-console
189
+ console.log('Worker message', message);
190
+ });
191
+ worker.addListener('online', () => {
192
+ worker.postMessage({
193
+ type: 'go',
194
+ });
195
+ });
196
+
197
+ responsePromises.push(responsePromise);
198
+ }
199
+
200
+ // eslint-disable-next-line no-console
201
+ console.log('Waiting for responses');
202
+ const responses = await Promise.all(responsePromises);
203
+
204
+ // eslint-disable-next-line no-console
205
+ console.log('Responses received');
206
+ for (const [index, response] of responses.entries()) {
207
+ const worker = workers[index];
208
+
209
+ assert.deepEqual(
210
+ response,
211
+ {
212
+ mainThreadData: {
213
+ mainThreadId: 0,
214
+ hello: 'world',
215
+ },
216
+ workerId: worker.threadId,
217
+ },
218
+ `worker_${index} - Worker ${worker.threadId} should have received the correct data`,
219
+ );
220
+ }
221
+
222
+ // eslint-disable-next-line no-console
223
+ console.log('Getting main thread key');
224
+ cache = new LMDBLiteCache(testDir);
225
+ const data = await cache?.get('main_thread_key');
226
+ assert.deepEqual(data, {
227
+ mainThreadId: 0,
228
+ hello: 'world',
229
+ });
230
+
231
+ // eslint-disable-next-line no-console
232
+ console.log('Getting worker keys');
233
+ for (const worker of workers) {
234
+ const data = await cache?.get(`worker_key/${worker.threadId}`);
235
+ assert.deepEqual(data, {
236
+ workerId: worker.threadId,
237
+ });
238
+
239
+ await new Promise((resolve) => setTimeout(resolve, 500));
240
+ worker.terminate();
241
+ }
242
+ });
33
243
  });
@@ -0,0 +1,42 @@
1
+ /* eslint-disable no-inner-declarations */
2
+
3
+ require('@atlaspack/babel-register');
4
+ const {
5
+ workerData,
6
+ threadId,
7
+ parentPort,
8
+ isMainThread,
9
+ } = require('worker_threads');
10
+ const {LMDBLiteCache} = require('../src/index');
11
+
12
+ if (!isMainThread) {
13
+ const cache = new LMDBLiteCache(workerData.cacheDir);
14
+
15
+ async function onMessage() {
16
+ try {
17
+ cache.set(`worker_key/${threadId}`, {
18
+ workerId: threadId,
19
+ });
20
+
21
+ const data = await cache.get('main_thread_key');
22
+
23
+ parentPort.postMessage({
24
+ mainThreadData: data,
25
+ workerId: threadId,
26
+ });
27
+
28
+ setTimeout(() => {
29
+ parentPort.postMessage({
30
+ type: 'close',
31
+ workerId: threadId,
32
+ });
33
+ }, Math.random() * 200);
34
+ } catch (error) {
35
+ parentPort.postMessage({
36
+ error: error.message,
37
+ });
38
+ }
39
+ }
40
+
41
+ parentPort.on('message', onMessage);
42
+ }