@atlaspack/cache 3.1.1-canary.13 → 3.1.1-canary.130

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,139 @@
1
1
  # @atlaspack/cache
2
2
 
3
+ ## 3.2.13
4
+
5
+ ### Patch Changes
6
+
7
+ - [#697](https://github.com/atlassian-labs/atlaspack/pull/697) [`c9631af`](https://github.com/atlassian-labs/atlaspack/commit/c9631aff284b2c1c27e8a52f9da392ce65d666e8) Thanks [@yamadapc](https://github.com/yamadapc)! - Fix bug where cache large blob operations were not atomic
8
+
9
+ - Updated dependencies [[`dbb4072`](https://github.com/atlassian-labs/atlaspack/commit/dbb40721ebeb45990a14ba04e6b44e7f836fb32d), [`becf977`](https://github.com/atlassian-labs/atlaspack/commit/becf977f625d5ee46dae3d4c679f173bf5f40cc0), [`becf977`](https://github.com/atlassian-labs/atlaspack/commit/becf977f625d5ee46dae3d4c679f173bf5f40cc0), [`c4415a4`](https://github.com/atlassian-labs/atlaspack/commit/c4415a455543d984ca28452c2cb87a794d22497c), [`f0f7c71`](https://github.com/atlassian-labs/atlaspack/commit/f0f7c7168a1d3d18c6f30d2daed611275692b7c5), [`de23e0c`](https://github.com/atlassian-labs/atlaspack/commit/de23e0ce49d5504fe3947ac26640a3d951087da3), [`18a57cf`](https://github.com/atlassian-labs/atlaspack/commit/18a57cf8a4789b2de5ad8e2676f317a26cc91417), [`a5ed1b4`](https://github.com/atlassian-labs/atlaspack/commit/a5ed1b414498560f393ff491af4da25b6e8dde56)]:
10
+ - @atlaspack/feature-flags@2.19.0
11
+ - @atlaspack/utils@2.17.0
12
+ - @atlaspack/rust@3.4.1
13
+ - @atlaspack/fs@2.15.13
14
+ - @atlaspack/logger@2.14.13
15
+
16
+ ## 3.2.12
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [[`c75bf55`](https://github.com/atlassian-labs/atlaspack/commit/c75bf553fff4decc285b5fd499a275853b18f8f2)]:
21
+ - @atlaspack/rust@3.4.0
22
+ - @atlaspack/fs@2.15.12
23
+ - @atlaspack/logger@2.14.12
24
+ - @atlaspack/utils@2.16.1
25
+
26
+ ## 3.2.11
27
+
28
+ ### Patch Changes
29
+
30
+ - Updated dependencies [[`e8a60ff`](https://github.com/atlassian-labs/atlaspack/commit/e8a60ffbea41caef265786bbf73349771760081c), [`30ee2cf`](https://github.com/atlassian-labs/atlaspack/commit/30ee2cfcd34cf2646ded0eda13fdb80a2a5de529)]:
31
+ - @atlaspack/feature-flags@2.18.4
32
+ - @atlaspack/utils@2.16.0
33
+ - @atlaspack/fs@2.15.11
34
+
35
+ ## 3.2.10
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [[`5ded263`](https://github.com/atlassian-labs/atlaspack/commit/5ded263c7f11b866e8885b81c73e20dd060b25be)]:
40
+ - @atlaspack/feature-flags@2.18.3
41
+ - @atlaspack/fs@2.15.10
42
+ - @atlaspack/utils@2.15.3
43
+
44
+ ## 3.2.9
45
+
46
+ ### Patch Changes
47
+
48
+ - Updated dependencies [[`644b157`](https://github.com/atlassian-labs/atlaspack/commit/644b157dee72a871acc2d0facf0b87b8eea51956)]:
49
+ - @atlaspack/feature-flags@2.18.2
50
+ - @atlaspack/fs@2.15.9
51
+ - @atlaspack/utils@2.15.2
52
+
53
+ ## 3.2.8
54
+
55
+ ### Patch Changes
56
+
57
+ - 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)]:
58
+ - @atlaspack/logger@2.14.11
59
+ - @atlaspack/feature-flags@2.18.1
60
+ - @atlaspack/fs@2.15.8
61
+ - @atlaspack/utils@2.15.1
62
+
63
+ ## 3.2.7
64
+
65
+ ### Patch Changes
66
+
67
+ - 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)]:
68
+ - @atlaspack/feature-flags@2.18.0
69
+ - @atlaspack/utils@2.15.0
70
+ - @atlaspack/fs@2.15.7
71
+
72
+ ## 3.2.6
73
+
74
+ ### Patch Changes
75
+
76
+ - Updated dependencies [[`73ea3c4`](https://github.com/atlassian-labs/atlaspack/commit/73ea3c4d85d4401fdd15abcbf988237e890e7ad3), [`b1b3693`](https://github.com/atlassian-labs/atlaspack/commit/b1b369317c66f8a431c170df2ebba4fa5b2e38ef)]:
77
+ - @atlaspack/feature-flags@2.17.0
78
+ - @atlaspack/fs@2.15.6
79
+ - @atlaspack/utils@2.14.11
80
+
81
+ ## 3.2.5
82
+
83
+ ### Patch Changes
84
+
85
+ - 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)]:
86
+ - @atlaspack/fs@2.15.5
87
+ - @atlaspack/rust@3.3.5
88
+ - @atlaspack/logger@2.14.10
89
+ - @atlaspack/utils@2.14.10
90
+
91
+ ## 3.2.4
92
+
93
+ ### Patch Changes
94
+
95
+ - [#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
96
+
97
+ - 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)]:
98
+ - @atlaspack/rust@3.3.4
99
+ - @atlaspack/fs@2.15.4
100
+ - @atlaspack/feature-flags@2.16.0
101
+ - @atlaspack/logger@2.14.9
102
+ - @atlaspack/utils@2.14.9
103
+
104
+ ## 3.2.3
105
+
106
+ ### Patch Changes
107
+
108
+ - 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)]:
109
+ - @atlaspack/feature-flags@2.15.1
110
+ - @atlaspack/fs@2.15.3
111
+ - @atlaspack/rust@3.3.3
112
+ - @atlaspack/utils@2.14.8
113
+ - @atlaspack/logger@2.14.8
114
+
115
+ ## 3.2.2
116
+
117
+ ### Patch Changes
118
+
119
+ - Updated dependencies [[`a1773d2`](https://github.com/atlassian-labs/atlaspack/commit/a1773d2a62d0ef7805ac7524621dcabcc1afe929), [`556d6ab`](https://github.com/atlassian-labs/atlaspack/commit/556d6ab8ede759fa7f37fcd3f4da336ef1c55e8f)]:
120
+ - @atlaspack/feature-flags@2.15.0
121
+ - @atlaspack/logger@2.14.7
122
+ - @atlaspack/rust@3.3.2
123
+ - @atlaspack/fs@2.15.2
124
+ - @atlaspack/utils@2.14.7
125
+
126
+ ## 3.2.1
127
+
128
+ ### Patch Changes
129
+
130
+ - Updated dependencies [[`e0f5337`](https://github.com/atlassian-labs/atlaspack/commit/e0f533757bd1019dbd108a04952c87da15286e09)]:
131
+ - @atlaspack/feature-flags@2.14.4
132
+ - @atlaspack/rust@3.3.1
133
+ - @atlaspack/fs@2.15.1
134
+ - @atlaspack/utils@2.14.6
135
+ - @atlaspack/logger@2.14.6
136
+
3
137
  ## 3.2.0
4
138
 
5
139
  ### Minor Changes
package/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type {FilePath} from '@atlaspack/types';
2
+
2
3
  import type {Cache} from './lib/types';
3
4
 
4
5
  export type {Cache} from './lib/types';
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,13 +26,6 @@ function _rust() {
26
26
  };
27
27
  return data;
28
28
  }
29
- function _fs() {
30
- const data = _interopRequireDefault(require("fs"));
31
- _fs = function () {
32
- return data;
33
- };
34
- return data;
35
- }
36
29
  function _ncp() {
37
30
  const data = _interopRequireDefault(require("ncp"));
38
31
  _ncp = function () {
@@ -61,26 +54,28 @@ function _path() {
61
54
  };
62
55
  return data;
63
56
  }
64
- function _fs2() {
57
+ function _fs() {
65
58
  const data = require("@atlaspack/fs");
66
- _fs2 = function () {
59
+ _fs = function () {
67
60
  return data;
68
61
  };
69
62
  return data;
70
63
  }
71
64
  var _package = _interopRequireDefault(require("../package.json"));
72
65
  var _FSCache = require("./FSCache");
66
+ function _logger() {
67
+ const data = require("@atlaspack/logger");
68
+ _logger = function () {
69
+ return data;
70
+ };
71
+ return data;
72
+ }
73
73
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
74
74
  // $FlowFixMe
75
75
  const ncpAsync = (0, _util().promisify)(_ncp().default);
76
76
  class LmdbWrapper {
77
77
  constructor(lmdb) {
78
78
  this.lmdb = lmdb;
79
-
80
- // $FlowFixMe
81
- this[Symbol.dispose] = () => {
82
- this.lmdb.close();
83
- };
84
79
  }
85
80
  has(key) {
86
81
  return this.lmdb.hasSync(key);
@@ -110,20 +105,25 @@ class LmdbWrapper {
110
105
  }
111
106
  }
112
107
  exports.LmdbWrapper = LmdbWrapper;
113
- function open(directory
108
+ function open(directory,
114
109
  // eslint-disable-next-line no-unused-vars
115
- ) {
110
+ openOptions) {
116
111
  return new LmdbWrapper(new (_rust().Lmdb)({
117
112
  path: directory,
118
113
  asyncWrites: true,
119
- mapSize: 1024 * 1024 * 1024 * 15
114
+ mapSize: process.env.ATLASPACK_BUILD_ENV === 'test' ? 1024 * 1024 * 1024 : 1024 * 1024 * 1024 * 15
120
115
  }));
121
116
  }
122
117
  const pipeline = (0, _util().promisify)(_stream().default.pipeline);
123
118
  class LMDBLiteCache {
119
+ /**
120
+ * Directory where we store raw files.
121
+ */
122
+
124
123
  constructor(cacheDir) {
125
- this.fs = new (_fs2().NodeFS)();
124
+ this.fs = new (_fs().NodeFS)();
126
125
  this.dir = cacheDir;
126
+ this.cacheFilesDirectory = _path().default.join(cacheDir, 'files');
127
127
  this.fsCache = new _FSCache.FSCache(this.fs, cacheDir);
128
128
  this.store = open(cacheDir, {
129
129
  name: 'parcel-cache',
@@ -142,6 +142,7 @@ class LMDBLiteCache {
142
142
  if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
143
143
  await this.fsCache.ensure();
144
144
  }
145
+ await this.fs.mkdirp(this.cacheFilesDirectory);
145
146
  return Promise.resolve();
146
147
  }
147
148
  serialize() {
@@ -166,10 +167,18 @@ class LMDBLiteCache {
166
167
  await this.setBlob(key, (0, _buildCache().serialize)(value));
167
168
  }
168
169
  getStream(key) {
169
- return this.fs.createReadStream(_path().default.join(this.dir, key));
170
+ if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
171
+ return this.fs.createReadStream(_path().default.join(this.dir, key));
172
+ }
173
+ return this.fs.createReadStream(this.getFileKey(key));
170
174
  }
171
- setStream(key, stream) {
172
- return pipeline(stream, this.fs.createWriteStream(_path().default.join(this.dir, key)));
175
+ async setStream(key, stream) {
176
+ if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
177
+ return pipeline(stream, this.fs.createWriteStream(_path().default.join(this.dir, key)));
178
+ }
179
+ const filePath = this.getFileKey(key);
180
+ await this.fs.mkdirp(_path().default.dirname(filePath));
181
+ return pipeline(stream, this.fs.createWriteStream(filePath));
173
182
  }
174
183
 
175
184
  // eslint-disable-next-line require-await
@@ -189,34 +198,25 @@ class LMDBLiteCache {
189
198
  getBuffer(key) {
190
199
  return Promise.resolve(this.store.get(key));
191
200
  }
192
- #getFilePath(key, index) {
193
- return _path().default.join(this.dir, `${key}-${index}`);
194
- }
195
201
  hasLargeBlob(key) {
196
202
  if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
197
203
  return this.fsCache.hasLargeBlob(key);
198
204
  }
199
- return this.has(key);
205
+ return this.fs.exists(this.getFileKey(key));
200
206
  }
201
-
202
- /**
203
- * @deprecated Use getBlob instead.
204
- */
205
207
  getLargeBlob(key) {
206
208
  if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
207
209
  return this.fsCache.getLargeBlob(key);
208
210
  }
209
- return Promise.resolve(this.getBlobSync(key));
211
+ return this.fs.readFile(this.getFileKey(key));
210
212
  }
211
-
212
- /**
213
- * @deprecated Use setBlob instead.
214
- */
215
- setLargeBlob(key, contents, options) {
213
+ async setLargeBlob(key, contents, options) {
216
214
  if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
217
215
  return this.fsCache.setLargeBlob(key, contents, options);
218
216
  }
219
- return this.setBlob(key, contents);
217
+ const targetPath = this.getFileKey(key);
218
+ await this.fs.mkdirp(_path().default.dirname(targetPath));
219
+ return this.fs.writeFile(targetPath, contents);
220
220
  }
221
221
 
222
222
  /**
@@ -232,10 +232,8 @@ class LMDBLiteCache {
232
232
  return this.store.keys();
233
233
  }
234
234
  async compact(targetPath) {
235
- await _fs().default.promises.mkdir(targetPath, {
236
- recursive: true
237
- });
238
- const files = await _fs().default.promises.readdir(this.dir);
235
+ await this.fs.mkdirp(targetPath);
236
+ const files = await this.fs.readdir(this.dir);
239
237
  // copy all files except data.mdb and lock.mdb to the target path (recursive)
240
238
  for (const file of files) {
241
239
  const filePath = _path().default.join(this.dir, file);
@@ -247,6 +245,37 @@ class LMDBLiteCache {
247
245
  this.store.compact(_path().default.join(targetPath, 'data.mdb'));
248
246
  }
249
247
  refresh() {}
248
+
249
+ /**
250
+ * Streams, packages are stored in files instead of LMDB.
251
+ *
252
+ * On this case, if a cache key happens to have a parent traversal, ../..
253
+ * it is treated specially
254
+ *
255
+ * That is, something/../something and something are meant to be different
256
+ * keys.
257
+ *
258
+ * Plus we do not want to store values outside of the cache directory.
259
+ */
260
+ getFileKey(key) {
261
+ const cleanKey = key.split('/').map(part => {
262
+ if (part === '..') {
263
+ return '$$__parent_dir$$';
264
+ }
265
+ return part;
266
+ }).join('/');
267
+ return _path().default.join(this.cacheFilesDirectory, cleanKey);
268
+ }
269
+ async clear() {
270
+ await (0, _logger().instrumentAsync)('LMDBLiteCache::clear', async () => {
271
+ const keys = await this.keys();
272
+ for (const key of keys) {
273
+ await this.store.delete(key);
274
+ }
275
+ await this.fs.rimraf(this.cacheFilesDirectory);
276
+ await this.fs.mkdirp(this.cacheFilesDirectory);
277
+ });
278
+ }
250
279
  }
251
280
  exports.LMDBLiteCache = LMDBLiteCache;
252
281
  (0, _buildCache().registerSerializableClass)(`${_package.default.version}: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.13+e0f533757",
4
+ "version": "3.1.1-canary.130+13aef177e",
5
5
  "license": "(MIT OR Apache-2.0)",
6
6
  "type": "commonjs",
7
7
  "publishConfig": {
@@ -23,12 +23,12 @@
23
23
  "check-ts": "tsc --noEmit index.d.ts"
24
24
  },
25
25
  "dependencies": {
26
- "@atlaspack/build-cache": "2.13.3-canary.81+e0f533757",
27
- "@atlaspack/feature-flags": "2.14.1-canary.81+e0f533757",
28
- "@atlaspack/fs": "2.14.5-canary.13+e0f533757",
29
- "@atlaspack/logger": "2.14.5-canary.13+e0f533757",
30
- "@atlaspack/rust": "3.2.1-canary.13+e0f533757",
31
- "@atlaspack/utils": "2.14.5-canary.13+e0f533757",
26
+ "@atlaspack/build-cache": "2.13.3-canary.198+13aef177e",
27
+ "@atlaspack/feature-flags": "2.14.1-canary.198+13aef177e",
28
+ "@atlaspack/fs": "2.14.5-canary.130+13aef177e",
29
+ "@atlaspack/logger": "2.14.5-canary.130+13aef177e",
30
+ "@atlaspack/rust": "3.2.1-canary.130+13aef177e",
31
+ "@atlaspack/utils": "2.14.5-canary.130+13aef177e",
32
32
  "ncp": "^2.0.0"
33
33
  },
34
34
  "devDependencies": {
@@ -37,5 +37,5 @@
37
37
  "browser": {
38
38
  "./src/IDBCache.js": "./src/IDBCache.browser.js"
39
39
  },
40
- "gitHead": "e0f533757bd1019dbd108a04952c87da15286e09"
40
+ "gitHead": "13aef177eea289a6e40d2113b5ec1ac9be18a33d"
41
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,7 +10,6 @@ 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
- import fs from 'fs';
14
13
  import ncp from 'ncp';
15
14
  import {promisify} from 'util';
16
15
  import stream from 'stream';
@@ -19,6 +18,7 @@ import {NodeFS} from '@atlaspack/fs';
19
18
  // $FlowFixMe
20
19
  import packageJson from '../package.json';
21
20
  import {FSCache} from './FSCache';
21
+ import {instrumentAsync} from '@atlaspack/logger';
22
22
 
23
23
  const ncpAsync = promisify(ncp);
24
24
 
@@ -35,11 +35,6 @@ export class LmdbWrapper {
35
35
 
36
36
  constructor(lmdb: Lmdb) {
37
37
  this.lmdb = lmdb;
38
-
39
- // $FlowFixMe
40
- this[Symbol.dispose] = () => {
41
- this.lmdb.close();
42
- };
43
38
  }
44
39
 
45
40
  has(key: string): boolean {
@@ -107,10 +102,15 @@ export class LMDBLiteCache implements Cache {
107
102
  dir: FilePath;
108
103
  store: LmdbWrapper;
109
104
  fsCache: FSCache;
105
+ /**
106
+ * Directory where we store raw files.
107
+ */
108
+ cacheFilesDirectory: FilePath;
110
109
 
111
110
  constructor(cacheDir: FilePath) {
112
111
  this.fs = new NodeFS();
113
112
  this.dir = cacheDir;
113
+ this.cacheFilesDirectory = path.join(cacheDir, 'files');
114
114
  this.fsCache = new FSCache(this.fs, cacheDir);
115
115
 
116
116
  this.store = open(cacheDir, {
@@ -131,6 +131,7 @@ export class LMDBLiteCache implements Cache {
131
131
  if (!getFeatureFlag('cachePerformanceImprovements')) {
132
132
  await this.fsCache.ensure();
133
133
  }
134
+ await this.fs.mkdirp(this.cacheFilesDirectory);
134
135
  return Promise.resolve();
135
136
  }
136
137
 
@@ -162,14 +163,24 @@ export class LMDBLiteCache implements Cache {
162
163
  }
163
164
 
164
165
  getStream(key: string): Readable {
165
- return this.fs.createReadStream(path.join(this.dir, key));
166
+ if (!getFeatureFlag('cachePerformanceImprovements')) {
167
+ return this.fs.createReadStream(path.join(this.dir, key));
168
+ }
169
+
170
+ return this.fs.createReadStream(this.getFileKey(key));
166
171
  }
167
172
 
168
- setStream(key: string, stream: Readable): Promise<void> {
169
- return pipeline(
170
- stream,
171
- this.fs.createWriteStream(path.join(this.dir, key)),
172
- );
173
+ async setStream(key: string, stream: Readable): Promise<void> {
174
+ if (!getFeatureFlag('cachePerformanceImprovements')) {
175
+ return pipeline(
176
+ stream,
177
+ this.fs.createWriteStream(path.join(this.dir, key)),
178
+ );
179
+ }
180
+
181
+ const filePath = this.getFileKey(key);
182
+ await this.fs.mkdirp(path.dirname(filePath));
183
+ return pipeline(stream, this.fs.createWriteStream(filePath));
173
184
  }
174
185
 
175
186
  // eslint-disable-next-line require-await
@@ -193,31 +204,22 @@ export class LMDBLiteCache implements Cache {
193
204
  return Promise.resolve(this.store.get(key));
194
205
  }
195
206
 
196
- #getFilePath(key: string, index: number): string {
197
- return path.join(this.dir, `${key}-${index}`);
198
- }
199
-
200
207
  hasLargeBlob(key: string): Promise<boolean> {
201
208
  if (!getFeatureFlag('cachePerformanceImprovements')) {
202
209
  return this.fsCache.hasLargeBlob(key);
203
210
  }
204
- return this.has(key);
211
+
212
+ return this.fs.exists(this.getFileKey(key));
205
213
  }
206
214
 
207
- /**
208
- * @deprecated Use getBlob instead.
209
- */
210
215
  getLargeBlob(key: string): Promise<Buffer> {
211
216
  if (!getFeatureFlag('cachePerformanceImprovements')) {
212
217
  return this.fsCache.getLargeBlob(key);
213
218
  }
214
- return Promise.resolve(this.getBlobSync(key));
219
+ return this.fs.readFile(this.getFileKey(key));
215
220
  }
216
221
 
217
- /**
218
- * @deprecated Use setBlob instead.
219
- */
220
- setLargeBlob(
222
+ async setLargeBlob(
221
223
  key: string,
222
224
  contents: Buffer | string,
223
225
  options?: {|signal?: AbortSignal|},
@@ -225,7 +227,10 @@ export class LMDBLiteCache implements Cache {
225
227
  if (!getFeatureFlag('cachePerformanceImprovements')) {
226
228
  return this.fsCache.setLargeBlob(key, contents, options);
227
229
  }
228
- return this.setBlob(key, contents);
230
+
231
+ const targetPath = this.getFileKey(key);
232
+ await this.fs.mkdirp(path.dirname(targetPath));
233
+ return this.fs.writeFile(targetPath, contents);
229
234
  }
230
235
 
231
236
  /**
@@ -244,9 +249,9 @@ export class LMDBLiteCache implements Cache {
244
249
  }
245
250
 
246
251
  async compact(targetPath: string): Promise<void> {
247
- await fs.promises.mkdir(targetPath, {recursive: true});
252
+ await this.fs.mkdirp(targetPath);
248
253
 
249
- const files = await fs.promises.readdir(this.dir);
254
+ const files = await this.fs.readdir(this.dir);
250
255
  // copy all files except data.mdb and lock.mdb to the target path (recursive)
251
256
  for (const file of files) {
252
257
  const filePath = path.join(this.dir, file);
@@ -262,6 +267,42 @@ export class LMDBLiteCache implements Cache {
262
267
  }
263
268
 
264
269
  refresh(): void {}
270
+
271
+ /**
272
+ * Streams, packages are stored in files instead of LMDB.
273
+ *
274
+ * On this case, if a cache key happens to have a parent traversal, ../..
275
+ * it is treated specially
276
+ *
277
+ * That is, something/../something and something are meant to be different
278
+ * keys.
279
+ *
280
+ * Plus we do not want to store values outside of the cache directory.
281
+ */
282
+ getFileKey(key: string): string {
283
+ const cleanKey = key
284
+ .split('/')
285
+ .map((part) => {
286
+ if (part === '..') {
287
+ return '$$__parent_dir$$';
288
+ }
289
+ return part;
290
+ })
291
+ .join('/');
292
+ return path.join(this.cacheFilesDirectory, cleanKey);
293
+ }
294
+
295
+ async clear(): Promise<void> {
296
+ await instrumentAsync('LMDBLiteCache::clear', async () => {
297
+ const keys = await this.keys();
298
+ for (const key of keys) {
299
+ await this.store.delete(key);
300
+ }
301
+
302
+ await this.fs.rimraf(this.cacheFilesDirectory);
303
+ await this.fs.mkdirp(this.cacheFilesDirectory);
304
+ });
305
+ }
265
306
  }
266
307
 
267
308
  registerSerializableClass(
@@ -6,6 +6,8 @@ import {tmpdir} from 'os';
6
6
  import {LMDBLiteCache} from '../src/index';
7
7
  import {deserialize, serialize} from 'v8';
8
8
  import assert from 'assert';
9
+ import {Worker} from 'worker_threads';
10
+ import {initializeMonitoring} from '@atlaspack/rust';
9
11
 
10
12
  const cacheDir = path.join(tmpdir(), 'lmdb-lite-cache-tests');
11
13
 
@@ -16,10 +18,6 @@ describe('LMDBLiteCache', () => {
16
18
  await fs.promises.rm(cacheDir, {recursive: true, force: true});
17
19
  });
18
20
 
19
- afterEach(() => {
20
- cache.getNativeRef().close();
21
- });
22
-
23
21
  it('can be constructed', async () => {
24
22
  cache = new LMDBLiteCache(cacheDir);
25
23
  await cache.ensure();
@@ -59,11 +57,187 @@ describe('LMDBLiteCache', () => {
59
57
  await cache.setBlob('key2', Buffer.from(serialize({value: 43})));
60
58
  await cache.compact(path.join(cacheDir, 'compact_test_compacted'));
61
59
 
62
- cache.getNativeRef().close();
63
-
64
60
  cache = new LMDBLiteCache(path.join(cacheDir, 'compact_test_compacted'));
65
61
  await cache.ensure();
66
62
  const keys = cache.keys();
67
63
  assert.deepEqual(Array.from(keys), ['key1', 'key2']);
68
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
+ });
69
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
+ }