draft: refactor project so it sits on top of a standalone app
This commit is contained in:
+661
@@ -0,0 +1,661 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [12.0.4](https://github.com/npm/cacache/compare/v12.0.3...v12.0.4) (2020-03-24)
|
||||
|
||||
|
||||
|
||||
### [12.0.3](https://github.com/npm/cacache/compare/v12.0.2...v12.0.3) (2019-08-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not chown if not running as root ([2d80af9](https://github.com/npm/cacache/commit/2d80af9))
|
||||
|
||||
|
||||
|
||||
### [12.0.2](https://github.com/npm/cacache/compare/v12.0.1...v12.0.2) (2019-07-19)
|
||||
|
||||
|
||||
|
||||
### [12.0.1](https://github.com/npm/cacache/compare/v12.0.0...v12.0.1) (2019-07-19)
|
||||
|
||||
* **deps** Abstracted out `lib/util/infer-owner.js` to
|
||||
[@npmcli/infer-owner](https://www.npmjs.com/package/@npmcli/infer-owner)
|
||||
so that it could be more easily used in other parts of the npm CLI.
|
||||
|
||||
|
||||
## [12.0.0](https://github.com/npm/cacache/compare/v11.3.3...v12.0.0) (2019-07-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* infer uid/gid instead of accepting as options ([ac84d14](https://github.com/npm/cacache/commit/ac84d14))
|
||||
* **i18n:** add another error message ([676cb32](https://github.com/npm/cacache/commit/676cb32))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* the uid gid options are no longer respected or
|
||||
necessary. As of this change, cacache will always match the cache
|
||||
contents to the ownership of the cache directory (or its parent
|
||||
directory), regardless of what the caller passes in.
|
||||
|
||||
Reasoning:
|
||||
|
||||
The number one reason to use a uid or gid option was to keep root-owned
|
||||
files from causing problems in the cache. In npm's case, this meant
|
||||
that CLI's ./lib/command.js had to work out the appropriate uid and gid,
|
||||
then pass it to the libnpmcommand module, which had to in turn pass the
|
||||
uid and gid to npm-registry-fetch, which then passed it to
|
||||
make-fetch-happen, which passed it to cacache. (For package fetching,
|
||||
pacote would be in that mix as well.)
|
||||
|
||||
Added to that, `cacache.rm()` will actually _write_ a file into the
|
||||
cache index, but has no way to accept an option so that its call to
|
||||
entry-index.js will write the index with the appropriate uid/gid.
|
||||
Little ownership bugs were all over the place, and tricky to trace
|
||||
through. (Why should make-fetch-happen even care about accepting or
|
||||
passing uids and gids? It's an http library.)
|
||||
|
||||
This change allows us to keep the cache from having mixed ownership in
|
||||
any situation.
|
||||
|
||||
Of course, this _does_ mean that if you have a root-owned but
|
||||
user-writable folder (for example, `/tmp`), then the cache will try to
|
||||
chown everything to root.
|
||||
|
||||
The solution is for the user to create a folder, make it user-owned, and
|
||||
use that, rather than relying on cacache to create the root cache folder.
|
||||
|
||||
If we decide to restore the uid/gid opts, and use ownership inferrence
|
||||
only when uid/gid are unset, then take care to also make rm take an
|
||||
option object, and pass it through to entry-index.js.
|
||||
|
||||
|
||||
|
||||
### [11.3.3](https://github.com/npm/cacache/compare/v11.3.2...v11.3.3) (2019-06-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **audit:** npm audit fix ([200a6d5](https://github.com/npm/cacache/commit/200a6d5))
|
||||
* **config:** Add ssri config 'error' option ([#146](https://github.com/npm/cacache/issues/146)) ([47de8f5](https://github.com/npm/cacache/commit/47de8f5))
|
||||
* **deps:** npm audit fix ([481a7dc](https://github.com/npm/cacache/commit/481a7dc))
|
||||
* **standard:** standard --fix ([7799149](https://github.com/npm/cacache/commit/7799149))
|
||||
* **write:** avoid another cb never called situation ([5156561](https://github.com/npm/cacache/commit/5156561))
|
||||
|
||||
|
||||
|
||||
<a name="11.3.2"></a>
|
||||
## [11.3.2](https://github.com/npm/cacache/compare/v11.3.1...v11.3.2) (2018-12-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **get:** make sure to handle errors in the .then ([b10bcd0](https://github.com/npm/cacache/commit/b10bcd0))
|
||||
|
||||
|
||||
|
||||
<a name="11.3.1"></a>
|
||||
## [11.3.1](https://github.com/npm/cacache/compare/v11.3.0...v11.3.1) (2018-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **get:** export hasContent.sync properly ([d76c920](https://github.com/npm/cacache/commit/d76c920))
|
||||
|
||||
|
||||
|
||||
<a name="11.3.0"></a>
|
||||
# [11.3.0](https://github.com/npm/cacache/compare/v11.2.0...v11.3.0) (2018-11-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **get:** add sync API for reading ([db1e094](https://github.com/npm/cacache/commit/db1e094))
|
||||
|
||||
|
||||
|
||||
<a name="11.2.0"></a>
|
||||
# [11.2.0](https://github.com/npm/cacache/compare/v11.1.0...v11.2.0) (2018-08-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **read:** add sync support to other internal read.js fns ([fe638b6](https://github.com/npm/cacache/commit/fe638b6))
|
||||
|
||||
|
||||
|
||||
<a name="11.1.0"></a>
|
||||
# [11.1.0](https://github.com/npm/cacache/compare/v11.0.3...v11.1.0) (2018-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **read:** add sync support for low-level content read ([b43af83](https://github.com/npm/cacache/commit/b43af83))
|
||||
|
||||
|
||||
|
||||
<a name="11.0.3"></a>
|
||||
## [11.0.3](https://github.com/npm/cacache/compare/v11.0.2...v11.0.3) (2018-08-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **config:** add ssri config options ([#136](https://github.com/npm/cacache/issues/136)) ([10d5d9a](https://github.com/npm/cacache/commit/10d5d9a))
|
||||
* **perf:** refactor content.read to avoid lstats ([c5ac10e](https://github.com/npm/cacache/commit/c5ac10e))
|
||||
* **test:** oops when removing safe-buffer ([1950490](https://github.com/npm/cacache/commit/1950490))
|
||||
|
||||
|
||||
|
||||
<a name="11.0.2"></a>
|
||||
## [11.0.2](https://github.com/npm/cacache/compare/v11.0.1...v11.0.2) (2018-05-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **verify:** size param no longer lost in a verify ([#131](https://github.com/npm/cacache/issues/131)) ([c614a19](https://github.com/npm/cacache/commit/c614a19)), closes [#130](https://github.com/npm/cacache/issues/130)
|
||||
|
||||
|
||||
|
||||
<a name="11.0.1"></a>
|
||||
## [11.0.1](https://github.com/npm/cacache/compare/v11.0.0...v11.0.1) (2018-04-10)
|
||||
|
||||
|
||||
|
||||
<a name="11.0.0"></a>
|
||||
# [11.0.0](https://github.com/npm/cacache/compare/v10.0.4...v11.0.0) (2018-04-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **opts:** use figgy-pudding for opts ([#128](https://github.com/npm/cacache/issues/128)) ([33d4eed](https://github.com/npm/cacache/commit/33d4eed))
|
||||
|
||||
|
||||
### meta
|
||||
|
||||
* drop support for node@4 ([529f347](https://github.com/npm/cacache/commit/529f347))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* node@4 is no longer supported
|
||||
|
||||
|
||||
|
||||
<a name="10.0.4"></a>
|
||||
## [10.0.4](https://github.com/npm/cacache/compare/v10.0.3...v10.0.4) (2018-02-16)
|
||||
|
||||
|
||||
|
||||
<a name="10.0.3"></a>
|
||||
## [10.0.3](https://github.com/npm/cacache/compare/v10.0.2...v10.0.3) (2018-02-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **content:** rethrow aggregate errors as ENOENT ([fa918f5](https://github.com/npm/cacache/commit/fa918f5))
|
||||
|
||||
|
||||
|
||||
<a name="10.0.2"></a>
|
||||
## [10.0.2](https://github.com/npm/cacache/compare/v10.0.1...v10.0.2) (2018-01-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ls:** deleted entries could cause a premature stream EOF ([347dc36](https://github.com/npm/cacache/commit/347dc36))
|
||||
|
||||
|
||||
|
||||
<a name="10.0.1"></a>
|
||||
## [10.0.1](https://github.com/npm/cacache/compare/v10.0.0...v10.0.1) (2017-11-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **move-file:** actually use the fallback to `move-concurrently` (#110) ([073fbe1](https://github.com/npm/cacache/commit/073fbe1))
|
||||
|
||||
|
||||
|
||||
<a name="10.0.0"></a>
|
||||
# [10.0.0](https://github.com/npm/cacache/compare/v9.3.0...v10.0.0) (2017-10-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **license:** relicense to ISC (#111) ([fdbb4e5](https://github.com/npm/cacache/commit/fdbb4e5))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* more copyFile benchmarks ([63787bb](https://github.com/npm/cacache/commit/63787bb))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **license:** the license has been changed from CC0-1.0 to ISC.
|
||||
|
||||
|
||||
|
||||
<a name="9.3.0"></a>
|
||||
# [9.3.0](https://github.com/npm/cacache/compare/v9.2.9...v9.3.0) (2017-10-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **copy:** added cacache.get.copy api for fast copies (#107) ([067b5f6](https://github.com/npm/cacache/commit/067b5f6))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.9"></a>
|
||||
## [9.2.9](https://github.com/npm/cacache/compare/v9.2.8...v9.2.9) (2017-06-17)
|
||||
|
||||
|
||||
|
||||
<a name="9.2.8"></a>
|
||||
## [9.2.8](https://github.com/npm/cacache/compare/v9.2.7...v9.2.8) (2017-06-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ssri:** bump ssri for bugfix ([c3232ea](https://github.com/npm/cacache/commit/c3232ea))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.7"></a>
|
||||
## [9.2.7](https://github.com/npm/cacache/compare/v9.2.6...v9.2.7) (2017-06-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **content:** make verified content completely read-only (#96) ([4131196](https://github.com/npm/cacache/commit/4131196))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.6"></a>
|
||||
## [9.2.6](https://github.com/npm/cacache/compare/v9.2.5...v9.2.6) (2017-05-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **node:** update ssri to prevent old node 4 crash ([5209ffe](https://github.com/npm/cacache/commit/5209ffe))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.5"></a>
|
||||
## [9.2.5](https://github.com/npm/cacache/compare/v9.2.4...v9.2.5) (2017-05-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** fix lockfile issues and bump ssri ([84e1d7e](https://github.com/npm/cacache/commit/84e1d7e))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.4"></a>
|
||||
## [9.2.4](https://github.com/npm/cacache/compare/v9.2.3...v9.2.4) (2017-05-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** bumping deps ([bbccb12](https://github.com/npm/cacache/commit/bbccb12))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.3"></a>
|
||||
## [9.2.3](https://github.com/npm/cacache/compare/v9.2.2...v9.2.3) (2017-05-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **rm:** stop crashing if content is missing on rm ([ac90bc0](https://github.com/npm/cacache/commit/ac90bc0))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.2"></a>
|
||||
## [9.2.2](https://github.com/npm/cacache/compare/v9.2.1...v9.2.2) (2017-05-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **i18n:** lets pretend this didn't happen ([519b4ee](https://github.com/npm/cacache/commit/519b4ee))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.1"></a>
|
||||
## [9.2.1](https://github.com/npm/cacache/compare/v9.2.0...v9.2.1) (2017-05-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docs:** fixing translation messup ([bb9e4f9](https://github.com/npm/cacache/commit/bb9e4f9))
|
||||
|
||||
|
||||
|
||||
<a name="9.2.0"></a>
|
||||
# [9.2.0](https://github.com/npm/cacache/compare/v9.1.0...v9.2.0) (2017-05-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** add Spanish translation for API ([531f9a4](https://github.com/npm/cacache/commit/531f9a4))
|
||||
|
||||
|
||||
|
||||
<a name="9.1.0"></a>
|
||||
# [9.1.0](https://github.com/npm/cacache/compare/v9.0.0...v9.1.0) (2017-05-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** Add Spanish translation and i18n setup (#91) ([323b90c](https://github.com/npm/cacache/commit/323b90c))
|
||||
|
||||
|
||||
|
||||
<a name="9.0.0"></a>
|
||||
# [9.0.0](https://github.com/npm/cacache/compare/v8.0.0...v9.0.0) (2017-04-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **memoization:** actually use the LRU ([0e55dc9](https://github.com/npm/cacache/commit/0e55dc9))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **memoization:** memoizers can be injected through opts.memoize (#90) ([e5614c7](https://github.com/npm/cacache/commit/e5614c7))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **memoization:** If you were passing an object to opts.memoize, it will now be used as an injected memoization object. If you were only passing booleans and other non-objects through that option, no changes are needed.
|
||||
|
||||
|
||||
|
||||
<a name="8.0.0"></a>
|
||||
# [8.0.0](https://github.com/npm/cacache/compare/v7.1.0...v8.0.0) (2017-04-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **read:** change hasContent to return {sri, size} (#88) ([bad6c49](https://github.com/npm/cacache/commit/bad6c49)), closes [#87](https://github.com/npm/cacache/issues/87)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **read:** hasContent now returns an object with `{sri, size}` instead of `sri`. Use `result.sri` anywhere that needed the old return value.
|
||||
|
||||
|
||||
|
||||
<a name="7.1.0"></a>
|
||||
# [7.1.0](https://github.com/npm/cacache/compare/v7.0.5...v7.1.0) (2017-04-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **size:** handle content size info (#49) ([91230af](https://github.com/npm/cacache/commit/91230af))
|
||||
|
||||
|
||||
|
||||
<a name="7.0.5"></a>
|
||||
## [7.0.5](https://github.com/npm/cacache/compare/v7.0.4...v7.0.5) (2017-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **integrity:** new ssri with fixed integrity stream ([6d13e8e](https://github.com/npm/cacache/commit/6d13e8e))
|
||||
* **write:** wrap stuff in promises to improve errors ([3624fc5](https://github.com/npm/cacache/commit/3624fc5))
|
||||
|
||||
|
||||
|
||||
<a name="7.0.4"></a>
|
||||
## [7.0.4](https://github.com/npm/cacache/compare/v7.0.3...v7.0.4) (2017-04-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fix-owner:** throw away ENOENTs on chownr ([d49bbcd](https://github.com/npm/cacache/commit/d49bbcd))
|
||||
|
||||
|
||||
|
||||
<a name="7.0.3"></a>
|
||||
## [7.0.3](https://github.com/npm/cacache/compare/v7.0.2...v7.0.3) (2017-04-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **read:** fixing error message for integrity verification failures ([9d4f0a5](https://github.com/npm/cacache/commit/9d4f0a5))
|
||||
|
||||
|
||||
|
||||
<a name="7.0.2"></a>
|
||||
## [7.0.2](https://github.com/npm/cacache/compare/v7.0.1...v7.0.2) (2017-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **integrity:** use EINTEGRITY error code and update ssri ([8dc2e62](https://github.com/npm/cacache/commit/8dc2e62))
|
||||
|
||||
|
||||
|
||||
<a name="7.0.1"></a>
|
||||
## [7.0.1](https://github.com/npm/cacache/compare/v7.0.0...v7.0.1) (2017-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docs:** fix header name conflict in readme ([afcd456](https://github.com/npm/cacache/commit/afcd456))
|
||||
|
||||
|
||||
|
||||
<a name="7.0.0"></a>
|
||||
# [7.0.0](https://github.com/npm/cacache/compare/v6.3.0...v7.0.0) (2017-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **test:** fix content.write tests when running in docker ([d2e9b6a](https://github.com/npm/cacache/commit/d2e9b6a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **integrity:** subresource integrity support (#78) ([b1e731f](https://github.com/npm/cacache/commit/b1e731f))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **integrity:** The entire API has been overhauled to use SRI hashes instead of digest/hashAlgorithm pairs. SRI hashes follow the Subresource Integrity standard and support strings and objects compatible with [`ssri`](https://npm.im/ssri).
|
||||
|
||||
* This change bumps the index version, which will invalidate all previous index entries. Content entries will remain intact, and existing caches will automatically reuse any content from before this breaking change.
|
||||
|
||||
* `cacache.get.info()`, `cacache.ls()`, and `cacache.ls.stream()` will now return objects that looks like this:
|
||||
|
||||
```
|
||||
{
|
||||
key: String,
|
||||
integrity: '<algorithm>-<base64hash>',
|
||||
path: ContentPath,
|
||||
time: Date<ms>,
|
||||
metadata: Any
|
||||
}
|
||||
```
|
||||
|
||||
* `opts.digest` and `opts.hashAlgorithm` are obsolete for any API calls that used them.
|
||||
|
||||
* Anywhere `opts.digest` was accepted, `opts.integrity` is now an option. Any valid SRI hash is accepted here -- multiple hash entries will be resolved according to the standard: first, the "strongest" hash algorithm will be picked, and then each of the entries for that algorithm will be matched against the content. Content will be validated if *any* of the entries match (so, a single integrity string can be used for multiple "versions" of the same document/data).
|
||||
|
||||
* `put.byDigest()`, `put.stream.byDigest`, `get.byDigest()` and `get.stream.byDigest()` now expect an SRI instead of a `digest` + `opts.hashAlgorithm` pairing.
|
||||
|
||||
* `get.hasContent()` now expects an integrity hash instead of a digest. If content exists, it will return the specific single integrity hash that was found in the cache.
|
||||
|
||||
* `verify()` has learned to handle integrity-based caches, and forgotten how to handle old-style cache indices due to the format change.
|
||||
|
||||
* `cacache.rm.content()` now expects an integrity hash instead of a hex digest.
|
||||
|
||||
|
||||
|
||||
<a name="6.3.0"></a>
|
||||
# [6.3.0](https://github.com/npm/cacache/compare/v6.2.0...v6.3.0) (2017-04-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fixOwner:** ignore EEXIST race condition from mkdirp ([4670e9b](https://github.com/npm/cacache/commit/4670e9b))
|
||||
* **index:** ignore index removal races when inserting ([b9d2fa2](https://github.com/npm/cacache/commit/b9d2fa2))
|
||||
* **memo:** use lru-cache for better mem management (#75) ([d8ac5aa](https://github.com/npm/cacache/commit/d8ac5aa))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **dependencies:** Switch to move-concurrently (#77) ([dc6482d](https://github.com/npm/cacache/commit/dc6482d))
|
||||
|
||||
|
||||
|
||||
<a name="6.2.0"></a>
|
||||
# [6.2.0](https://github.com/npm/cacache/compare/v6.1.2...v6.2.0) (2017-03-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **index:** additional bucket entry verification with checksum (#72) ([f8e0f25](https://github.com/npm/cacache/commit/f8e0f25))
|
||||
* **verify:** return fixOwner.chownr promise ([6818521](https://github.com/npm/cacache/commit/6818521))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **tmp:** safe tmp dir creation/management util (#73) ([c42da71](https://github.com/npm/cacache/commit/c42da71))
|
||||
|
||||
|
||||
|
||||
<a name="6.1.2"></a>
|
||||
## [6.1.2](https://github.com/npm/cacache/compare/v6.1.1...v6.1.2) (2017-03-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **index:** set default hashAlgorithm ([d6eb2f0](https://github.com/npm/cacache/commit/d6eb2f0))
|
||||
|
||||
|
||||
|
||||
<a name="6.1.1"></a>
|
||||
## [6.1.1](https://github.com/npm/cacache/compare/v6.1.0...v6.1.1) (2017-03-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **coverage:** bumping coverage for verify (#71) ([0b7faf6](https://github.com/npm/cacache/commit/0b7faf6))
|
||||
* **deps:** glob should have been a regular dep :< ([0640bc4](https://github.com/npm/cacache/commit/0640bc4))
|
||||
|
||||
|
||||
|
||||
<a name="6.1.0"></a>
|
||||
# [6.1.0](https://github.com/npm/cacache/compare/v6.0.2...v6.1.0) (2017-03-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **coverage:** more coverage for content reads (#70) ([ef4f70a](https://github.com/npm/cacache/commit/ef4f70a))
|
||||
* **tests:** use safe-buffer because omfg (#69) ([6ab8132](https://github.com/npm/cacache/commit/6ab8132))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **rm:** limited rm.all and fixed bugs (#66) ([d5d25ba](https://github.com/npm/cacache/commit/d5d25ba)), closes [#66](https://github.com/npm/cacache/issues/66)
|
||||
* **verify:** tested, working cache verifier/gc (#68) ([45ad77a](https://github.com/npm/cacache/commit/45ad77a))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.2"></a>
|
||||
## [6.0.2](https://github.com/npm/cacache/compare/v6.0.1...v6.0.2) (2017-03-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **index:** segment cache items with another subbucket (#64) ([c3644e5](https://github.com/npm/cacache/commit/c3644e5))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.1"></a>
|
||||
## [6.0.1](https://github.com/npm/cacache/compare/v6.0.0...v6.0.1) (2017-03-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docs:** Missed spots in README ([8ffb7fa](https://github.com/npm/cacache/commit/8ffb7fa))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.0"></a>
|
||||
# [6.0.0](https://github.com/npm/cacache/compare/v5.0.3...v6.0.0) (2017-03-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** keep memo cache mostly-internal ([2f72d0a](https://github.com/npm/cacache/commit/2f72d0a))
|
||||
* **content:** use the rest of the string, not the whole string ([fa8f3c3](https://github.com/npm/cacache/commit/fa8f3c3))
|
||||
* **deps:** removed `format-number@2.0.2` ([1187791](https://github.com/npm/cacache/commit/1187791))
|
||||
* **deps:** removed inflight@1.0.6 ([0d1819c](https://github.com/npm/cacache/commit/0d1819c))
|
||||
* **deps:** rimraf@2.6.1 ([9efab6b](https://github.com/npm/cacache/commit/9efab6b))
|
||||
* **deps:** standard@9.0.0 ([4202cba](https://github.com/npm/cacache/commit/4202cba))
|
||||
* **deps:** tap@10.3.0 ([aa03088](https://github.com/npm/cacache/commit/aa03088))
|
||||
* **deps:** weallcontribute@1.0.8 ([ad4f4dc](https://github.com/npm/cacache/commit/ad4f4dc))
|
||||
* **docs:** add security note to hashKey ([03f81ba](https://github.com/npm/cacache/commit/03f81ba))
|
||||
* **hashes:** change default hashAlgorithm to sha512 ([ea00ba6](https://github.com/npm/cacache/commit/ea00ba6))
|
||||
* **hashes:** missed a spot for hashAlgorithm defaults ([45997d8](https://github.com/npm/cacache/commit/45997d8))
|
||||
* **index:** add length header before JSON for verification ([fb8cb4d](https://github.com/npm/cacache/commit/fb8cb4d))
|
||||
* **index:** change index filenames to sha1s of keys ([bbc5fca](https://github.com/npm/cacache/commit/bbc5fca))
|
||||
* **index:** who cares about race conditions anyway ([b1d3888](https://github.com/npm/cacache/commit/b1d3888))
|
||||
* **perf:** bulk-read get+read for massive speed ([d26cdf9](https://github.com/npm/cacache/commit/d26cdf9))
|
||||
* **perf:** use bulk file reads for index reads ([79a8891](https://github.com/npm/cacache/commit/79a8891))
|
||||
* **put-stream:** remove tmp file on stream insert error ([65f6632](https://github.com/npm/cacache/commit/65f6632))
|
||||
* **put-stream:** robustified and predictibilized ([daf9e08](https://github.com/npm/cacache/commit/daf9e08))
|
||||
* **put-stream:** use new promise API for moves ([1d36013](https://github.com/npm/cacache/commit/1d36013))
|
||||
* **readme:** updated to reflect new default hashAlgo ([c60a2fa](https://github.com/npm/cacache/commit/c60a2fa))
|
||||
* **verify:** tiny typo fix ([db22d05](https://github.com/npm/cacache/commit/db22d05))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** converted external api ([7bf032f](https://github.com/npm/cacache/commit/7bf032f))
|
||||
* **cacache:** exported clearMemoized() utility ([8d2c5b6](https://github.com/npm/cacache/commit/8d2c5b6))
|
||||
* **cache:** add versioning to content and index ([31bc549](https://github.com/npm/cacache/commit/31bc549))
|
||||
* **content:** collate content files into subdirs ([c094d9f](https://github.com/npm/cacache/commit/c094d9f))
|
||||
* **deps:** `@npmcorp/move@1.0.0` ([bdd00bf](https://github.com/npm/cacache/commit/bdd00bf))
|
||||
* **deps:** `bluebird@3.4.7` ([3a17aff](https://github.com/npm/cacache/commit/3a17aff))
|
||||
* **deps:** `promise-inflight@1.0.1` ([a004fe6](https://github.com/npm/cacache/commit/a004fe6))
|
||||
* **get:** added memoization support for get ([c77d794](https://github.com/npm/cacache/commit/c77d794))
|
||||
* **get:** export hasContent ([2956ec3](https://github.com/npm/cacache/commit/2956ec3))
|
||||
* **index:** add hashAlgorithm and format insert ret val ([b639746](https://github.com/npm/cacache/commit/b639746))
|
||||
* **index:** collate index files into subdirs ([e8402a5](https://github.com/npm/cacache/commit/e8402a5))
|
||||
* **index:** promisify entry index ([cda3335](https://github.com/npm/cacache/commit/cda3335))
|
||||
* **memo:** added memoization lib ([da07b92](https://github.com/npm/cacache/commit/da07b92))
|
||||
* **memo:** export memoization api ([954b1b3](https://github.com/npm/cacache/commit/954b1b3))
|
||||
* **move-file:** add move fallback for weird errors ([5cf4616](https://github.com/npm/cacache/commit/5cf4616))
|
||||
* **perf:** bulk content write api ([51b536e](https://github.com/npm/cacache/commit/51b536e))
|
||||
* **put:** added memoization support to put ([b613a70](https://github.com/npm/cacache/commit/b613a70))
|
||||
* **read:** switched to promises ([a869362](https://github.com/npm/cacache/commit/a869362))
|
||||
* **rm:** added memoization support to rm ([4205cf0](https://github.com/npm/cacache/commit/4205cf0))
|
||||
* **rm:** switched to promises ([a000d24](https://github.com/npm/cacache/commit/a000d24))
|
||||
* **util:** promise-inflight ownership fix requests ([9517cd7](https://github.com/npm/cacache/commit/9517cd7))
|
||||
* **util:** use promises for api ([ae204bb](https://github.com/npm/cacache/commit/ae204bb))
|
||||
* **verify:** converted to Promises ([f0b3974](https://github.com/npm/cacache/commit/f0b3974))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* cache: index/content directories are now versioned. Previous caches are no longer compatible and cannot be migrated.
|
||||
* util: fix-owner now uses Promises instead of callbacks
|
||||
* index: Previously-generated index entries are no longer compatible and the index must be regenerated.
|
||||
* index: The index format has changed and previous caches are no longer compatible. Existing caches will need to be regenerated.
|
||||
* hashes: Default hashAlgorithm changed from sha1 to sha512. If you
|
||||
rely on the prior setting, pass `opts.hashAlgorithm` in explicitly.
|
||||
* content: Previously-generated content directories are no longer compatible
|
||||
and must be regenerated.
|
||||
* verify: API is now promise-based
|
||||
* read: Switches to a Promise-based API and removes callback stuff
|
||||
* rm: Switches to a Promise-based API and removes callback stuff
|
||||
* index: this changes the API to work off promises instead of callbacks
|
||||
* api: this means we are going all in on promises now
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) npm, Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE COPYRIGHT HOLDER DISCLAIMS
|
||||
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
|
||||
USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
+628
@@ -0,0 +1,628 @@
|
||||
# cacache [](https://npm.im/cacache) [](https://npm.im/cacache) [](https://travis-ci.org/zkat/cacache) [](https://ci.appveyor.com/project/zkat/cacache) [](https://coveralls.io/github/zkat/cacache?branch=latest)
|
||||
|
||||
[`cacache`](https://github.com/zkat/cacache) es una librería de Node.js para
|
||||
manejar caches locales en disco, con acceso tanto con claves únicas como
|
||||
direcciones de contenido (hashes/hacheos). Es súper rápida, excelente con el
|
||||
acceso concurrente, y jamás te dará datos incorrectos, aún si se corrompen o
|
||||
manipulan directamente los ficheros del cache.
|
||||
|
||||
El propósito original era reemplazar el caché local de
|
||||
[npm](https://npm.im/npm), pero se puede usar por su propia cuenta.
|
||||
|
||||
_Traducciones: [English](README.md)_
|
||||
|
||||
## Instalación
|
||||
|
||||
`$ npm install --save cacache`
|
||||
|
||||
## Índice
|
||||
|
||||
* [Ejemplo](#ejemplo)
|
||||
* [Características](#características)
|
||||
* [Cómo Contribuir](#cómo-contribuir)
|
||||
* [API](#api)
|
||||
* [Usando el API en español](#localized-api)
|
||||
* Leer
|
||||
* [`ls`](#ls)
|
||||
* [`ls.flujo`](#ls-stream)
|
||||
* [`saca`](#get-data)
|
||||
* [`saca.flujo`](#get-stream)
|
||||
* [`saca.info`](#get-info)
|
||||
* [`saca.tieneDatos`](#get-hasContent)
|
||||
* Escribir
|
||||
* [`mete`](#put-data)
|
||||
* [`mete.flujo`](#put-stream)
|
||||
* [opciones para `mete*`](#put-options)
|
||||
* [`rm.todo`](#rm-all)
|
||||
* [`rm.entrada`](#rm-entry)
|
||||
* [`rm.datos`](#rm-content)
|
||||
* Utilidades
|
||||
* [`ponLenguaje`](#set-locale)
|
||||
* [`limpiaMemoizado`](#clear-memoized)
|
||||
* [`tmp.hazdir`](#tmp-mkdir)
|
||||
* [`tmp.conTmp`](#with-tmp)
|
||||
* Integridad
|
||||
* [Subresource Integrity](#integrity)
|
||||
* [`verifica`](#verify)
|
||||
* [`verifica.ultimaVez`](#verify-last-run)
|
||||
|
||||
### Ejemplo
|
||||
|
||||
```javascript
|
||||
const cacache = require('cacache/es')
|
||||
const fs = require('fs')
|
||||
|
||||
const tarbol = '/ruta/a/mi-tar.tgz'
|
||||
const rutaCache = '/tmp/my-toy-cache'
|
||||
const clave = 'mi-clave-única-1234'
|
||||
|
||||
// ¡Añádelo al caché! Usa `rutaCache` como raíz del caché.
|
||||
cacache.mete(rutaCache, clave, '10293801983029384').then(integrity => {
|
||||
console.log(`Saved content to ${rutaCache}.`)
|
||||
})
|
||||
|
||||
const destino = '/tmp/mytar.tgz'
|
||||
|
||||
// Copia el contenido del caché a otro fichero, pero esta vez con flujos.
|
||||
cacache.saca.flujo(
|
||||
rutaCache, clave
|
||||
).pipe(
|
||||
fs.createWriteStream(destino)
|
||||
).on('finish', () => {
|
||||
console.log('extracción completada')
|
||||
})
|
||||
|
||||
// La misma cosa, pero accesando el contenido directamente, sin tocar el índice.
|
||||
cacache.saca.porHacheo(rutaCache, integridad).then(datos => {
|
||||
fs.writeFile(destino, datos, err => {
|
||||
console.log('datos del tarbol sacados basado en su sha512, y escrito a otro fichero')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Características
|
||||
|
||||
* Extracción por clave o por dirección de contenido (shasum, etc)
|
||||
* Usa el estándard de web, [Subresource Integrity](#integrity)
|
||||
* Compatible con multiples algoritmos - usa sha1, sha512, etc, en el mismo caché sin problema
|
||||
* Entradas con contenido idéntico comparten ficheros
|
||||
* Tolerancia de fallas (inmune a corrupción, ficheros parciales, carreras de proceso, etc)
|
||||
* Verificación completa de datos cuando (escribiendo y leyendo)
|
||||
* Concurrencia rápida, segura y "lockless"
|
||||
* Compatible con `stream`s (flujos)
|
||||
* Compatible con `Promise`s (promesas)
|
||||
* Bastante rápida -- acceso, incluyendo verificación, en microsegundos
|
||||
* Almacenaje de metadatos arbitrarios
|
||||
* Colección de basura y verificación adicional fuera de banda
|
||||
* Cobertura rigurosa de pruebas
|
||||
* Probablente hay un "Bloom filter" por ahí en algún lado. Eso le mola a la gente, ¿Verdad? 🤔
|
||||
|
||||
### Cómo Contribuir
|
||||
|
||||
El equipo de cacache felizmente acepta contribuciones de código y otras maneras de participación. ¡Hay muchas formas diferentes de contribuir! La [Guía de Colaboradores](CONTRIBUTING.md) (en inglés) tiene toda la información que necesitas para cualquier tipo de contribución: todo desde cómo reportar errores hasta cómo someter parches con nuevas características. Con todo y eso, no se preocupe por si lo que haces está exáctamente correcto: no hay ningún problema en hacer preguntas si algo no está claro, o no lo encuentras.
|
||||
|
||||
El equipo de cacache tiene miembros hispanohablantes: es completamente aceptable crear `issues` y `pull requests` en español/castellano.
|
||||
|
||||
Todos los participantes en este proyecto deben obedecer el [Código de Conducta](CODE_OF_CONDUCT.md) (en inglés), y en general actuar de forma amable y respetuosa mientras participan en esta comunidad.
|
||||
|
||||
Por favor refiérase al [Historial de Cambios](CHANGELOG.md) (en inglés) para detalles sobre cambios importantes incluídos en cada versión.
|
||||
|
||||
Finalmente, cacache tiene un sistema de localización de lenguaje. Si te interesa añadir lenguajes o mejorar los que existen, mira en el directorio `./locales` para comenzar.
|
||||
|
||||
Happy hacking!
|
||||
|
||||
### API
|
||||
|
||||
#### <a name="localized-api"></a> Usando el API en español
|
||||
|
||||
cacache incluye una traducción completa de su API al castellano, con las mismas
|
||||
características. Para usar el API como está documentado en este documento, usa
|
||||
`require('cacache/es')`
|
||||
|
||||
cacache también tiene otros lenguajes: encuéntralos bajo `./locales`, y podrás
|
||||
usar el API en ese lenguaje con `require('cacache/<lenguaje>')`
|
||||
|
||||
#### <a name="ls"></a> `> cacache.ls(cache) -> Promise<Object>`
|
||||
|
||||
Enumera todas las entradas en el caché, dentro de un solo objeto. Cada entrada
|
||||
en el objeto tendrá como clave la clave única usada para el índice, el valor
|
||||
siendo un objeto de [`saca.info`](#get-info).
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.ls(rutaCache).then(console.log)
|
||||
// Salida
|
||||
{
|
||||
'my-thing': {
|
||||
key: 'my-thing',
|
||||
integrity: 'sha512-BaSe64/EnCoDED+HAsh=='
|
||||
path: '.testcache/content/deadbeef', // unido con `rutaCache`
|
||||
time: 12345698490,
|
||||
size: 4023948,
|
||||
metadata: {
|
||||
name: 'blah',
|
||||
version: '1.2.3',
|
||||
description: 'this was once a package but now it is my-thing'
|
||||
}
|
||||
},
|
||||
'other-thing': {
|
||||
key: 'other-thing',
|
||||
integrity: 'sha1-ANothER+hasH=',
|
||||
path: '.testcache/content/bada55',
|
||||
time: 11992309289,
|
||||
size: 111112
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### <a name="ls-stream"></a> `> cacache.ls.flujo(cache) -> Readable`
|
||||
|
||||
Enumera todas las entradas en el caché, emitiendo un objeto de
|
||||
[`saca.info`](#get-info) por cada evento de `data` en el flujo.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.ls.flujo(rutaCache).on('data', console.log)
|
||||
// Salida
|
||||
{
|
||||
key: 'my-thing',
|
||||
integrity: 'sha512-BaSe64HaSh',
|
||||
path: '.testcache/content/deadbeef', // unido con `rutaCache`
|
||||
time: 12345698490,
|
||||
size: 13423,
|
||||
metadata: {
|
||||
name: 'blah',
|
||||
version: '1.2.3',
|
||||
description: 'this was once a package but now it is my-thing'
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
key: 'other-thing',
|
||||
integrity: 'whirlpool-WoWSoMuchSupport',
|
||||
path: '.testcache/content/bada55',
|
||||
time: 11992309289,
|
||||
size: 498023984029
|
||||
}
|
||||
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### <a name="get-data"></a> `> cacache.saca(cache, clave, [ops]) -> Promise({data, metadata, integrity})`
|
||||
|
||||
Devuelve un objeto con los datos, hacheo de integridad y metadatos identificados
|
||||
por la `clave`. La propiedad `data` de este objeto será una instancia de
|
||||
`Buffer` con los datos almacenados en el caché. to do with it! cacache just
|
||||
won't care.
|
||||
|
||||
`integrity` es un `string` de [Subresource Integrity](#integrity). Dígase, un
|
||||
`string` que puede ser usado para verificar a la `data`, que tiene como formato
|
||||
`<algoritmo>-<hacheo-integridad-base64>`.
|
||||
|
||||
So no existe ninguna entrada identificada por `clave`, o se los datos
|
||||
almacenados localmente fallan verificación, el `Promise` fallará.
|
||||
|
||||
Una sub-función, `saca.porHacheo`, tiene casi el mismo comportamiento, excepto
|
||||
que busca entradas usando el hacheo de integridad, sin tocar el índice general.
|
||||
Esta versión *sólo* devuelve `data`, sin ningún objeto conteniéndola.
|
||||
|
||||
##### Nota
|
||||
|
||||
Esta función lee la entrada completa a la memoria antes de devolverla. Si estás
|
||||
almacenando datos Muy Grandes, es posible que [`saca.flujo`](#get-stream) sea
|
||||
una mejor solución.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
// Busca por clave
|
||||
cache.saca(rutaCache, 'my-thing').then(console.log)
|
||||
// Salida:
|
||||
{
|
||||
metadata: {
|
||||
thingName: 'my'
|
||||
},
|
||||
integrity: 'sha512-BaSe64HaSh',
|
||||
data: Buffer#<deadbeef>,
|
||||
size: 9320
|
||||
}
|
||||
|
||||
// Busca por hacheo
|
||||
cache.saca.porHacheo(rutaCache, 'sha512-BaSe64HaSh').then(console.log)
|
||||
// Salida:
|
||||
Buffer#<deadbeef>
|
||||
```
|
||||
|
||||
#### <a name="get-stream"></a> `> cacache.saca.flujo(cache, clave, [ops]) -> Readable`
|
||||
|
||||
Devuelve un [Readable
|
||||
Stream](https://nodejs.org/api/stream.html#stream_readable_streams) de los datos
|
||||
almacenados bajo `clave`.
|
||||
|
||||
So no existe ninguna entrada identificada por `clave`, o se los datos
|
||||
almacenados localmente fallan verificación, el `Promise` fallará.
|
||||
|
||||
`metadata` y `integrity` serán emitidos como eventos antes de que el flujo
|
||||
cierre.
|
||||
|
||||
Una sub-función, `saca.flujo.porHacheo`, tiene casi el mismo comportamiento,
|
||||
excepto que busca entradas usando el hacheo de integridad, sin tocar el índice
|
||||
general. Esta versión no emite eventos de `metadata` o `integrity`.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
// Busca por clave
|
||||
cache.saca.flujo(
|
||||
rutaCache, 'my-thing'
|
||||
).on('metadata', metadata => {
|
||||
console.log('metadata:', metadata)
|
||||
}).on('integrity', integrity => {
|
||||
console.log('integrity:', integrity)
|
||||
}).pipe(
|
||||
fs.createWriteStream('./x.tgz')
|
||||
)
|
||||
// Salidas:
|
||||
metadata: { ... }
|
||||
integrity: 'sha512-SoMeDIGest+64=='
|
||||
|
||||
// Busca por hacheo
|
||||
cache.saca.flujo.porHacheo(
|
||||
rutaCache, 'sha512-SoMeDIGest+64=='
|
||||
).pipe(
|
||||
fs.createWriteStream('./x.tgz')
|
||||
)
|
||||
```
|
||||
|
||||
#### <a name="get-info"></a> `> cacache.saca.info(cache, clave) -> Promise`
|
||||
|
||||
Busca la `clave` en el índice del caché, devolviendo información sobre la
|
||||
entrada si existe.
|
||||
|
||||
##### Campos
|
||||
|
||||
* `key` - Clave de la entrada. Igual al argumento `clave`.
|
||||
* `integrity` - [hacheo de Subresource Integrity](#integrity) del contenido al que se refiere esta entrada.
|
||||
* `path` - Dirección del fichero de datos almacenados, unida al argumento `cache`.
|
||||
* `time` - Hora de creación de la entrada
|
||||
* `metadata` - Metadatos asignados a esta entrada por el usuario
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.saca.info(rutaCache, 'my-thing').then(console.log)
|
||||
|
||||
// Salida
|
||||
{
|
||||
key: 'my-thing',
|
||||
integrity: 'sha256-MUSTVERIFY+ALL/THINGS=='
|
||||
path: '.testcache/content/deadbeef',
|
||||
time: 12345698490,
|
||||
size: 849234,
|
||||
metadata: {
|
||||
name: 'blah',
|
||||
version: '1.2.3',
|
||||
description: 'this was once a package but now it is my-thing'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### <a name="get-hasContent"></a> `> cacache.saca.tieneDatos(cache, integrity) -> Promise`
|
||||
|
||||
Busca un [hacheo Subresource Integrity](#integrity) en el caché. Si existe el
|
||||
contenido asociado con `integrity`, devuelve un objeto con dos campos: el hacheo
|
||||
_específico_ que se usó para la búsqueda, `sri`, y el tamaño total del
|
||||
contenido, `size`. Si no existe ningún contenido asociado con `integrity`,
|
||||
devuelve `false`.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.saca.tieneDatos(rutaCache, 'sha256-MUSTVERIFY+ALL/THINGS==').then(console.log)
|
||||
|
||||
// Salida
|
||||
{
|
||||
sri: {
|
||||
source: 'sha256-MUSTVERIFY+ALL/THINGS==',
|
||||
algorithm: 'sha256',
|
||||
digest: 'MUSTVERIFY+ALL/THINGS==',
|
||||
options: []
|
||||
},
|
||||
size: 9001
|
||||
}
|
||||
|
||||
cacache.saca.tieneDatos(rutaCache, 'sha521-NOT+IN/CACHE==').then(console.log)
|
||||
|
||||
// Salida
|
||||
false
|
||||
```
|
||||
|
||||
#### <a name="put-data"></a> `> cacache.mete(cache, clave, datos, [ops]) -> Promise`
|
||||
|
||||
Inserta `datos` en el caché. El `Promise` devuelto se resuelve con un hacheo
|
||||
(generado conforme a [`ops.algorithms`](#optsalgorithms)) después que la entrada
|
||||
haya sido escrita en completo.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
fetch(
|
||||
'https://registry.npmjs.org/cacache/-/cacache-1.0.0.tgz'
|
||||
).then(datos => {
|
||||
return cacache.mete(rutaCache, 'registry.npmjs.org|cacache@1.0.0', datos)
|
||||
}).then(integridad => {
|
||||
console.log('el hacheo de integridad es', integridad)
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="put-stream"></a> `> cacache.mete.flujo(cache, clave, [ops]) -> Writable`
|
||||
|
||||
Devuelve un [Writable
|
||||
Stream](https://nodejs.org/api/stream.html#stream_writable_streams) que inserta
|
||||
al caché los datos escritos a él. Emite un evento `integrity` con el hacheo del
|
||||
contenido escrito, cuando completa.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
request.get(
|
||||
'https://registry.npmjs.org/cacache/-/cacache-1.0.0.tgz'
|
||||
).pipe(
|
||||
cacache.mete.flujo(
|
||||
rutaCache, 'registry.npmjs.org|cacache@1.0.0'
|
||||
).on('integrity', d => console.log(`integrity digest is ${d}`))
|
||||
)
|
||||
```
|
||||
|
||||
#### <a name="put-options"></a> `> opciones para cacache.mete`
|
||||
|
||||
La funciones `cacache.mete` tienen un número de opciones en común.
|
||||
|
||||
##### `ops.metadata`
|
||||
|
||||
Metadatos del usuario que se almacenarán con la entrada.
|
||||
|
||||
##### `ops.size`
|
||||
|
||||
El tamaño declarado de los datos que se van a insertar. Si es proveído, cacache
|
||||
verificará que los datos escritos sean de ese tamaño, o si no, fallará con un
|
||||
error con código `EBADSIZE`.
|
||||
|
||||
##### `ops.integrity`
|
||||
|
||||
El hacheo de integridad de los datos siendo escritos.
|
||||
|
||||
Si es proveído, y los datos escritos no le corresponden, la operación fallará
|
||||
con un error con código `EINTEGRITY`.
|
||||
|
||||
`ops.algorithms` no tiene ningún efecto si esta opción está presente.
|
||||
|
||||
##### `ops.algorithms`
|
||||
|
||||
Por Defecto: `['sha512']`
|
||||
|
||||
Algoritmos que se deben usar cuando se calcule el hacheo de [subresource
|
||||
integrity](#integrity) para los datos insertados. Puede usar cualquier algoritmo
|
||||
enumerado en `crypto.getHashes()`.
|
||||
|
||||
Por el momento, sólo se acepta un algoritmo (dígase, un array con exáctamente un
|
||||
valor). No tiene ningún efecto si `ops.integrity` también ha sido proveido.
|
||||
|
||||
##### `ops.uid`/`ops.gid`
|
||||
|
||||
Si están presentes, cacache hará todo lo posible para asegurarse que todos los
|
||||
ficheros creados en el proceso de sus operaciones en el caché usen esta
|
||||
combinación en particular.
|
||||
|
||||
##### `ops.memoize`
|
||||
|
||||
Por Defecto: `null`
|
||||
|
||||
Si es verdad, cacache tratará de memoizar los datos de la entrada en memoria. La
|
||||
próxima vez que el proceso corriente trate de accesar los datos o entrada,
|
||||
cacache buscará en memoria antes de buscar en disco.
|
||||
|
||||
Si `ops.memoize` es un objeto regular o un objeto como `Map` (es decir, un
|
||||
objeto con métodos `get()` y `set()`), este objeto en sí sera usado en vez del
|
||||
caché de memoria global. Esto permite tener lógica específica a tu aplicación
|
||||
encuanto al almacenaje en memoria de tus datos.
|
||||
|
||||
Si quieres asegurarte que los datos se lean del disco en vez de memoria, usa
|
||||
`memoize: false` cuando uses funciones de `cacache.saca`.
|
||||
|
||||
#### <a name="rm-all"></a> `> cacache.rm.todo(cache) -> Promise`
|
||||
|
||||
Borra el caché completo, incluyendo ficheros temporeros, ficheros de datos, y el
|
||||
índice del caché.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.rm.todo(rutaCache).then(() => {
|
||||
console.log('THE APOCALYPSE IS UPON US 😱')
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="rm-entry"></a> `> cacache.rm.entrada(cache, clave) -> Promise`
|
||||
|
||||
Alias: `cacache.rm`
|
||||
|
||||
Borra la entrada `clave` del índuce. El contenido asociado con esta entrada
|
||||
seguirá siendo accesible por hacheo usando
|
||||
[`saca.flujo.porHacheo`](#get-stream).
|
||||
|
||||
Para borrar el contenido en sí, usa [`rm.datos`](#rm-content). Si quieres hacer
|
||||
esto de manera más segura (pues ficheros de contenido pueden ser usados por
|
||||
multiples entradas), usa [`verifica`](#verify) para borrar huérfanos.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.rm.entrada(rutaCache, 'my-thing').then(() => {
|
||||
console.log('I did not like it anyway')
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="rm-content"></a> `> cacache.rm.datos(cache, integrity) -> Promise`
|
||||
|
||||
Borra el contenido identificado por `integrity`. Cualquier entrada que se
|
||||
refiera a este contenido quedarán huérfanas y se invalidarán si se tratan de
|
||||
accesar, al menos que contenido idéntico sea añadido bajo `integrity`.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.rm.datos(rutaCache, 'sha512-SoMeDIGest/IN+BaSE64==').then(() => {
|
||||
console.log('los datos para `mi-cosa` se borraron')
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="set-locale"></a> `> cacache.ponLenguaje(locale)`
|
||||
|
||||
Configura el lenguaje usado para mensajes y errores de cacache. La lista de
|
||||
lenguajes disponibles está en el directorio `./locales` del proyecto.
|
||||
|
||||
_Te interesa añadir más lenguajes? [Somete un PR](CONTRIBUTING.md)!_
|
||||
|
||||
#### <a name="clear-memoized"></a> `> cacache.limpiaMemoizado()`
|
||||
|
||||
Completamente reinicializa el caché de memoria interno. Si estás usando tu
|
||||
propio objecto con `ops.memoize`, debes hacer esto de manera específica a él.
|
||||
|
||||
#### <a name="tmp-mkdir"></a> `> tmp.hazdir(cache, ops) -> Promise<Path>`
|
||||
|
||||
Alias: `tmp.mkdir`
|
||||
|
||||
Devuelve un directorio único dentro del directorio `tmp` del caché.
|
||||
|
||||
Una vez tengas el directorio, es responsabilidad tuya asegurarte que todos los
|
||||
ficheros escrito a él sean creados usando los permisos y `uid`/`gid` concordante
|
||||
con el caché. Si no, puedes pedirle a cacache que lo haga llamando a
|
||||
[`cacache.tmp.fix()`](#tmp-fix). Esta función arreglará todos los permisos en el
|
||||
directorio tmp.
|
||||
|
||||
Si quieres que cacache limpie el directorio automáticamente cuando termines, usa
|
||||
[`cacache.tmp.conTmp()`](#with-tpm).
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.tmp.mkdir(cache).then(dir => {
|
||||
fs.writeFile(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="with-tmp"></a> `> tmp.conTmp(cache, ops, cb) -> Promise`
|
||||
|
||||
Crea un directorio temporero con [`tmp.mkdir()`](#tmp-mkdir) y ejecuta `cb` con
|
||||
él como primer argumento. El directorio creado será removido automáticamente
|
||||
cuando el valor devolvido por `cb()` se resuelva.
|
||||
|
||||
Las mismas advertencias aplican en cuanto a manejando permisos para los ficheros
|
||||
dentro del directorio.
|
||||
|
||||
##### Ejemplo
|
||||
|
||||
```javascript
|
||||
cacache.tmp.conTmp(cache, dir => {
|
||||
return fs.writeFileAsync(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
|
||||
}).then(() => {
|
||||
// `dir` no longer exists
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="integrity"></a> Hacheos de Subresource Integrity
|
||||
|
||||
cacache usa strings que siguen la especificación de [Subresource Integrity
|
||||
spec](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
|
||||
|
||||
Es decir, donde quiera cacache espera un argumento o opción `integrity`, ese
|
||||
string debería usar el formato `<algoritmo>-<hacheo-base64>`.
|
||||
|
||||
Una variación importante sobre los hacheos que cacache acepta es que acepta el
|
||||
nombre de cualquier algoritmo aceptado por el proceso de Node.js donde se usa.
|
||||
Puedes usar `crypto.getHashes()` para ver cuales están disponibles.
|
||||
|
||||
##### Generando tus propios hacheos
|
||||
|
||||
Si tienes un `shasum`, en general va a estar en formato de string hexadecimal
|
||||
(es decir, un `sha1` se vería como algo así:
|
||||
`5f5513f8822fdbe5145af33b64d8d970dcf95c6e`).
|
||||
|
||||
Para ser compatible con cacache, necesitas convertir esto a su equivalente en
|
||||
subresource integrity. Por ejemplo, el hacheo correspondiente al ejemplo
|
||||
anterior sería: `sha1-X1UT+IIv2+UUWvM7ZNjZcNz5XG4=`.
|
||||
|
||||
Puedes usar código así para generarlo por tu cuenta:
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto')
|
||||
const algoritmo = 'sha512'
|
||||
const datos = 'foobarbaz'
|
||||
|
||||
const integrity = (
|
||||
algorithm +
|
||||
'-' +
|
||||
crypto.createHash(algoritmo).update(datos).digest('base64')
|
||||
)
|
||||
```
|
||||
|
||||
También puedes usar [`ssri`](https://npm.im/ssri) para deferir el trabajo a otra
|
||||
librería que garantiza que todo esté correcto, pues maneja probablemente todas
|
||||
las operaciones que tendrías que hacer con SRIs, incluyendo convirtiendo entre
|
||||
hexadecimal y el formato SRI.
|
||||
|
||||
#### <a name="verify"></a> `> cacache.verifica(cache, ops) -> Promise`
|
||||
|
||||
Examina y arregla tu caché:
|
||||
|
||||
* Limpia entradas inválidas, huérfanas y corrompidas
|
||||
* Te deja filtrar cuales entradas retener, con tu propio filtro
|
||||
* Reclama cualquier ficheros de contenido sin referencias en el índice
|
||||
* Verifica integridad de todos los ficheros de contenido y remueve los malos
|
||||
* Arregla permisos del caché
|
||||
* Remieve el directorio `tmp` en el caché, y todo su contenido.
|
||||
|
||||
Cuando termine, devuelve un objeto con varias estadísticas sobre el proceso de
|
||||
verificación, por ejemplo la cantidad de espacio de disco reclamado, el número
|
||||
de entradas válidas, número de entradas removidas, etc.
|
||||
|
||||
##### Opciones
|
||||
|
||||
* `ops.uid` - uid para asignarle al caché y su contenido
|
||||
* `ops.gid` - gid para asignarle al caché y su contenido
|
||||
* `ops.filter` - recibe una entrada como argumento. Devuelve falso para removerla. Nota: es posible que esta función sea invocada con la misma entrada más de una vez.
|
||||
|
||||
##### Example
|
||||
|
||||
```sh
|
||||
echo somegarbage >> $RUTACACHE/content/deadbeef
|
||||
```
|
||||
|
||||
```javascript
|
||||
cacache.verifica(rutaCache).then(stats => {
|
||||
// deadbeef collected, because of invalid checksum.
|
||||
console.log('cache is much nicer now! stats:', stats)
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="verify-last-run"></a> `> cacache.verifica.ultimaVez(cache) -> Promise`
|
||||
|
||||
Alias: `últimaVez`
|
||||
|
||||
Devuelve un `Date` que representa la última vez que `cacache.verifica` fue
|
||||
ejecutada en `cache`.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.verifica(rutaCache).then(() => {
|
||||
cacache.verifica.ultimaVez(rutaCache).then(última => {
|
||||
console.log('La última vez que se usó cacache.verifica() fue ' + última)
|
||||
})
|
||||
})
|
||||
```
|
||||
+641
@@ -0,0 +1,641 @@
|
||||
# cacache [](https://npm.im/cacache) [](https://npm.im/cacache) [](https://travis-ci.org/npm/cacache) [](https://ci.appveyor.com/project/npm/cacache) [](https://coveralls.io/github/npm/cacache?branch=latest)
|
||||
|
||||
[`cacache`](https://github.com/npm/cacache) is a Node.js library for managing
|
||||
local key and content address caches. It's really fast, really good at
|
||||
concurrency, and it will never give you corrupted data, even if cache files
|
||||
get corrupted or manipulated.
|
||||
|
||||
On systems that support user and group settings on files, cacache will
|
||||
match the `uid` and `gid` values to the folder where the cache lives, even
|
||||
when running as `root`.
|
||||
|
||||
It was written to be used as [npm](https://npm.im)'s local cache, but can
|
||||
just as easily be used on its own.
|
||||
|
||||
_Translations: [español](README.es.md)_
|
||||
|
||||
## Install
|
||||
|
||||
`$ npm install --save cacache`
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Example](#example)
|
||||
* [Features](#features)
|
||||
* [Contributing](#contributing)
|
||||
* [API](#api)
|
||||
* [Using localized APIs](#localized-api)
|
||||
* Reading
|
||||
* [`ls`](#ls)
|
||||
* [`ls.stream`](#ls-stream)
|
||||
* [`get`](#get-data)
|
||||
* [`get.stream`](#get-stream)
|
||||
* [`get.info`](#get-info)
|
||||
* [`get.hasContent`](#get-hasContent)
|
||||
* Writing
|
||||
* [`put`](#put-data)
|
||||
* [`put.stream`](#put-stream)
|
||||
* [`put*` opts](#put-options)
|
||||
* [`rm.all`](#rm-all)
|
||||
* [`rm.entry`](#rm-entry)
|
||||
* [`rm.content`](#rm-content)
|
||||
* Utilities
|
||||
* [`setLocale`](#set-locale)
|
||||
* [`clearMemoized`](#clear-memoized)
|
||||
* [`tmp.mkdir`](#tmp-mkdir)
|
||||
* [`tmp.withTmp`](#with-tmp)
|
||||
* Integrity
|
||||
* [Subresource Integrity](#integrity)
|
||||
* [`verify`](#verify)
|
||||
* [`verify.lastRun`](#verify-last-run)
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const cacache = require('cacache/en')
|
||||
const fs = require('fs')
|
||||
|
||||
const tarball = '/path/to/mytar.tgz'
|
||||
const cachePath = '/tmp/my-toy-cache'
|
||||
const key = 'my-unique-key-1234'
|
||||
|
||||
// Cache it! Use `cachePath` as the root of the content cache
|
||||
cacache.put(cachePath, key, '10293801983029384').then(integrity => {
|
||||
console.log(`Saved content to ${cachePath}.`)
|
||||
})
|
||||
|
||||
const destination = '/tmp/mytar.tgz'
|
||||
|
||||
// Copy the contents out of the cache and into their destination!
|
||||
// But this time, use stream instead!
|
||||
cacache.get.stream(
|
||||
cachePath, key
|
||||
).pipe(
|
||||
fs.createWriteStream(destination)
|
||||
).on('finish', () => {
|
||||
console.log('done extracting!')
|
||||
})
|
||||
|
||||
// The same thing, but skip the key index.
|
||||
cacache.get.byDigest(cachePath, integrityHash).then(data => {
|
||||
fs.writeFile(destination, data, err => {
|
||||
console.log('tarball data fetched based on its sha512sum and written out!')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
* Extraction by key or by content address (shasum, etc)
|
||||
* [Subresource Integrity](#integrity) web standard support
|
||||
* Multi-hash support - safely host sha1, sha512, etc, in a single cache
|
||||
* Automatic content deduplication
|
||||
* Fault tolerance (immune to corruption, partial writes, process races, etc)
|
||||
* Consistency guarantees on read and write (full data verification)
|
||||
* Lockless, high-concurrency cache access
|
||||
* Streaming support
|
||||
* Promise support
|
||||
* Pretty darn fast -- sub-millisecond reads and writes including verification
|
||||
* Arbitrary metadata storage
|
||||
* Garbage collection and additional offline verification
|
||||
* Thorough test coverage
|
||||
* There's probably a bloom filter in there somewhere. Those are cool, right? 🤔
|
||||
|
||||
### Contributing
|
||||
|
||||
The cacache team enthusiastically welcomes contributions and project participation! There's a bunch of things you can do if you want to contribute! The [Contributor Guide](CONTRIBUTING.md) has all the information you need for everything from reporting bugs to contributing entire new features. Please don't hesitate to jump in if you'd like to, or even ask us questions if something isn't clear.
|
||||
|
||||
All participants and maintainers in this project are expected to follow [Code of Conduct](CODE_OF_CONDUCT.md), and just generally be excellent to each other.
|
||||
|
||||
Please refer to the [Changelog](CHANGELOG.md) for project history details, too.
|
||||
|
||||
Happy hacking!
|
||||
|
||||
### API
|
||||
|
||||
#### <a name="localized-api"></a> Using localized APIs
|
||||
|
||||
cacache includes a complete API in English, with the same features as other
|
||||
translations. To use the English API as documented in this README, use
|
||||
`require('cacache/en')`. This is also currently the default if you do
|
||||
`require('cacache')`, but may change in the future.
|
||||
|
||||
cacache also supports other languages! You can find the list of currently
|
||||
supported ones by looking in `./locales` in the source directory. You can use
|
||||
the API in that language with `require('cacache/<lang>')`.
|
||||
|
||||
Want to add support for a new language? Please go ahead! You should be able to
|
||||
copy `./locales/en.js` and `./locales/en.json` and fill them in. Translating the
|
||||
`README.md` is a bit more work, but also appreciated if you get around to it. 👍🏼
|
||||
|
||||
#### <a name="ls"></a> `> cacache.ls(cache) -> Promise<Object>`
|
||||
|
||||
Lists info for all entries currently in the cache as a single large object. Each
|
||||
entry in the object will be keyed by the unique index key, with corresponding
|
||||
[`get.info`](#get-info) objects as the values.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.ls(cachePath).then(console.log)
|
||||
// Output
|
||||
{
|
||||
'my-thing': {
|
||||
key: 'my-thing',
|
||||
integrity: 'sha512-BaSe64/EnCoDED+HAsh=='
|
||||
path: '.testcache/content/deadbeef', // joined with `cachePath`
|
||||
time: 12345698490,
|
||||
size: 4023948,
|
||||
metadata: {
|
||||
name: 'blah',
|
||||
version: '1.2.3',
|
||||
description: 'this was once a package but now it is my-thing'
|
||||
}
|
||||
},
|
||||
'other-thing': {
|
||||
key: 'other-thing',
|
||||
integrity: 'sha1-ANothER+hasH=',
|
||||
path: '.testcache/content/bada55',
|
||||
time: 11992309289,
|
||||
size: 111112
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### <a name="ls-stream"></a> `> cacache.ls.stream(cache) -> Readable`
|
||||
|
||||
Lists info for all entries currently in the cache as a single large object.
|
||||
|
||||
This works just like [`ls`](#ls), except [`get.info`](#get-info) entries are
|
||||
returned as `'data'` events on the returned stream.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.ls.stream(cachePath).on('data', console.log)
|
||||
// Output
|
||||
{
|
||||
key: 'my-thing',
|
||||
integrity: 'sha512-BaSe64HaSh',
|
||||
path: '.testcache/content/deadbeef', // joined with `cachePath`
|
||||
time: 12345698490,
|
||||
size: 13423,
|
||||
metadata: {
|
||||
name: 'blah',
|
||||
version: '1.2.3',
|
||||
description: 'this was once a package but now it is my-thing'
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
key: 'other-thing',
|
||||
integrity: 'whirlpool-WoWSoMuchSupport',
|
||||
path: '.testcache/content/bada55',
|
||||
time: 11992309289,
|
||||
size: 498023984029
|
||||
}
|
||||
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### <a name="get-data"></a> `> cacache.get(cache, key, [opts]) -> Promise({data, metadata, integrity})`
|
||||
|
||||
Returns an object with the cached data, digest, and metadata identified by
|
||||
`key`. The `data` property of this object will be a `Buffer` instance that
|
||||
presumably holds some data that means something to you. I'm sure you know what
|
||||
to do with it! cacache just won't care.
|
||||
|
||||
`integrity` is a [Subresource
|
||||
Integrity](#integrity)
|
||||
string. That is, a string that can be used to verify `data`, which looks like
|
||||
`<hash-algorithm>-<base64-integrity-hash>`.
|
||||
|
||||
If there is no content identified by `key`, or if the locally-stored data does
|
||||
not pass the validity checksum, the promise will be rejected.
|
||||
|
||||
A sub-function, `get.byDigest` may be used for identical behavior, except lookup
|
||||
will happen by integrity hash, bypassing the index entirely. This version of the
|
||||
function *only* returns `data` itself, without any wrapper.
|
||||
|
||||
##### Note
|
||||
|
||||
This function loads the entire cache entry into memory before returning it. If
|
||||
you're dealing with Very Large data, consider using [`get.stream`](#get-stream)
|
||||
instead.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
// Look up by key
|
||||
cache.get(cachePath, 'my-thing').then(console.log)
|
||||
// Output:
|
||||
{
|
||||
metadata: {
|
||||
thingName: 'my'
|
||||
},
|
||||
integrity: 'sha512-BaSe64HaSh',
|
||||
data: Buffer#<deadbeef>,
|
||||
size: 9320
|
||||
}
|
||||
|
||||
// Look up by digest
|
||||
cache.get.byDigest(cachePath, 'sha512-BaSe64HaSh').then(console.log)
|
||||
// Output:
|
||||
Buffer#<deadbeef>
|
||||
```
|
||||
|
||||
#### <a name="get-stream"></a> `> cacache.get.stream(cache, key, [opts]) -> Readable`
|
||||
|
||||
Returns a [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams) of the cached data identified by `key`.
|
||||
|
||||
If there is no content identified by `key`, or if the locally-stored data does
|
||||
not pass the validity checksum, an error will be emitted.
|
||||
|
||||
`metadata` and `integrity` events will be emitted before the stream closes, if
|
||||
you need to collect that extra data about the cached entry.
|
||||
|
||||
A sub-function, `get.stream.byDigest` may be used for identical behavior,
|
||||
except lookup will happen by integrity hash, bypassing the index entirely. This
|
||||
version does not emit the `metadata` and `integrity` events at all.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
// Look up by key
|
||||
cache.get.stream(
|
||||
cachePath, 'my-thing'
|
||||
).on('metadata', metadata => {
|
||||
console.log('metadata:', metadata)
|
||||
}).on('integrity', integrity => {
|
||||
console.log('integrity:', integrity)
|
||||
}).pipe(
|
||||
fs.createWriteStream('./x.tgz')
|
||||
)
|
||||
// Outputs:
|
||||
metadata: { ... }
|
||||
integrity: 'sha512-SoMeDIGest+64=='
|
||||
|
||||
// Look up by digest
|
||||
cache.get.stream.byDigest(
|
||||
cachePath, 'sha512-SoMeDIGest+64=='
|
||||
).pipe(
|
||||
fs.createWriteStream('./x.tgz')
|
||||
)
|
||||
```
|
||||
|
||||
#### <a name="get-info"></a> `> cacache.get.info(cache, key) -> Promise`
|
||||
|
||||
Looks up `key` in the cache index, returning information about the entry if
|
||||
one exists.
|
||||
|
||||
##### Fields
|
||||
|
||||
* `key` - Key the entry was looked up under. Matches the `key` argument.
|
||||
* `integrity` - [Subresource Integrity hash](#integrity) for the content this entry refers to.
|
||||
* `path` - Filesystem path where content is stored, joined with `cache` argument.
|
||||
* `time` - Timestamp the entry was first added on.
|
||||
* `metadata` - User-assigned metadata associated with the entry/content.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.get.info(cachePath, 'my-thing').then(console.log)
|
||||
|
||||
// Output
|
||||
{
|
||||
key: 'my-thing',
|
||||
integrity: 'sha256-MUSTVERIFY+ALL/THINGS=='
|
||||
path: '.testcache/content/deadbeef',
|
||||
time: 12345698490,
|
||||
size: 849234,
|
||||
metadata: {
|
||||
name: 'blah',
|
||||
version: '1.2.3',
|
||||
description: 'this was once a package but now it is my-thing'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### <a name="get-hasContent"></a> `> cacache.get.hasContent(cache, integrity) -> Promise`
|
||||
|
||||
Looks up a [Subresource Integrity hash](#integrity) in the cache. If content
|
||||
exists for this `integrity`, it will return an object, with the specific single integrity hash
|
||||
that was found in `sri` key, and the size of the found content as `size`. If no content exists for this integrity, it will return `false`.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.get.hasContent(cachePath, 'sha256-MUSTVERIFY+ALL/THINGS==').then(console.log)
|
||||
|
||||
// Output
|
||||
{
|
||||
sri: {
|
||||
source: 'sha256-MUSTVERIFY+ALL/THINGS==',
|
||||
algorithm: 'sha256',
|
||||
digest: 'MUSTVERIFY+ALL/THINGS==',
|
||||
options: []
|
||||
},
|
||||
size: 9001
|
||||
}
|
||||
|
||||
cacache.get.hasContent(cachePath, 'sha521-NOT+IN/CACHE==').then(console.log)
|
||||
|
||||
// Output
|
||||
false
|
||||
```
|
||||
|
||||
#### <a name="put-data"></a> `> cacache.put(cache, key, data, [opts]) -> Promise`
|
||||
|
||||
Inserts data passed to it into the cache. The returned Promise resolves with a
|
||||
digest (generated according to [`opts.algorithms`](#optsalgorithms)) after the
|
||||
cache entry has been successfully written.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
fetch(
|
||||
'https://registry.npmjs.org/cacache/-/cacache-1.0.0.tgz'
|
||||
).then(data => {
|
||||
return cacache.put(cachePath, 'registry.npmjs.org|cacache@1.0.0', data)
|
||||
}).then(integrity => {
|
||||
console.log('integrity hash is', integrity)
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="put-stream"></a> `> cacache.put.stream(cache, key, [opts]) -> Writable`
|
||||
|
||||
Returns a [Writable
|
||||
Stream](https://nodejs.org/api/stream.html#stream_writable_streams) that inserts
|
||||
data written to it into the cache. Emits an `integrity` event with the digest of
|
||||
written contents when it succeeds.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
request.get(
|
||||
'https://registry.npmjs.org/cacache/-/cacache-1.0.0.tgz'
|
||||
).pipe(
|
||||
cacache.put.stream(
|
||||
cachePath, 'registry.npmjs.org|cacache@1.0.0'
|
||||
).on('integrity', d => console.log(`integrity digest is ${d}`))
|
||||
)
|
||||
```
|
||||
|
||||
#### <a name="put-options"></a> `> cacache.put options`
|
||||
|
||||
`cacache.put` functions have a number of options in common.
|
||||
|
||||
##### `opts.metadata`
|
||||
|
||||
Arbitrary metadata to be attached to the inserted key.
|
||||
|
||||
##### `opts.size`
|
||||
|
||||
If provided, the data stream will be verified to check that enough data was
|
||||
passed through. If there's more or less data than expected, insertion will fail
|
||||
with an `EBADSIZE` error.
|
||||
|
||||
##### `opts.integrity`
|
||||
|
||||
If present, the pre-calculated digest for the inserted content. If this option
|
||||
if provided and does not match the post-insertion digest, insertion will fail
|
||||
with an `EINTEGRITY` error.
|
||||
|
||||
`algorithms` has no effect if this option is present.
|
||||
|
||||
##### `opts.algorithms`
|
||||
|
||||
Default: ['sha512']
|
||||
|
||||
Hashing algorithms to use when calculating the [subresource integrity
|
||||
digest](#integrity)
|
||||
for inserted data. Can use any algorithm listed in `crypto.getHashes()` or
|
||||
`'omakase'`/`'お任せします'` to pick a random hash algorithm on each insertion. You
|
||||
may also use any anagram of `'modnar'` to use this feature.
|
||||
|
||||
Currently only supports one algorithm at a time (i.e., an array length of
|
||||
exactly `1`). Has no effect if `opts.integrity` is present.
|
||||
|
||||
##### `opts.memoize`
|
||||
|
||||
Default: null
|
||||
|
||||
If provided, cacache will memoize the given cache insertion in memory, bypassing
|
||||
any filesystem checks for that key or digest in future cache fetches. Nothing
|
||||
will be written to the in-memory cache unless this option is explicitly truthy.
|
||||
|
||||
If `opts.memoize` is an object or a `Map`-like (that is, an object with `get`
|
||||
and `set` methods), it will be written to instead of the global memoization
|
||||
cache.
|
||||
|
||||
Reading from disk data can be forced by explicitly passing `memoize: false` to
|
||||
the reader functions, but their default will be to read from memory.
|
||||
|
||||
#### <a name="rm-all"></a> `> cacache.rm.all(cache) -> Promise`
|
||||
|
||||
Clears the entire cache. Mainly by blowing away the cache directory itself.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.rm.all(cachePath).then(() => {
|
||||
console.log('THE APOCALYPSE IS UPON US 😱')
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="rm-entry"></a> `> cacache.rm.entry(cache, key) -> Promise`
|
||||
|
||||
Alias: `cacache.rm`
|
||||
|
||||
Removes the index entry for `key`. Content will still be accessible if
|
||||
requested directly by content address ([`get.stream.byDigest`](#get-stream)).
|
||||
|
||||
To remove the content itself (which might still be used by other entries), use
|
||||
[`rm.content`](#rm-content). Or, to safely vacuum any unused content, use
|
||||
[`verify`](#verify).
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.rm.entry(cachePath, 'my-thing').then(() => {
|
||||
console.log('I did not like it anyway')
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="rm-content"></a> `> cacache.rm.content(cache, integrity) -> Promise`
|
||||
|
||||
Removes the content identified by `integrity`. Any index entries referring to it
|
||||
will not be usable again until the content is re-added to the cache with an
|
||||
identical digest.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.rm.content(cachePath, 'sha512-SoMeDIGest/IN+BaSE64==').then(() => {
|
||||
console.log('data for my-thing is gone!')
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="set-locale"></a> `> cacache.setLocale(locale)`
|
||||
|
||||
Configure the language/locale used for messages and errors coming from cacache.
|
||||
The list of available locales is in the `./locales` directory in the project
|
||||
root.
|
||||
|
||||
_Interested in contributing more languages! [Submit a PR](CONTRIBUTING.md)!_
|
||||
|
||||
#### <a name="clear-memoized"></a> `> cacache.clearMemoized()`
|
||||
|
||||
Completely resets the in-memory entry cache.
|
||||
|
||||
#### <a name="tmp-mkdir"></a> `> tmp.mkdir(cache, opts) -> Promise<Path>`
|
||||
|
||||
Returns a unique temporary directory inside the cache's `tmp` dir. This
|
||||
directory will use the same safe user assignment that all the other stuff use.
|
||||
|
||||
Once the directory is made, it's the user's responsibility that all files
|
||||
within are given the appropriate `gid`/`uid` ownership settings to match
|
||||
the rest of the cache. If not, you can ask cacache to do it for you by
|
||||
calling [`tmp.fix()`](#tmp-fix), which will fix all tmp directory
|
||||
permissions.
|
||||
|
||||
If you want automatic cleanup of this directory, use
|
||||
[`tmp.withTmp()`](#with-tpm)
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.tmp.mkdir(cache).then(dir => {
|
||||
fs.writeFile(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="tmp-fix"></a> `> tmp.fix(cache) -> Promise`
|
||||
|
||||
Sets the `uid` and `gid` properties on all files and folders within the tmp
|
||||
folder to match the rest of the cache.
|
||||
|
||||
Use this after manually writing files into [`tmp.mkdir`](#tmp-mkdir) or
|
||||
[`tmp.withTmp`](#with-tmp).
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.tmp.mkdir(cache).then(dir => {
|
||||
writeFile(path.join(dir, 'file'), someData).then(() => {
|
||||
// make sure we didn't just put a root-owned file in the cache
|
||||
cacache.tmp.fix().then(() => {
|
||||
// all uids and gids match now
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="with-tmp"></a> `> tmp.withTmp(cache, opts, cb) -> Promise`
|
||||
|
||||
Creates a temporary directory with [`tmp.mkdir()`](#tmp-mkdir) and calls `cb`
|
||||
with it. The created temporary directory will be removed when the return value
|
||||
of `cb()` resolves -- that is, if you return a Promise from `cb()`, the tmp
|
||||
directory will be automatically deleted once that promise completes.
|
||||
|
||||
The same caveats apply when it comes to managing permissions for the tmp dir's
|
||||
contents.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.tmp.withTmp(cache, dir => {
|
||||
return fs.writeFileAsync(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
|
||||
}).then(() => {
|
||||
// `dir` no longer exists
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="integrity"></a> Subresource Integrity Digests
|
||||
|
||||
For content verification and addressing, cacache uses strings following the
|
||||
[Subresource
|
||||
Integrity spec](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
|
||||
That is, any time cacache expects an `integrity` argument or option, it
|
||||
should be in the format `<hashAlgorithm>-<base64-hash>`.
|
||||
|
||||
One deviation from the current spec is that cacache will support any hash
|
||||
algorithms supported by the underlying Node.js process. You can use
|
||||
`crypto.getHashes()` to see which ones you can use.
|
||||
|
||||
##### Generating Digests Yourself
|
||||
|
||||
If you have an existing content shasum, they are generally formatted as a
|
||||
hexadecimal string (that is, a sha1 would look like:
|
||||
`5f5513f8822fdbe5145af33b64d8d970dcf95c6e`). In order to be compatible with
|
||||
cacache, you'll need to convert this to an equivalent subresource integrity
|
||||
string. For this example, the corresponding hash would be:
|
||||
`sha1-X1UT+IIv2+UUWvM7ZNjZcNz5XG4=`.
|
||||
|
||||
If you want to generate an integrity string yourself for existing data, you can
|
||||
use something like this:
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto')
|
||||
const hashAlgorithm = 'sha512'
|
||||
const data = 'foobarbaz'
|
||||
|
||||
const integrity = (
|
||||
hashAlgorithm +
|
||||
'-' +
|
||||
crypto.createHash(hashAlgorithm).update(data).digest('base64')
|
||||
)
|
||||
```
|
||||
|
||||
You can also use [`ssri`](https://npm.im/ssri) to have a richer set of functionality
|
||||
around SRI strings, including generation, parsing, and translating from existing
|
||||
hex-formatted strings.
|
||||
|
||||
#### <a name="verify"></a> `> cacache.verify(cache, opts) -> Promise`
|
||||
|
||||
Checks out and fixes up your cache:
|
||||
|
||||
* Cleans up corrupted or invalid index entries.
|
||||
* Custom entry filtering options.
|
||||
* Garbage collects any content entries not referenced by the index.
|
||||
* Checks integrity for all content entries and removes invalid content.
|
||||
* Fixes cache ownership.
|
||||
* Removes the `tmp` directory in the cache and all its contents.
|
||||
|
||||
When it's done, it'll return an object with various stats about the verification
|
||||
process, including amount of storage reclaimed, number of valid entries, number
|
||||
of entries removed, etc.
|
||||
|
||||
##### Options
|
||||
|
||||
* `opts.filter` - receives a formatted entry. Return false to remove it.
|
||||
Note: might be called more than once on the same entry.
|
||||
|
||||
##### Example
|
||||
|
||||
```sh
|
||||
echo somegarbage >> $CACHEPATH/content/deadbeef
|
||||
```
|
||||
|
||||
```javascript
|
||||
cacache.verify(cachePath).then(stats => {
|
||||
// deadbeef collected, because of invalid checksum.
|
||||
console.log('cache is much nicer now! stats:', stats)
|
||||
})
|
||||
```
|
||||
|
||||
#### <a name="verify-last-run"></a> `> cacache.verify.lastRun(cache) -> Promise`
|
||||
|
||||
Returns a `Date` representing the last time `cacache.verify` was run on `cache`.
|
||||
|
||||
##### Example
|
||||
|
||||
```javascript
|
||||
cacache.verify(cachePath).then(() => {
|
||||
cacache.verify.lastRun(cachePath).then(lastTime => {
|
||||
console.log('cacache.verify was last called on' + lastTime)
|
||||
})
|
||||
})
|
||||
```
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = require('./locales/en.js')
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = require('./locales/es.js')
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const figgyPudding = require('figgy-pudding')
|
||||
const fs = require('fs')
|
||||
const index = require('./lib/entry-index')
|
||||
const memo = require('./lib/memoization')
|
||||
const pipe = require('mississippi').pipe
|
||||
const pipeline = require('mississippi').pipeline
|
||||
const read = require('./lib/content/read')
|
||||
const through = require('mississippi').through
|
||||
|
||||
const GetOpts = figgyPudding({
|
||||
integrity: {},
|
||||
memoize: {},
|
||||
size: {}
|
||||
})
|
||||
|
||||
module.exports = function get (cache, key, opts) {
|
||||
return getData(false, cache, key, opts)
|
||||
}
|
||||
module.exports.byDigest = function getByDigest (cache, digest, opts) {
|
||||
return getData(true, cache, digest, opts)
|
||||
}
|
||||
function getData (byDigest, cache, key, opts) {
|
||||
opts = GetOpts(opts)
|
||||
const memoized = (
|
||||
byDigest
|
||||
? memo.get.byDigest(cache, key, opts)
|
||||
: memo.get(cache, key, opts)
|
||||
)
|
||||
if (memoized && opts.memoize !== false) {
|
||||
return BB.resolve(byDigest ? memoized : {
|
||||
metadata: memoized.entry.metadata,
|
||||
data: memoized.data,
|
||||
integrity: memoized.entry.integrity,
|
||||
size: memoized.entry.size
|
||||
})
|
||||
}
|
||||
return (
|
||||
byDigest ? BB.resolve(null) : index.find(cache, key, opts)
|
||||
).then(entry => {
|
||||
if (!entry && !byDigest) {
|
||||
throw new index.NotFoundError(cache, key)
|
||||
}
|
||||
return read(cache, byDigest ? key : entry.integrity, {
|
||||
integrity: opts.integrity,
|
||||
size: opts.size
|
||||
}).then(data => byDigest ? data : {
|
||||
metadata: entry.metadata,
|
||||
data: data,
|
||||
size: entry.size,
|
||||
integrity: entry.integrity
|
||||
}).then(res => {
|
||||
if (opts.memoize && byDigest) {
|
||||
memo.put.byDigest(cache, key, res, opts)
|
||||
} else if (opts.memoize) {
|
||||
memo.put(cache, entry, res.data, opts)
|
||||
}
|
||||
return res
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.sync = function get (cache, key, opts) {
|
||||
return getDataSync(false, cache, key, opts)
|
||||
}
|
||||
module.exports.sync.byDigest = function getByDigest (cache, digest, opts) {
|
||||
return getDataSync(true, cache, digest, opts)
|
||||
}
|
||||
function getDataSync (byDigest, cache, key, opts) {
|
||||
opts = GetOpts(opts)
|
||||
const memoized = (
|
||||
byDigest
|
||||
? memo.get.byDigest(cache, key, opts)
|
||||
: memo.get(cache, key, opts)
|
||||
)
|
||||
if (memoized && opts.memoize !== false) {
|
||||
return byDigest ? memoized : {
|
||||
metadata: memoized.entry.metadata,
|
||||
data: memoized.data,
|
||||
integrity: memoized.entry.integrity,
|
||||
size: memoized.entry.size
|
||||
}
|
||||
}
|
||||
const entry = !byDigest && index.find.sync(cache, key, opts)
|
||||
if (!entry && !byDigest) {
|
||||
throw new index.NotFoundError(cache, key)
|
||||
}
|
||||
const data = read.sync(
|
||||
cache,
|
||||
byDigest ? key : entry.integrity,
|
||||
{
|
||||
integrity: opts.integrity,
|
||||
size: opts.size
|
||||
}
|
||||
)
|
||||
const res = byDigest
|
||||
? data
|
||||
: {
|
||||
metadata: entry.metadata,
|
||||
data: data,
|
||||
size: entry.size,
|
||||
integrity: entry.integrity
|
||||
}
|
||||
if (opts.memoize && byDigest) {
|
||||
memo.put.byDigest(cache, key, res, opts)
|
||||
} else if (opts.memoize) {
|
||||
memo.put(cache, entry, res.data, opts)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
module.exports.stream = getStream
|
||||
function getStream (cache, key, opts) {
|
||||
opts = GetOpts(opts)
|
||||
let stream = through()
|
||||
const memoized = memo.get(cache, key, opts)
|
||||
if (memoized && opts.memoize !== false) {
|
||||
stream.on('newListener', function (ev, cb) {
|
||||
ev === 'metadata' && cb(memoized.entry.metadata)
|
||||
ev === 'integrity' && cb(memoized.entry.integrity)
|
||||
ev === 'size' && cb(memoized.entry.size)
|
||||
})
|
||||
stream.write(memoized.data, () => stream.end())
|
||||
return stream
|
||||
}
|
||||
index.find(cache, key).then(entry => {
|
||||
if (!entry) {
|
||||
return stream.emit(
|
||||
'error', new index.NotFoundError(cache, key)
|
||||
)
|
||||
}
|
||||
let memoStream
|
||||
if (opts.memoize) {
|
||||
let memoData = []
|
||||
let memoLength = 0
|
||||
memoStream = through((c, en, cb) => {
|
||||
memoData && memoData.push(c)
|
||||
memoLength += c.length
|
||||
cb(null, c, en)
|
||||
}, cb => {
|
||||
memoData && memo.put(cache, entry, Buffer.concat(memoData, memoLength), opts)
|
||||
cb()
|
||||
})
|
||||
} else {
|
||||
memoStream = through()
|
||||
}
|
||||
stream.emit('metadata', entry.metadata)
|
||||
stream.emit('integrity', entry.integrity)
|
||||
stream.emit('size', entry.size)
|
||||
stream.on('newListener', function (ev, cb) {
|
||||
ev === 'metadata' && cb(entry.metadata)
|
||||
ev === 'integrity' && cb(entry.integrity)
|
||||
ev === 'size' && cb(entry.size)
|
||||
})
|
||||
pipe(
|
||||
read.readStream(cache, entry.integrity, opts.concat({
|
||||
size: opts.size == null ? entry.size : opts.size
|
||||
})),
|
||||
memoStream,
|
||||
stream
|
||||
)
|
||||
}).catch(err => stream.emit('error', err))
|
||||
return stream
|
||||
}
|
||||
|
||||
module.exports.stream.byDigest = getStreamDigest
|
||||
function getStreamDigest (cache, integrity, opts) {
|
||||
opts = GetOpts(opts)
|
||||
const memoized = memo.get.byDigest(cache, integrity, opts)
|
||||
if (memoized && opts.memoize !== false) {
|
||||
const stream = through()
|
||||
stream.write(memoized, () => stream.end())
|
||||
return stream
|
||||
} else {
|
||||
let stream = read.readStream(cache, integrity, opts)
|
||||
if (opts.memoize) {
|
||||
let memoData = []
|
||||
let memoLength = 0
|
||||
const memoStream = through((c, en, cb) => {
|
||||
memoData && memoData.push(c)
|
||||
memoLength += c.length
|
||||
cb(null, c, en)
|
||||
}, cb => {
|
||||
memoData && memo.put.byDigest(
|
||||
cache,
|
||||
integrity,
|
||||
Buffer.concat(memoData, memoLength),
|
||||
opts
|
||||
)
|
||||
cb()
|
||||
})
|
||||
stream = pipeline(stream, memoStream)
|
||||
}
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.info = info
|
||||
function info (cache, key, opts) {
|
||||
opts = GetOpts(opts)
|
||||
const memoized = memo.get(cache, key, opts)
|
||||
if (memoized && opts.memoize !== false) {
|
||||
return BB.resolve(memoized.entry)
|
||||
} else {
|
||||
return index.find(cache, key)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.hasContent = read.hasContent
|
||||
|
||||
module.exports.copy = function cp (cache, key, dest, opts) {
|
||||
return copy(false, cache, key, dest, opts)
|
||||
}
|
||||
module.exports.copy.byDigest = function cpDigest (cache, digest, dest, opts) {
|
||||
return copy(true, cache, digest, dest, opts)
|
||||
}
|
||||
function copy (byDigest, cache, key, dest, opts) {
|
||||
opts = GetOpts(opts)
|
||||
if (read.copy) {
|
||||
return (
|
||||
byDigest ? BB.resolve(null) : index.find(cache, key, opts)
|
||||
).then(entry => {
|
||||
if (!entry && !byDigest) {
|
||||
throw new index.NotFoundError(cache, key)
|
||||
}
|
||||
return read.copy(
|
||||
cache, byDigest ? key : entry.integrity, dest, opts
|
||||
).then(() => byDigest ? key : {
|
||||
metadata: entry.metadata,
|
||||
size: entry.size,
|
||||
integrity: entry.integrity
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return getData(byDigest, cache, key, opts).then(res => {
|
||||
return fs.writeFileAsync(dest, byDigest ? res : res.data)
|
||||
.then(() => byDigest ? key : {
|
||||
metadata: res.metadata,
|
||||
size: res.size,
|
||||
integrity: res.integrity
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = require('./locales/en.js')
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
'use strict'
|
||||
|
||||
const contentVer = require('../../package.json')['cache-version'].content
|
||||
const hashToSegments = require('../util/hash-to-segments')
|
||||
const path = require('path')
|
||||
const ssri = require('ssri')
|
||||
|
||||
// Current format of content file path:
|
||||
//
|
||||
// sha512-BaSE64Hex= ->
|
||||
// ~/.my-cache/content-v2/sha512/ba/da/55deadbeefc0ffee
|
||||
//
|
||||
module.exports = contentPath
|
||||
function contentPath (cache, integrity) {
|
||||
const sri = ssri.parse(integrity, { single: true })
|
||||
// contentPath is the *strongest* algo given
|
||||
return path.join.apply(path, [
|
||||
contentDir(cache),
|
||||
sri.algorithm
|
||||
].concat(hashToSegments(sri.hexDigest())))
|
||||
}
|
||||
|
||||
module.exports._contentDir = contentDir
|
||||
function contentDir (cache) {
|
||||
return path.join(cache, `content-v${contentVer}`)
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const contentPath = require('./path')
|
||||
const figgyPudding = require('figgy-pudding')
|
||||
const fs = require('graceful-fs')
|
||||
const PassThrough = require('stream').PassThrough
|
||||
const pipe = BB.promisify(require('mississippi').pipe)
|
||||
const ssri = require('ssri')
|
||||
const Y = require('../util/y.js')
|
||||
|
||||
const lstatAsync = BB.promisify(fs.lstat)
|
||||
const readFileAsync = BB.promisify(fs.readFile)
|
||||
|
||||
const ReadOpts = figgyPudding({
|
||||
size: {}
|
||||
})
|
||||
|
||||
module.exports = read
|
||||
function read (cache, integrity, opts) {
|
||||
opts = ReadOpts(opts)
|
||||
return withContentSri(cache, integrity, (cpath, sri) => {
|
||||
return readFileAsync(cpath, null).then(data => {
|
||||
if (typeof opts.size === 'number' && opts.size !== data.length) {
|
||||
throw sizeError(opts.size, data.length)
|
||||
} else if (ssri.checkData(data, sri)) {
|
||||
return data
|
||||
} else {
|
||||
throw integrityError(sri, cpath)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.sync = readSync
|
||||
function readSync (cache, integrity, opts) {
|
||||
opts = ReadOpts(opts)
|
||||
return withContentSriSync(cache, integrity, (cpath, sri) => {
|
||||
const data = fs.readFileSync(cpath)
|
||||
if (typeof opts.size === 'number' && opts.size !== data.length) {
|
||||
throw sizeError(opts.size, data.length)
|
||||
} else if (ssri.checkData(data, sri)) {
|
||||
return data
|
||||
} else {
|
||||
throw integrityError(sri, cpath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.stream = readStream
|
||||
module.exports.readStream = readStream
|
||||
function readStream (cache, integrity, opts) {
|
||||
opts = ReadOpts(opts)
|
||||
const stream = new PassThrough()
|
||||
withContentSri(cache, integrity, (cpath, sri) => {
|
||||
return lstatAsync(cpath).then(stat => ({ cpath, sri, stat }))
|
||||
}).then(({ cpath, sri, stat }) => {
|
||||
return pipe(
|
||||
fs.createReadStream(cpath),
|
||||
ssri.integrityStream({
|
||||
integrity: sri,
|
||||
size: opts.size
|
||||
}),
|
||||
stream
|
||||
)
|
||||
}).catch(err => {
|
||||
stream.emit('error', err)
|
||||
})
|
||||
return stream
|
||||
}
|
||||
|
||||
let copyFileAsync
|
||||
if (fs.copyFile) {
|
||||
module.exports.copy = copy
|
||||
module.exports.copy.sync = copySync
|
||||
copyFileAsync = BB.promisify(fs.copyFile)
|
||||
}
|
||||
|
||||
function copy (cache, integrity, dest, opts) {
|
||||
opts = ReadOpts(opts)
|
||||
return withContentSri(cache, integrity, (cpath, sri) => {
|
||||
return copyFileAsync(cpath, dest)
|
||||
})
|
||||
}
|
||||
|
||||
function copySync (cache, integrity, dest, opts) {
|
||||
opts = ReadOpts(opts)
|
||||
return withContentSriSync(cache, integrity, (cpath, sri) => {
|
||||
return fs.copyFileSync(cpath, dest)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.hasContent = hasContent
|
||||
function hasContent (cache, integrity) {
|
||||
if (!integrity) { return BB.resolve(false) }
|
||||
return withContentSri(cache, integrity, (cpath, sri) => {
|
||||
return lstatAsync(cpath).then(stat => ({ size: stat.size, sri, stat }))
|
||||
}).catch(err => {
|
||||
if (err.code === 'ENOENT') { return false }
|
||||
if (err.code === 'EPERM') {
|
||||
if (process.platform !== 'win32') {
|
||||
throw err
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.hasContent.sync = hasContentSync
|
||||
function hasContentSync (cache, integrity) {
|
||||
if (!integrity) { return false }
|
||||
return withContentSriSync(cache, integrity, (cpath, sri) => {
|
||||
try {
|
||||
const stat = fs.lstatSync(cpath)
|
||||
return { size: stat.size, sri, stat }
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') { return false }
|
||||
if (err.code === 'EPERM') {
|
||||
if (process.platform !== 'win32') {
|
||||
throw err
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function withContentSri (cache, integrity, fn) {
|
||||
return BB.try(() => {
|
||||
const sri = ssri.parse(integrity)
|
||||
// If `integrity` has multiple entries, pick the first digest
|
||||
// with available local data.
|
||||
const algo = sri.pickAlgorithm()
|
||||
const digests = sri[algo]
|
||||
if (digests.length <= 1) {
|
||||
const cpath = contentPath(cache, digests[0])
|
||||
return fn(cpath, digests[0])
|
||||
} else {
|
||||
return BB.any(sri[sri.pickAlgorithm()].map(meta => {
|
||||
return withContentSri(cache, meta, fn)
|
||||
}, { concurrency: 1 }))
|
||||
.catch(err => {
|
||||
if ([].some.call(err, e => e.code === 'ENOENT')) {
|
||||
throw Object.assign(
|
||||
new Error('No matching content found for ' + sri.toString()),
|
||||
{ code: 'ENOENT' }
|
||||
)
|
||||
} else {
|
||||
throw err[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function withContentSriSync (cache, integrity, fn) {
|
||||
const sri = ssri.parse(integrity)
|
||||
// If `integrity` has multiple entries, pick the first digest
|
||||
// with available local data.
|
||||
const algo = sri.pickAlgorithm()
|
||||
const digests = sri[algo]
|
||||
if (digests.length <= 1) {
|
||||
const cpath = contentPath(cache, digests[0])
|
||||
return fn(cpath, digests[0])
|
||||
} else {
|
||||
let lastErr = null
|
||||
for (const meta of sri[sri.pickAlgorithm()]) {
|
||||
try {
|
||||
return withContentSriSync(cache, meta, fn)
|
||||
} catch (err) {
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
if (lastErr) { throw lastErr }
|
||||
}
|
||||
}
|
||||
|
||||
function sizeError (expected, found) {
|
||||
var err = new Error(Y`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
|
||||
err.expected = expected
|
||||
err.found = found
|
||||
err.code = 'EBADSIZE'
|
||||
return err
|
||||
}
|
||||
|
||||
function integrityError (sri, path) {
|
||||
var err = new Error(Y`Integrity verification failed for ${sri} (${path})`)
|
||||
err.code = 'EINTEGRITY'
|
||||
err.sri = sri
|
||||
err.path = path
|
||||
return err
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const contentPath = require('./path')
|
||||
const hasContent = require('./read').hasContent
|
||||
const rimraf = BB.promisify(require('rimraf'))
|
||||
|
||||
module.exports = rm
|
||||
function rm (cache, integrity) {
|
||||
return hasContent(cache, integrity).then(content => {
|
||||
if (content) {
|
||||
const sri = content.sri
|
||||
if (sri) {
|
||||
return rimraf(contentPath(cache, sri)).then(() => true)
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const contentPath = require('./path')
|
||||
const fixOwner = require('../util/fix-owner')
|
||||
const fs = require('graceful-fs')
|
||||
const moveFile = require('../util/move-file')
|
||||
const PassThrough = require('stream').PassThrough
|
||||
const path = require('path')
|
||||
const pipe = BB.promisify(require('mississippi').pipe)
|
||||
const rimraf = BB.promisify(require('rimraf'))
|
||||
const ssri = require('ssri')
|
||||
const to = require('mississippi').to
|
||||
const uniqueFilename = require('unique-filename')
|
||||
const Y = require('../util/y.js')
|
||||
|
||||
const writeFileAsync = BB.promisify(fs.writeFile)
|
||||
|
||||
module.exports = write
|
||||
function write (cache, data, opts) {
|
||||
opts = opts || {}
|
||||
if (opts.algorithms && opts.algorithms.length > 1) {
|
||||
throw new Error(
|
||||
Y`opts.algorithms only supports a single algorithm for now`
|
||||
)
|
||||
}
|
||||
if (typeof opts.size === 'number' && data.length !== opts.size) {
|
||||
return BB.reject(sizeError(opts.size, data.length))
|
||||
}
|
||||
const sri = ssri.fromData(data, {
|
||||
algorithms: opts.algorithms
|
||||
})
|
||||
if (opts.integrity && !ssri.checkData(data, opts.integrity, opts)) {
|
||||
return BB.reject(checksumError(opts.integrity, sri))
|
||||
}
|
||||
return BB.using(makeTmp(cache, opts), tmp => (
|
||||
writeFileAsync(
|
||||
tmp.target, data, { flag: 'wx' }
|
||||
).then(() => (
|
||||
moveToDestination(tmp, cache, sri, opts)
|
||||
))
|
||||
)).then(() => ({ integrity: sri, size: data.length }))
|
||||
}
|
||||
|
||||
module.exports.stream = writeStream
|
||||
function writeStream (cache, opts) {
|
||||
opts = opts || {}
|
||||
const inputStream = new PassThrough()
|
||||
let inputErr = false
|
||||
function errCheck () {
|
||||
if (inputErr) { throw inputErr }
|
||||
}
|
||||
|
||||
let allDone
|
||||
const ret = to((c, n, cb) => {
|
||||
if (!allDone) {
|
||||
allDone = handleContent(inputStream, cache, opts, errCheck)
|
||||
}
|
||||
inputStream.write(c, n, cb)
|
||||
}, cb => {
|
||||
inputStream.end(() => {
|
||||
if (!allDone) {
|
||||
const e = new Error(Y`Cache input stream was empty`)
|
||||
e.code = 'ENODATA'
|
||||
return ret.emit('error', e)
|
||||
}
|
||||
allDone.then(res => {
|
||||
res.integrity && ret.emit('integrity', res.integrity)
|
||||
res.size !== null && ret.emit('size', res.size)
|
||||
cb()
|
||||
}, e => {
|
||||
ret.emit('error', e)
|
||||
})
|
||||
})
|
||||
})
|
||||
ret.once('error', e => {
|
||||
inputErr = e
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
function handleContent (inputStream, cache, opts, errCheck) {
|
||||
return BB.using(makeTmp(cache, opts), tmp => {
|
||||
errCheck()
|
||||
return pipeToTmp(
|
||||
inputStream, cache, tmp.target, opts, errCheck
|
||||
).then(res => {
|
||||
return moveToDestination(
|
||||
tmp, cache, res.integrity, opts, errCheck
|
||||
).then(() => res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function pipeToTmp (inputStream, cache, tmpTarget, opts, errCheck) {
|
||||
return BB.resolve().then(() => {
|
||||
let integrity
|
||||
let size
|
||||
const hashStream = ssri.integrityStream({
|
||||
integrity: opts.integrity,
|
||||
algorithms: opts.algorithms,
|
||||
size: opts.size
|
||||
}).on('integrity', s => {
|
||||
integrity = s
|
||||
}).on('size', s => {
|
||||
size = s
|
||||
})
|
||||
const outStream = fs.createWriteStream(tmpTarget, {
|
||||
flags: 'wx'
|
||||
})
|
||||
errCheck()
|
||||
return pipe(inputStream, hashStream, outStream).then(() => {
|
||||
return { integrity, size }
|
||||
}).catch(err => {
|
||||
return rimraf(tmpTarget).then(() => { throw err })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function makeTmp (cache, opts) {
|
||||
const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
|
||||
return fixOwner.mkdirfix(
|
||||
cache, path.dirname(tmpTarget)
|
||||
).then(() => ({
|
||||
target: tmpTarget,
|
||||
moved: false
|
||||
})).disposer(tmp => (!tmp.moved && rimraf(tmp.target)))
|
||||
}
|
||||
|
||||
function moveToDestination (tmp, cache, sri, opts, errCheck) {
|
||||
errCheck && errCheck()
|
||||
const destination = contentPath(cache, sri)
|
||||
const destDir = path.dirname(destination)
|
||||
|
||||
return fixOwner.mkdirfix(
|
||||
cache, destDir
|
||||
).then(() => {
|
||||
errCheck && errCheck()
|
||||
return moveFile(tmp.target, destination)
|
||||
}).then(() => {
|
||||
errCheck && errCheck()
|
||||
tmp.moved = true
|
||||
return fixOwner.chownr(cache, destination)
|
||||
})
|
||||
}
|
||||
|
||||
function sizeError (expected, found) {
|
||||
var err = new Error(Y`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
|
||||
err.expected = expected
|
||||
err.found = found
|
||||
err.code = 'EBADSIZE'
|
||||
return err
|
||||
}
|
||||
|
||||
function checksumError (expected, found) {
|
||||
var err = new Error(Y`Integrity check failed:
|
||||
Wanted: ${expected}
|
||||
Found: ${found}`)
|
||||
err.code = 'EINTEGRITY'
|
||||
err.expected = expected
|
||||
err.found = found
|
||||
return err
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const contentPath = require('./content/path')
|
||||
const crypto = require('crypto')
|
||||
const figgyPudding = require('figgy-pudding')
|
||||
const fixOwner = require('./util/fix-owner')
|
||||
const fs = require('graceful-fs')
|
||||
const hashToSegments = require('./util/hash-to-segments')
|
||||
const ms = require('mississippi')
|
||||
const path = require('path')
|
||||
const ssri = require('ssri')
|
||||
const Y = require('./util/y.js')
|
||||
|
||||
const indexV = require('../package.json')['cache-version'].index
|
||||
|
||||
const appendFileAsync = BB.promisify(fs.appendFile)
|
||||
const readFileAsync = BB.promisify(fs.readFile)
|
||||
const readdirAsync = BB.promisify(fs.readdir)
|
||||
const concat = ms.concat
|
||||
const from = ms.from
|
||||
|
||||
module.exports.NotFoundError = class NotFoundError extends Error {
|
||||
constructor (cache, key) {
|
||||
super(Y`No cache entry for \`${key}\` found in \`${cache}\``)
|
||||
this.code = 'ENOENT'
|
||||
this.cache = cache
|
||||
this.key = key
|
||||
}
|
||||
}
|
||||
|
||||
const IndexOpts = figgyPudding({
|
||||
metadata: {},
|
||||
size: {}
|
||||
})
|
||||
|
||||
module.exports.insert = insert
|
||||
function insert (cache, key, integrity, opts) {
|
||||
opts = IndexOpts(opts)
|
||||
const bucket = bucketPath(cache, key)
|
||||
const entry = {
|
||||
key,
|
||||
integrity: integrity && ssri.stringify(integrity),
|
||||
time: Date.now(),
|
||||
size: opts.size,
|
||||
metadata: opts.metadata
|
||||
}
|
||||
return fixOwner.mkdirfix(
|
||||
cache, path.dirname(bucket)
|
||||
).then(() => {
|
||||
const stringified = JSON.stringify(entry)
|
||||
// NOTE - Cleverness ahoy!
|
||||
//
|
||||
// This works because it's tremendously unlikely for an entry to corrupt
|
||||
// another while still preserving the string length of the JSON in
|
||||
// question. So, we just slap the length in there and verify it on read.
|
||||
//
|
||||
// Thanks to @isaacs for the whiteboarding session that ended up with this.
|
||||
return appendFileAsync(
|
||||
bucket, `\n${hashEntry(stringified)}\t${stringified}`
|
||||
)
|
||||
}).then(
|
||||
() => fixOwner.chownr(cache, bucket)
|
||||
).catch({ code: 'ENOENT' }, () => {
|
||||
// There's a class of race conditions that happen when things get deleted
|
||||
// during fixOwner, or between the two mkdirfix/chownr calls.
|
||||
//
|
||||
// It's perfectly fine to just not bother in those cases and lie
|
||||
// that the index entry was written. Because it's a cache.
|
||||
}).then(() => {
|
||||
return formatEntry(cache, entry)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.insert.sync = insertSync
|
||||
function insertSync (cache, key, integrity, opts) {
|
||||
opts = IndexOpts(opts)
|
||||
const bucket = bucketPath(cache, key)
|
||||
const entry = {
|
||||
key,
|
||||
integrity: integrity && ssri.stringify(integrity),
|
||||
time: Date.now(),
|
||||
size: opts.size,
|
||||
metadata: opts.metadata
|
||||
}
|
||||
fixOwner.mkdirfix.sync(cache, path.dirname(bucket))
|
||||
const stringified = JSON.stringify(entry)
|
||||
fs.appendFileSync(
|
||||
bucket, `\n${hashEntry(stringified)}\t${stringified}`
|
||||
)
|
||||
try {
|
||||
fixOwner.chownr.sync(cache, bucket)
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
return formatEntry(cache, entry)
|
||||
}
|
||||
|
||||
module.exports.find = find
|
||||
function find (cache, key) {
|
||||
const bucket = bucketPath(cache, key)
|
||||
return bucketEntries(bucket).then(entries => {
|
||||
return entries.reduce((latest, next) => {
|
||||
if (next && next.key === key) {
|
||||
return formatEntry(cache, next)
|
||||
} else {
|
||||
return latest
|
||||
}
|
||||
}, null)
|
||||
}).catch(err => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return null
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.find.sync = findSync
|
||||
function findSync (cache, key) {
|
||||
const bucket = bucketPath(cache, key)
|
||||
try {
|
||||
return bucketEntriesSync(bucket).reduce((latest, next) => {
|
||||
if (next && next.key === key) {
|
||||
return formatEntry(cache, next)
|
||||
} else {
|
||||
return latest
|
||||
}
|
||||
}, null)
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return null
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.delete = del
|
||||
function del (cache, key, opts) {
|
||||
return insert(cache, key, null, opts)
|
||||
}
|
||||
|
||||
module.exports.delete.sync = delSync
|
||||
function delSync (cache, key, opts) {
|
||||
return insertSync(cache, key, null, opts)
|
||||
}
|
||||
|
||||
module.exports.lsStream = lsStream
|
||||
function lsStream (cache) {
|
||||
const indexDir = bucketDir(cache)
|
||||
const stream = from.obj()
|
||||
|
||||
// "/cachename/*"
|
||||
readdirOrEmpty(indexDir).map(bucket => {
|
||||
const bucketPath = path.join(indexDir, bucket)
|
||||
|
||||
// "/cachename/<bucket 0xFF>/*"
|
||||
return readdirOrEmpty(bucketPath).map(subbucket => {
|
||||
const subbucketPath = path.join(bucketPath, subbucket)
|
||||
|
||||
// "/cachename/<bucket 0xFF>/<bucket 0xFF>/*"
|
||||
return readdirOrEmpty(subbucketPath).map(entry => {
|
||||
const getKeyToEntry = bucketEntries(
|
||||
path.join(subbucketPath, entry)
|
||||
).reduce((acc, entry) => {
|
||||
acc.set(entry.key, entry)
|
||||
return acc
|
||||
}, new Map())
|
||||
|
||||
return getKeyToEntry.then(reduced => {
|
||||
for (let entry of reduced.values()) {
|
||||
const formatted = formatEntry(cache, entry)
|
||||
formatted && stream.push(formatted)
|
||||
}
|
||||
}).catch({ code: 'ENOENT' }, nop)
|
||||
})
|
||||
})
|
||||
}).then(() => {
|
||||
stream.push(null)
|
||||
}, err => {
|
||||
stream.emit('error', err)
|
||||
})
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
module.exports.ls = ls
|
||||
function ls (cache) {
|
||||
return BB.fromNode(cb => {
|
||||
lsStream(cache).on('error', cb).pipe(concat(entries => {
|
||||
cb(null, entries.reduce((acc, xs) => {
|
||||
acc[xs.key] = xs
|
||||
return acc
|
||||
}, {}))
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function bucketEntries (bucket, filter) {
|
||||
return readFileAsync(
|
||||
bucket, 'utf8'
|
||||
).then(data => _bucketEntries(data, filter))
|
||||
}
|
||||
|
||||
function bucketEntriesSync (bucket, filter) {
|
||||
const data = fs.readFileSync(bucket, 'utf8')
|
||||
return _bucketEntries(data, filter)
|
||||
}
|
||||
|
||||
function _bucketEntries (data, filter) {
|
||||
let entries = []
|
||||
data.split('\n').forEach(entry => {
|
||||
if (!entry) { return }
|
||||
const pieces = entry.split('\t')
|
||||
if (!pieces[1] || hashEntry(pieces[1]) !== pieces[0]) {
|
||||
// Hash is no good! Corruption or malice? Doesn't matter!
|
||||
// EJECT EJECT
|
||||
return
|
||||
}
|
||||
let obj
|
||||
try {
|
||||
obj = JSON.parse(pieces[1])
|
||||
} catch (e) {
|
||||
// Entry is corrupted!
|
||||
return
|
||||
}
|
||||
if (obj) {
|
||||
entries.push(obj)
|
||||
}
|
||||
})
|
||||
return entries
|
||||
}
|
||||
|
||||
module.exports._bucketDir = bucketDir
|
||||
function bucketDir (cache) {
|
||||
return path.join(cache, `index-v${indexV}`)
|
||||
}
|
||||
|
||||
module.exports._bucketPath = bucketPath
|
||||
function bucketPath (cache, key) {
|
||||
const hashed = hashKey(key)
|
||||
return path.join.apply(path, [bucketDir(cache)].concat(
|
||||
hashToSegments(hashed)
|
||||
))
|
||||
}
|
||||
|
||||
module.exports._hashKey = hashKey
|
||||
function hashKey (key) {
|
||||
return hash(key, 'sha256')
|
||||
}
|
||||
|
||||
module.exports._hashEntry = hashEntry
|
||||
function hashEntry (str) {
|
||||
return hash(str, 'sha1')
|
||||
}
|
||||
|
||||
function hash (str, digest) {
|
||||
return crypto
|
||||
.createHash(digest)
|
||||
.update(str)
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
function formatEntry (cache, entry) {
|
||||
// Treat null digests as deletions. They'll shadow any previous entries.
|
||||
if (!entry.integrity) { return null }
|
||||
return {
|
||||
key: entry.key,
|
||||
integrity: entry.integrity,
|
||||
path: contentPath(cache, entry.integrity),
|
||||
size: entry.size,
|
||||
time: entry.time,
|
||||
metadata: entry.metadata
|
||||
}
|
||||
}
|
||||
|
||||
function readdirOrEmpty (dir) {
|
||||
return readdirAsync(dir)
|
||||
.catch({ code: 'ENOENT' }, () => [])
|
||||
.catch({ code: 'ENOTDIR' }, () => [])
|
||||
}
|
||||
|
||||
function nop () {
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
'use strict'
|
||||
|
||||
const LRU = require('lru-cache')
|
||||
|
||||
const MAX_SIZE = 50 * 1024 * 1024 // 50MB
|
||||
const MAX_AGE = 3 * 60 * 1000
|
||||
|
||||
let MEMOIZED = new LRU({
|
||||
max: MAX_SIZE,
|
||||
maxAge: MAX_AGE,
|
||||
length: (entry, key) => {
|
||||
if (key.startsWith('key:')) {
|
||||
return entry.data.length
|
||||
} else if (key.startsWith('digest:')) {
|
||||
return entry.length
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports.clearMemoized = clearMemoized
|
||||
function clearMemoized () {
|
||||
const old = {}
|
||||
MEMOIZED.forEach((v, k) => {
|
||||
old[k] = v
|
||||
})
|
||||
MEMOIZED.reset()
|
||||
return old
|
||||
}
|
||||
|
||||
module.exports.put = put
|
||||
function put (cache, entry, data, opts) {
|
||||
pickMem(opts).set(`key:${cache}:${entry.key}`, { entry, data })
|
||||
putDigest(cache, entry.integrity, data, opts)
|
||||
}
|
||||
|
||||
module.exports.put.byDigest = putDigest
|
||||
function putDigest (cache, integrity, data, opts) {
|
||||
pickMem(opts).set(`digest:${cache}:${integrity}`, data)
|
||||
}
|
||||
|
||||
module.exports.get = get
|
||||
function get (cache, key, opts) {
|
||||
return pickMem(opts).get(`key:${cache}:${key}`)
|
||||
}
|
||||
|
||||
module.exports.get.byDigest = getDigest
|
||||
function getDigest (cache, integrity, opts) {
|
||||
return pickMem(opts).get(`digest:${cache}:${integrity}`)
|
||||
}
|
||||
|
||||
class ObjProxy {
|
||||
constructor (obj) {
|
||||
this.obj = obj
|
||||
}
|
||||
get (key) { return this.obj[key] }
|
||||
set (key, val) { this.obj[key] = val }
|
||||
}
|
||||
|
||||
function pickMem (opts) {
|
||||
if (!opts || !opts.memoize) {
|
||||
return MEMOIZED
|
||||
} else if (opts.memoize.get && opts.memoize.set) {
|
||||
return opts.memoize
|
||||
} else if (typeof opts.memoize === 'object') {
|
||||
return new ObjProxy(opts.memoize)
|
||||
} else {
|
||||
return MEMOIZED
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const chownr = BB.promisify(require('chownr'))
|
||||
const mkdirp = BB.promisify(require('mkdirp'))
|
||||
const inflight = require('promise-inflight')
|
||||
const inferOwner = require('infer-owner')
|
||||
|
||||
// Memoize getuid()/getgid() calls.
|
||||
// patch process.setuid/setgid to invalidate cached value on change
|
||||
const self = { uid: null, gid: null }
|
||||
const getSelf = () => {
|
||||
if (typeof self.uid !== 'number') {
|
||||
self.uid = process.getuid()
|
||||
const setuid = process.setuid
|
||||
process.setuid = (uid) => {
|
||||
self.uid = null
|
||||
process.setuid = setuid
|
||||
return process.setuid(uid)
|
||||
}
|
||||
}
|
||||
if (typeof self.gid !== 'number') {
|
||||
self.gid = process.getgid()
|
||||
const setgid = process.setgid
|
||||
process.setgid = (gid) => {
|
||||
self.gid = null
|
||||
process.setgid = setgid
|
||||
return process.setgid(gid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.chownr = fixOwner
|
||||
function fixOwner (cache, filepath) {
|
||||
if (!process.getuid) {
|
||||
// This platform doesn't need ownership fixing
|
||||
return BB.resolve()
|
||||
}
|
||||
|
||||
getSelf()
|
||||
if (self.uid !== 0) {
|
||||
// almost certainly can't chown anyway
|
||||
return BB.resolve()
|
||||
}
|
||||
|
||||
return BB.resolve(inferOwner(cache)).then(owner => {
|
||||
const { uid, gid } = owner
|
||||
|
||||
// No need to override if it's already what we used.
|
||||
if (self.uid === uid && self.gid === gid) {
|
||||
return
|
||||
}
|
||||
|
||||
return inflight(
|
||||
'fixOwner: fixing ownership on ' + filepath,
|
||||
() => chownr(
|
||||
filepath,
|
||||
typeof uid === 'number' ? uid : self.uid,
|
||||
typeof gid === 'number' ? gid : self.gid
|
||||
).catch({ code: 'ENOENT' }, () => null)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.chownr.sync = fixOwnerSync
|
||||
function fixOwnerSync (cache, filepath) {
|
||||
if (!process.getuid) {
|
||||
// This platform doesn't need ownership fixing
|
||||
return
|
||||
}
|
||||
const { uid, gid } = inferOwner.sync(cache)
|
||||
getSelf()
|
||||
if (self.uid === uid && self.gid === gid) {
|
||||
// No need to override if it's already what we used.
|
||||
return
|
||||
}
|
||||
try {
|
||||
chownr.sync(
|
||||
filepath,
|
||||
typeof uid === 'number' ? uid : self.uid,
|
||||
typeof gid === 'number' ? gid : self.gid
|
||||
)
|
||||
} catch (err) {
|
||||
// only catch ENOENT, any other error is a problem.
|
||||
if (err.code === 'ENOENT') {
|
||||
return null
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.mkdirfix = mkdirfix
|
||||
function mkdirfix (cache, p, cb) {
|
||||
// we have to infer the owner _before_ making the directory, even though
|
||||
// we aren't going to use the results, since the cache itself might not
|
||||
// exist yet. If we mkdirp it, then our current uid/gid will be assumed
|
||||
// to be correct if it creates the cache folder in the process.
|
||||
return BB.resolve(inferOwner(cache)).then(() => {
|
||||
return mkdirp(p).then(made => {
|
||||
if (made) {
|
||||
return fixOwner(cache, made).then(() => made)
|
||||
}
|
||||
}).catch({ code: 'EEXIST' }, () => {
|
||||
// There's a race in mkdirp!
|
||||
return fixOwner(cache, p).then(() => null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.mkdirfix.sync = mkdirfixSync
|
||||
function mkdirfixSync (cache, p) {
|
||||
try {
|
||||
inferOwner.sync(cache)
|
||||
const made = mkdirp.sync(p)
|
||||
if (made) {
|
||||
fixOwnerSync(cache, made)
|
||||
return made
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'EEXIST') {
|
||||
fixOwnerSync(cache, p)
|
||||
return null
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = hashToSegments
|
||||
|
||||
function hashToSegments (hash) {
|
||||
return [
|
||||
hash.slice(0, 2),
|
||||
hash.slice(2, 4),
|
||||
hash.slice(4)
|
||||
]
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('graceful-fs')
|
||||
const BB = require('bluebird')
|
||||
const chmod = BB.promisify(fs.chmod)
|
||||
const unlink = BB.promisify(fs.unlink)
|
||||
let move
|
||||
let pinflight
|
||||
|
||||
module.exports = moveFile
|
||||
function moveFile (src, dest) {
|
||||
// This isn't quite an fs.rename -- the assumption is that
|
||||
// if `dest` already exists, and we get certain errors while
|
||||
// trying to move it, we should just not bother.
|
||||
//
|
||||
// In the case of cache corruption, users will receive an
|
||||
// EINTEGRITY error elsewhere, and can remove the offending
|
||||
// content their own way.
|
||||
//
|
||||
// Note that, as the name suggests, this strictly only supports file moves.
|
||||
return BB.fromNode(cb => {
|
||||
fs.link(src, dest, err => {
|
||||
if (err) {
|
||||
if (err.code === 'EEXIST' || err.code === 'EBUSY') {
|
||||
// file already exists, so whatever
|
||||
} else if (err.code === 'EPERM' && process.platform === 'win32') {
|
||||
// file handle stayed open even past graceful-fs limits
|
||||
} else {
|
||||
return cb(err)
|
||||
}
|
||||
}
|
||||
return cb()
|
||||
})
|
||||
}).then(() => {
|
||||
// content should never change for any reason, so make it read-only
|
||||
return BB.join(unlink(src), process.platform !== 'win32' && chmod(dest, '0444'))
|
||||
}).catch(() => {
|
||||
if (!pinflight) { pinflight = require('promise-inflight') }
|
||||
return pinflight('cacache-move-file:' + dest, () => {
|
||||
return BB.promisify(fs.stat)(dest).catch(err => {
|
||||
if (err.code !== 'ENOENT') {
|
||||
// Something else is wrong here. Bail bail bail
|
||||
throw err
|
||||
}
|
||||
// file doesn't already exist! let's try a rename -> copy fallback
|
||||
if (!move) { move = require('move-concurrently') }
|
||||
return move(src, dest, { BB, fs })
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const figgyPudding = require('figgy-pudding')
|
||||
const fixOwner = require('./fix-owner')
|
||||
const path = require('path')
|
||||
const rimraf = BB.promisify(require('rimraf'))
|
||||
const uniqueFilename = require('unique-filename')
|
||||
|
||||
const TmpOpts = figgyPudding({
|
||||
tmpPrefix: {}
|
||||
})
|
||||
|
||||
module.exports.mkdir = mktmpdir
|
||||
function mktmpdir (cache, opts) {
|
||||
opts = TmpOpts(opts)
|
||||
const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
|
||||
return fixOwner.mkdirfix(cache, tmpTarget).then(() => {
|
||||
return tmpTarget
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.withTmp = withTmp
|
||||
function withTmp (cache, opts, cb) {
|
||||
if (!cb) {
|
||||
cb = opts
|
||||
opts = null
|
||||
}
|
||||
opts = TmpOpts(opts)
|
||||
return BB.using(mktmpdir(cache, opts).disposer(rimraf), cb)
|
||||
}
|
||||
|
||||
module.exports.fix = fixtmpdir
|
||||
function fixtmpdir (cache) {
|
||||
return fixOwner(cache, path.join(cache, 'tmp'))
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const y18n = require('y18n')({
|
||||
directory: path.join(__dirname, '../../locales'),
|
||||
locale: 'en',
|
||||
updateFiles: process.env.CACACHE_UPDATE_LOCALE_FILES === 'true'
|
||||
})
|
||||
|
||||
module.exports = yTag
|
||||
function yTag (parts) {
|
||||
let str = ''
|
||||
parts.forEach((part, i) => {
|
||||
const arg = arguments[i + 1]
|
||||
str += part
|
||||
if (arg) {
|
||||
str += '%s'
|
||||
}
|
||||
})
|
||||
return y18n.__.apply(null, [str].concat([].slice.call(arguments, 1)))
|
||||
}
|
||||
|
||||
module.exports.setLocale = locale => {
|
||||
y18n.setLocale(locale)
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const contentPath = require('./content/path')
|
||||
const figgyPudding = require('figgy-pudding')
|
||||
const finished = BB.promisify(require('mississippi').finished)
|
||||
const fixOwner = require('./util/fix-owner')
|
||||
const fs = require('graceful-fs')
|
||||
const glob = BB.promisify(require('glob'))
|
||||
const index = require('./entry-index')
|
||||
const path = require('path')
|
||||
const rimraf = BB.promisify(require('rimraf'))
|
||||
const ssri = require('ssri')
|
||||
|
||||
BB.promisifyAll(fs)
|
||||
|
||||
const VerifyOpts = figgyPudding({
|
||||
concurrency: {
|
||||
default: 20
|
||||
},
|
||||
filter: {},
|
||||
log: {
|
||||
default: { silly () {} }
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = verify
|
||||
function verify (cache, opts) {
|
||||
opts = VerifyOpts(opts)
|
||||
opts.log.silly('verify', 'verifying cache at', cache)
|
||||
return BB.reduce([
|
||||
markStartTime,
|
||||
fixPerms,
|
||||
garbageCollect,
|
||||
rebuildIndex,
|
||||
cleanTmp,
|
||||
writeVerifile,
|
||||
markEndTime
|
||||
], (stats, step, i) => {
|
||||
const label = step.name || `step #${i}`
|
||||
const start = new Date()
|
||||
return BB.resolve(step(cache, opts)).then(s => {
|
||||
s && Object.keys(s).forEach(k => {
|
||||
stats[k] = s[k]
|
||||
})
|
||||
const end = new Date()
|
||||
if (!stats.runTime) { stats.runTime = {} }
|
||||
stats.runTime[label] = end - start
|
||||
return stats
|
||||
})
|
||||
}, {}).tap(stats => {
|
||||
stats.runTime.total = stats.endTime - stats.startTime
|
||||
opts.log.silly('verify', 'verification finished for', cache, 'in', `${stats.runTime.total}ms`)
|
||||
})
|
||||
}
|
||||
|
||||
function markStartTime (cache, opts) {
|
||||
return { startTime: new Date() }
|
||||
}
|
||||
|
||||
function markEndTime (cache, opts) {
|
||||
return { endTime: new Date() }
|
||||
}
|
||||
|
||||
function fixPerms (cache, opts) {
|
||||
opts.log.silly('verify', 'fixing cache permissions')
|
||||
return fixOwner.mkdirfix(cache, cache).then(() => {
|
||||
// TODO - fix file permissions too
|
||||
return fixOwner.chownr(cache, cache)
|
||||
}).then(() => null)
|
||||
}
|
||||
|
||||
// Implements a naive mark-and-sweep tracing garbage collector.
|
||||
//
|
||||
// The algorithm is basically as follows:
|
||||
// 1. Read (and filter) all index entries ("pointers")
|
||||
// 2. Mark each integrity value as "live"
|
||||
// 3. Read entire filesystem tree in `content-vX/` dir
|
||||
// 4. If content is live, verify its checksum and delete it if it fails
|
||||
// 5. If content is not marked as live, rimraf it.
|
||||
//
|
||||
function garbageCollect (cache, opts) {
|
||||
opts.log.silly('verify', 'garbage collecting content')
|
||||
const indexStream = index.lsStream(cache)
|
||||
const liveContent = new Set()
|
||||
indexStream.on('data', entry => {
|
||||
if (opts.filter && !opts.filter(entry)) { return }
|
||||
liveContent.add(entry.integrity.toString())
|
||||
})
|
||||
return finished(indexStream).then(() => {
|
||||
const contentDir = contentPath._contentDir(cache)
|
||||
return glob(path.join(contentDir, '**'), {
|
||||
follow: false,
|
||||
nodir: true,
|
||||
nosort: true
|
||||
}).then(files => {
|
||||
return BB.resolve({
|
||||
verifiedContent: 0,
|
||||
reclaimedCount: 0,
|
||||
reclaimedSize: 0,
|
||||
badContentCount: 0,
|
||||
keptSize: 0
|
||||
}).tap((stats) => BB.map(files, (f) => {
|
||||
const split = f.split(/[/\\]/)
|
||||
const digest = split.slice(split.length - 3).join('')
|
||||
const algo = split[split.length - 4]
|
||||
const integrity = ssri.fromHex(digest, algo)
|
||||
if (liveContent.has(integrity.toString())) {
|
||||
return verifyContent(f, integrity).then(info => {
|
||||
if (!info.valid) {
|
||||
stats.reclaimedCount++
|
||||
stats.badContentCount++
|
||||
stats.reclaimedSize += info.size
|
||||
} else {
|
||||
stats.verifiedContent++
|
||||
stats.keptSize += info.size
|
||||
}
|
||||
return stats
|
||||
})
|
||||
} else {
|
||||
// No entries refer to this content. We can delete.
|
||||
stats.reclaimedCount++
|
||||
return fs.statAsync(f).then(s => {
|
||||
return rimraf(f).then(() => {
|
||||
stats.reclaimedSize += s.size
|
||||
return stats
|
||||
})
|
||||
})
|
||||
}
|
||||
}, { concurrency: opts.concurrency }))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function verifyContent (filepath, sri) {
|
||||
return fs.statAsync(filepath).then(stat => {
|
||||
const contentInfo = {
|
||||
size: stat.size,
|
||||
valid: true
|
||||
}
|
||||
return ssri.checkStream(
|
||||
fs.createReadStream(filepath),
|
||||
sri
|
||||
).catch(err => {
|
||||
if (err.code !== 'EINTEGRITY') { throw err }
|
||||
return rimraf(filepath).then(() => {
|
||||
contentInfo.valid = false
|
||||
})
|
||||
}).then(() => contentInfo)
|
||||
}).catch({ code: 'ENOENT' }, () => ({ size: 0, valid: false }))
|
||||
}
|
||||
|
||||
function rebuildIndex (cache, opts) {
|
||||
opts.log.silly('verify', 'rebuilding index')
|
||||
return index.ls(cache).then(entries => {
|
||||
const stats = {
|
||||
missingContent: 0,
|
||||
rejectedEntries: 0,
|
||||
totalEntries: 0
|
||||
}
|
||||
const buckets = {}
|
||||
for (let k in entries) {
|
||||
if (entries.hasOwnProperty(k)) {
|
||||
const hashed = index._hashKey(k)
|
||||
const entry = entries[k]
|
||||
const excluded = opts.filter && !opts.filter(entry)
|
||||
excluded && stats.rejectedEntries++
|
||||
if (buckets[hashed] && !excluded) {
|
||||
buckets[hashed].push(entry)
|
||||
} else if (buckets[hashed] && excluded) {
|
||||
// skip
|
||||
} else if (excluded) {
|
||||
buckets[hashed] = []
|
||||
buckets[hashed]._path = index._bucketPath(cache, k)
|
||||
} else {
|
||||
buckets[hashed] = [entry]
|
||||
buckets[hashed]._path = index._bucketPath(cache, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return BB.map(Object.keys(buckets), key => {
|
||||
return rebuildBucket(cache, buckets[key], stats, opts)
|
||||
}, { concurrency: opts.concurrency }).then(() => stats)
|
||||
})
|
||||
}
|
||||
|
||||
function rebuildBucket (cache, bucket, stats, opts) {
|
||||
return fs.truncateAsync(bucket._path).then(() => {
|
||||
// This needs to be serialized because cacache explicitly
|
||||
// lets very racy bucket conflicts clobber each other.
|
||||
return BB.mapSeries(bucket, entry => {
|
||||
const content = contentPath(cache, entry.integrity)
|
||||
return fs.statAsync(content).then(() => {
|
||||
return index.insert(cache, entry.key, entry.integrity, {
|
||||
metadata: entry.metadata,
|
||||
size: entry.size
|
||||
}).then(() => { stats.totalEntries++ })
|
||||
}).catch({ code: 'ENOENT' }, () => {
|
||||
stats.rejectedEntries++
|
||||
stats.missingContent++
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function cleanTmp (cache, opts) {
|
||||
opts.log.silly('verify', 'cleaning tmp directory')
|
||||
return rimraf(path.join(cache, 'tmp'))
|
||||
}
|
||||
|
||||
function writeVerifile (cache, opts) {
|
||||
const verifile = path.join(cache, '_lastverified')
|
||||
opts.log.silly('verify', 'writing verifile to ' + verifile)
|
||||
try {
|
||||
return fs.writeFileAsync(verifile, '' + (+(new Date())))
|
||||
} finally {
|
||||
fixOwner.chownr.sync(cache, verifile)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.lastRun = lastRun
|
||||
function lastRun (cache) {
|
||||
return fs.readFileAsync(
|
||||
path.join(cache, '_lastverified'), 'utf8'
|
||||
).then(data => new Date(+data))
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
'use strict'
|
||||
|
||||
const ls = require('../ls.js')
|
||||
const get = require('../get.js')
|
||||
const put = require('../put.js')
|
||||
const rm = require('../rm.js')
|
||||
const verify = require('../verify.js')
|
||||
const setLocale = require('../lib/util/y.js').setLocale
|
||||
const clearMemoized = require('../lib/memoization.js').clearMemoized
|
||||
const tmp = require('../lib/util/tmp.js')
|
||||
|
||||
setLocale('en')
|
||||
|
||||
const x = module.exports
|
||||
|
||||
x.ls = cache => ls(cache)
|
||||
x.ls.stream = cache => ls.stream(cache)
|
||||
|
||||
x.get = (cache, key, opts) => get(cache, key, opts)
|
||||
x.get.byDigest = (cache, hash, opts) => get.byDigest(cache, hash, opts)
|
||||
x.get.sync = (cache, key, opts) => get.sync(cache, key, opts)
|
||||
x.get.sync.byDigest = (cache, key, opts) => get.sync.byDigest(cache, key, opts)
|
||||
x.get.stream = (cache, key, opts) => get.stream(cache, key, opts)
|
||||
x.get.stream.byDigest = (cache, hash, opts) => get.stream.byDigest(cache, hash, opts)
|
||||
x.get.copy = (cache, key, dest, opts) => get.copy(cache, key, dest, opts)
|
||||
x.get.copy.byDigest = (cache, hash, dest, opts) => get.copy.byDigest(cache, hash, dest, opts)
|
||||
x.get.info = (cache, key) => get.info(cache, key)
|
||||
x.get.hasContent = (cache, hash) => get.hasContent(cache, hash)
|
||||
x.get.hasContent.sync = (cache, hash) => get.hasContent.sync(cache, hash)
|
||||
|
||||
x.put = (cache, key, data, opts) => put(cache, key, data, opts)
|
||||
x.put.stream = (cache, key, opts) => put.stream(cache, key, opts)
|
||||
|
||||
x.rm = (cache, key) => rm.entry(cache, key)
|
||||
x.rm.all = cache => rm.all(cache)
|
||||
x.rm.entry = x.rm
|
||||
x.rm.content = (cache, hash) => rm.content(cache, hash)
|
||||
|
||||
x.setLocale = lang => setLocale(lang)
|
||||
x.clearMemoized = () => clearMemoized()
|
||||
|
||||
x.tmp = {}
|
||||
x.tmp.mkdir = (cache, opts) => tmp.mkdir(cache, opts)
|
||||
x.tmp.withTmp = (cache, opts, cb) => tmp.withTmp(cache, opts, cb)
|
||||
|
||||
x.verify = (cache, opts) => verify(cache, opts)
|
||||
x.verify.lastRun = cache => verify.lastRun(cache)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"No cache entry for `%s` found in `%s`": "No cache entry for %s found in %s",
|
||||
"Integrity verification failed for %s (%s)": "Integrity verification failed for %s (%s)",
|
||||
"Bad data size: expected inserted data to be %s bytes, but got %s instead": "Bad data size: expected inserted data to be %s bytes, but got %s instead",
|
||||
"Cache input stream was empty": "Cache input stream was empty",
|
||||
"Integrity check failed:\n Wanted: %s\n Found: %s": "Integrity check failed:\n Wanted: %s\n Found: %s"
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
'use strict'
|
||||
|
||||
const ls = require('../ls.js')
|
||||
const get = require('../get.js')
|
||||
const put = require('../put.js')
|
||||
const rm = require('../rm.js')
|
||||
const verify = require('../verify.js')
|
||||
const setLocale = require('../lib/util/y.js').setLocale
|
||||
const clearMemoized = require('../lib/memoization.js').clearMemoized
|
||||
const tmp = require('../lib/util/tmp.js')
|
||||
|
||||
setLocale('es')
|
||||
|
||||
const x = module.exports
|
||||
|
||||
x.ls = cache => ls(cache)
|
||||
x.ls.flujo = cache => ls.stream(cache)
|
||||
|
||||
x.saca = (cache, clave, ops) => get(cache, clave, ops)
|
||||
x.saca.porHacheo = (cache, hacheo, ops) => get.byDigest(cache, hacheo, ops)
|
||||
x.saca.sinc = (cache, clave, ops) => get.sync(cache, clave, ops)
|
||||
x.saca.sinc.porHacheo = (cache, hacheo, ops) => get.sync.byDigest(cache, hacheo, ops)
|
||||
x.saca.flujo = (cache, clave, ops) => get.stream(cache, clave, ops)
|
||||
x.saca.flujo.porHacheo = (cache, hacheo, ops) => get.stream.byDigest(cache, hacheo, ops)
|
||||
x.sava.copia = (cache, clave, destino, opts) => get.copy(cache, clave, destino, opts)
|
||||
x.sava.copia.porHacheo = (cache, hacheo, destino, opts) => get.copy.byDigest(cache, hacheo, destino, opts)
|
||||
x.saca.info = (cache, clave) => get.info(cache, clave)
|
||||
x.saca.tieneDatos = (cache, hacheo) => get.hasContent(cache, hacheo)
|
||||
x.saca.tieneDatos.sinc = (cache, hacheo) => get.hasContent.sync(cache, hacheo)
|
||||
|
||||
x.mete = (cache, clave, datos, ops) => put(cache, clave, datos, ops)
|
||||
x.mete.flujo = (cache, clave, ops) => put.stream(cache, clave, ops)
|
||||
|
||||
x.rm = (cache, clave) => rm.entry(cache, clave)
|
||||
x.rm.todo = cache => rm.all(cache)
|
||||
x.rm.entrada = x.rm
|
||||
x.rm.datos = (cache, hacheo) => rm.content(cache, hacheo)
|
||||
|
||||
x.ponLenguaje = lang => setLocale(lang)
|
||||
x.limpiaMemoizado = () => clearMemoized()
|
||||
|
||||
x.tmp = {}
|
||||
x.tmp.mkdir = (cache, ops) => tmp.mkdir(cache, ops)
|
||||
x.tmp.hazdir = x.tmp.mkdir
|
||||
x.tmp.conTmp = (cache, ops, cb) => tmp.withTmp(cache, ops, cb)
|
||||
|
||||
x.verifica = (cache, ops) => verify(cache, ops)
|
||||
x.verifica.ultimaVez = cache => verify.lastRun(cache)
|
||||
x.verifica.últimaVez = x.verifica.ultimaVez
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"No cache entry for `%s` found in `%s`": "No existe ninguna entrada para «%s» en «%s»",
|
||||
"Integrity verification failed for %s (%s)": "Verificación de integridad falló para «%s» (%s)",
|
||||
"Bad data size: expected inserted data to be %s bytes, but got %s instead": "Tamaño incorrecto de datos: los datos insertados debieron haber sido %s octetos, pero fueron %s",
|
||||
"Cache input stream was empty": "El stream de entrada al caché estaba vacío"
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
var index = require('./lib/entry-index')
|
||||
|
||||
module.exports = index.ls
|
||||
module.exports.stream = index.lsStream
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
# lru cache
|
||||
|
||||
A cache object that deletes the least-recently-used items.
|
||||
|
||||
[](https://travis-ci.org/isaacs/node-lru-cache) [](https://coveralls.io/github/isaacs/node-lru-cache)
|
||||
|
||||
## Installation:
|
||||
|
||||
```javascript
|
||||
npm install lru-cache --save
|
||||
```
|
||||
|
||||
## Usage:
|
||||
|
||||
```javascript
|
||||
var LRU = require("lru-cache")
|
||||
, options = { max: 500
|
||||
, length: function (n, key) { return n * 2 + key.length }
|
||||
, dispose: function (key, n) { n.close() }
|
||||
, maxAge: 1000 * 60 * 60 }
|
||||
, cache = new LRU(options)
|
||||
, otherCache = new LRU(50) // sets just the max size
|
||||
|
||||
cache.set("key", "value")
|
||||
cache.get("key") // "value"
|
||||
|
||||
// non-string keys ARE fully supported
|
||||
// but note that it must be THE SAME object, not
|
||||
// just a JSON-equivalent object.
|
||||
var someObject = { a: 1 }
|
||||
cache.set(someObject, 'a value')
|
||||
// Object keys are not toString()-ed
|
||||
cache.set('[object Object]', 'a different value')
|
||||
assert.equal(cache.get(someObject), 'a value')
|
||||
// A similar object with same keys/values won't work,
|
||||
// because it's a different object identity
|
||||
assert.equal(cache.get({ a: 1 }), undefined)
|
||||
|
||||
cache.reset() // empty the cache
|
||||
```
|
||||
|
||||
If you put more stuff in it, then items will fall out.
|
||||
|
||||
If you try to put an oversized thing in it, then it'll fall out right
|
||||
away.
|
||||
|
||||
## Options
|
||||
|
||||
* `max` The maximum size of the cache, checked by applying the length
|
||||
function to all values in the cache. Not setting this is kind of
|
||||
silly, since that's the whole purpose of this lib, but it defaults
|
||||
to `Infinity`. Setting it to a non-number or negative number will
|
||||
throw a `TypeError`. Setting it to 0 makes it be `Infinity`.
|
||||
* `maxAge` Maximum age in ms. Items are not pro-actively pruned out
|
||||
as they age, but if you try to get an item that is too old, it'll
|
||||
drop it and return undefined instead of giving it to you.
|
||||
Setting this to a negative value will make everything seem old!
|
||||
Setting it to a non-number will throw a `TypeError`.
|
||||
* `length` Function that is used to calculate the length of stored
|
||||
items. If you're storing strings or buffers, then you probably want
|
||||
to do something like `function(n, key){return n.length}`. The default is
|
||||
`function(){return 1}`, which is fine if you want to store `max`
|
||||
like-sized things. The item is passed as the first argument, and
|
||||
the key is passed as the second argumnet.
|
||||
* `dispose` Function that is called on items when they are dropped
|
||||
from the cache. This can be handy if you want to close file
|
||||
descriptors or do other cleanup tasks when items are no longer
|
||||
accessible. Called with `key, value`. It's called *before*
|
||||
actually removing the item from the internal cache, so if you want
|
||||
to immediately put it back in, you'll have to do that in a
|
||||
`nextTick` or `setTimeout` callback or it won't do anything.
|
||||
* `stale` By default, if you set a `maxAge`, it'll only actually pull
|
||||
stale items out of the cache when you `get(key)`. (That is, it's
|
||||
not pre-emptively doing a `setTimeout` or anything.) If you set
|
||||
`stale:true`, it'll return the stale value before deleting it. If
|
||||
you don't set this, then it'll return `undefined` when you try to
|
||||
get a stale entry, as if it had already been deleted.
|
||||
* `noDisposeOnSet` By default, if you set a `dispose()` method, then
|
||||
it'll be called whenever a `set()` operation overwrites an existing
|
||||
key. If you set this option, `dispose()` will only be called when a
|
||||
key falls out of the cache, not when it is overwritten.
|
||||
* `updateAgeOnGet` When using time-expiring entries with `maxAge`,
|
||||
setting this to `true` will make each item's effective time update
|
||||
to the current time whenever it is retrieved from cache, causing it
|
||||
to not expire. (It can still fall out of cache based on recency of
|
||||
use, of course.)
|
||||
|
||||
## API
|
||||
|
||||
* `set(key, value, maxAge)`
|
||||
* `get(key) => value`
|
||||
|
||||
Both of these will update the "recently used"-ness of the key.
|
||||
They do what you think. `maxAge` is optional and overrides the
|
||||
cache `maxAge` option if provided.
|
||||
|
||||
If the key is not found, `get()` will return `undefined`.
|
||||
|
||||
The key and val can be any value.
|
||||
|
||||
* `peek(key)`
|
||||
|
||||
Returns the key value (or `undefined` if not found) without
|
||||
updating the "recently used"-ness of the key.
|
||||
|
||||
(If you find yourself using this a lot, you *might* be using the
|
||||
wrong sort of data structure, but there are some use cases where
|
||||
it's handy.)
|
||||
|
||||
* `del(key)`
|
||||
|
||||
Deletes a key out of the cache.
|
||||
|
||||
* `reset()`
|
||||
|
||||
Clear the cache entirely, throwing away all values.
|
||||
|
||||
* `has(key)`
|
||||
|
||||
Check if a key is in the cache, without updating the recent-ness
|
||||
or deleting it for being stale.
|
||||
|
||||
* `forEach(function(value,key,cache), [thisp])`
|
||||
|
||||
Just like `Array.prototype.forEach`. Iterates over all the keys
|
||||
in the cache, in order of recent-ness. (Ie, more recently used
|
||||
items are iterated over first.)
|
||||
|
||||
* `rforEach(function(value,key,cache), [thisp])`
|
||||
|
||||
The same as `cache.forEach(...)` but items are iterated over in
|
||||
reverse order. (ie, less recently used items are iterated over
|
||||
first.)
|
||||
|
||||
* `keys()`
|
||||
|
||||
Return an array of the keys in the cache.
|
||||
|
||||
* `values()`
|
||||
|
||||
Return an array of the values in the cache.
|
||||
|
||||
* `length`
|
||||
|
||||
Return total length of objects in cache taking into account
|
||||
`length` options function.
|
||||
|
||||
* `itemCount`
|
||||
|
||||
Return total quantity of objects currently in cache. Note, that
|
||||
`stale` (see options) items are returned as part of this item
|
||||
count.
|
||||
|
||||
* `dump()`
|
||||
|
||||
Return an array of the cache entries ready for serialization and usage
|
||||
with 'destinationCache.load(arr)`.
|
||||
|
||||
* `load(cacheEntriesArray)`
|
||||
|
||||
Loads another cache entries array, obtained with `sourceCache.dump()`,
|
||||
into the cache. The destination cache is reset before loading new entries
|
||||
|
||||
* `prune()`
|
||||
|
||||
Manually iterates over the entire cache proactively pruning old entries
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
'use strict'
|
||||
|
||||
// A linked list to keep track of recently-used-ness
|
||||
const Yallist = require('yallist')
|
||||
|
||||
const MAX = Symbol('max')
|
||||
const LENGTH = Symbol('length')
|
||||
const LENGTH_CALCULATOR = Symbol('lengthCalculator')
|
||||
const ALLOW_STALE = Symbol('allowStale')
|
||||
const MAX_AGE = Symbol('maxAge')
|
||||
const DISPOSE = Symbol('dispose')
|
||||
const NO_DISPOSE_ON_SET = Symbol('noDisposeOnSet')
|
||||
const LRU_LIST = Symbol('lruList')
|
||||
const CACHE = Symbol('cache')
|
||||
const UPDATE_AGE_ON_GET = Symbol('updateAgeOnGet')
|
||||
|
||||
const naiveLength = () => 1
|
||||
|
||||
// lruList is a yallist where the head is the youngest
|
||||
// item, and the tail is the oldest. the list contains the Hit
|
||||
// objects as the entries.
|
||||
// Each Hit object has a reference to its Yallist.Node. This
|
||||
// never changes.
|
||||
//
|
||||
// cache is a Map (or PseudoMap) that matches the keys to
|
||||
// the Yallist.Node object.
|
||||
class LRUCache {
|
||||
constructor (options) {
|
||||
if (typeof options === 'number')
|
||||
options = { max: options }
|
||||
|
||||
if (!options)
|
||||
options = {}
|
||||
|
||||
if (options.max && (typeof options.max !== 'number' || options.max < 0))
|
||||
throw new TypeError('max must be a non-negative number')
|
||||
// Kind of weird to have a default max of Infinity, but oh well.
|
||||
const max = this[MAX] = options.max || Infinity
|
||||
|
||||
const lc = options.length || naiveLength
|
||||
this[LENGTH_CALCULATOR] = (typeof lc !== 'function') ? naiveLength : lc
|
||||
this[ALLOW_STALE] = options.stale || false
|
||||
if (options.maxAge && typeof options.maxAge !== 'number')
|
||||
throw new TypeError('maxAge must be a number')
|
||||
this[MAX_AGE] = options.maxAge || 0
|
||||
this[DISPOSE] = options.dispose
|
||||
this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false
|
||||
this[UPDATE_AGE_ON_GET] = options.updateAgeOnGet || false
|
||||
this.reset()
|
||||
}
|
||||
|
||||
// resize the cache when the max changes.
|
||||
set max (mL) {
|
||||
if (typeof mL !== 'number' || mL < 0)
|
||||
throw new TypeError('max must be a non-negative number')
|
||||
|
||||
this[MAX] = mL || Infinity
|
||||
trim(this)
|
||||
}
|
||||
get max () {
|
||||
return this[MAX]
|
||||
}
|
||||
|
||||
set allowStale (allowStale) {
|
||||
this[ALLOW_STALE] = !!allowStale
|
||||
}
|
||||
get allowStale () {
|
||||
return this[ALLOW_STALE]
|
||||
}
|
||||
|
||||
set maxAge (mA) {
|
||||
if (typeof mA !== 'number')
|
||||
throw new TypeError('maxAge must be a non-negative number')
|
||||
|
||||
this[MAX_AGE] = mA
|
||||
trim(this)
|
||||
}
|
||||
get maxAge () {
|
||||
return this[MAX_AGE]
|
||||
}
|
||||
|
||||
// resize the cache when the lengthCalculator changes.
|
||||
set lengthCalculator (lC) {
|
||||
if (typeof lC !== 'function')
|
||||
lC = naiveLength
|
||||
|
||||
if (lC !== this[LENGTH_CALCULATOR]) {
|
||||
this[LENGTH_CALCULATOR] = lC
|
||||
this[LENGTH] = 0
|
||||
this[LRU_LIST].forEach(hit => {
|
||||
hit.length = this[LENGTH_CALCULATOR](hit.value, hit.key)
|
||||
this[LENGTH] += hit.length
|
||||
})
|
||||
}
|
||||
trim(this)
|
||||
}
|
||||
get lengthCalculator () { return this[LENGTH_CALCULATOR] }
|
||||
|
||||
get length () { return this[LENGTH] }
|
||||
get itemCount () { return this[LRU_LIST].length }
|
||||
|
||||
rforEach (fn, thisp) {
|
||||
thisp = thisp || this
|
||||
for (let walker = this[LRU_LIST].tail; walker !== null;) {
|
||||
const prev = walker.prev
|
||||
forEachStep(this, fn, walker, thisp)
|
||||
walker = prev
|
||||
}
|
||||
}
|
||||
|
||||
forEach (fn, thisp) {
|
||||
thisp = thisp || this
|
||||
for (let walker = this[LRU_LIST].head; walker !== null;) {
|
||||
const next = walker.next
|
||||
forEachStep(this, fn, walker, thisp)
|
||||
walker = next
|
||||
}
|
||||
}
|
||||
|
||||
keys () {
|
||||
return this[LRU_LIST].toArray().map(k => k.key)
|
||||
}
|
||||
|
||||
values () {
|
||||
return this[LRU_LIST].toArray().map(k => k.value)
|
||||
}
|
||||
|
||||
reset () {
|
||||
if (this[DISPOSE] &&
|
||||
this[LRU_LIST] &&
|
||||
this[LRU_LIST].length) {
|
||||
this[LRU_LIST].forEach(hit => this[DISPOSE](hit.key, hit.value))
|
||||
}
|
||||
|
||||
this[CACHE] = new Map() // hash of items by key
|
||||
this[LRU_LIST] = new Yallist() // list of items in order of use recency
|
||||
this[LENGTH] = 0 // length of items in the list
|
||||
}
|
||||
|
||||
dump () {
|
||||
return this[LRU_LIST].map(hit =>
|
||||
isStale(this, hit) ? false : {
|
||||
k: hit.key,
|
||||
v: hit.value,
|
||||
e: hit.now + (hit.maxAge || 0)
|
||||
}).toArray().filter(h => h)
|
||||
}
|
||||
|
||||
dumpLru () {
|
||||
return this[LRU_LIST]
|
||||
}
|
||||
|
||||
set (key, value, maxAge) {
|
||||
maxAge = maxAge || this[MAX_AGE]
|
||||
|
||||
if (maxAge && typeof maxAge !== 'number')
|
||||
throw new TypeError('maxAge must be a number')
|
||||
|
||||
const now = maxAge ? Date.now() : 0
|
||||
const len = this[LENGTH_CALCULATOR](value, key)
|
||||
|
||||
if (this[CACHE].has(key)) {
|
||||
if (len > this[MAX]) {
|
||||
del(this, this[CACHE].get(key))
|
||||
return false
|
||||
}
|
||||
|
||||
const node = this[CACHE].get(key)
|
||||
const item = node.value
|
||||
|
||||
// dispose of the old one before overwriting
|
||||
// split out into 2 ifs for better coverage tracking
|
||||
if (this[DISPOSE]) {
|
||||
if (!this[NO_DISPOSE_ON_SET])
|
||||
this[DISPOSE](key, item.value)
|
||||
}
|
||||
|
||||
item.now = now
|
||||
item.maxAge = maxAge
|
||||
item.value = value
|
||||
this[LENGTH] += len - item.length
|
||||
item.length = len
|
||||
this.get(key)
|
||||
trim(this)
|
||||
return true
|
||||
}
|
||||
|
||||
const hit = new Entry(key, value, len, now, maxAge)
|
||||
|
||||
// oversized objects fall out of cache automatically.
|
||||
if (hit.length > this[MAX]) {
|
||||
if (this[DISPOSE])
|
||||
this[DISPOSE](key, value)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
this[LENGTH] += hit.length
|
||||
this[LRU_LIST].unshift(hit)
|
||||
this[CACHE].set(key, this[LRU_LIST].head)
|
||||
trim(this)
|
||||
return true
|
||||
}
|
||||
|
||||
has (key) {
|
||||
if (!this[CACHE].has(key)) return false
|
||||
const hit = this[CACHE].get(key).value
|
||||
return !isStale(this, hit)
|
||||
}
|
||||
|
||||
get (key) {
|
||||
return get(this, key, true)
|
||||
}
|
||||
|
||||
peek (key) {
|
||||
return get(this, key, false)
|
||||
}
|
||||
|
||||
pop () {
|
||||
const node = this[LRU_LIST].tail
|
||||
if (!node)
|
||||
return null
|
||||
|
||||
del(this, node)
|
||||
return node.value
|
||||
}
|
||||
|
||||
del (key) {
|
||||
del(this, this[CACHE].get(key))
|
||||
}
|
||||
|
||||
load (arr) {
|
||||
// reset the cache
|
||||
this.reset()
|
||||
|
||||
const now = Date.now()
|
||||
// A previous serialized cache has the most recent items first
|
||||
for (let l = arr.length - 1; l >= 0; l--) {
|
||||
const hit = arr[l]
|
||||
const expiresAt = hit.e || 0
|
||||
if (expiresAt === 0)
|
||||
// the item was created without expiration in a non aged cache
|
||||
this.set(hit.k, hit.v)
|
||||
else {
|
||||
const maxAge = expiresAt - now
|
||||
// dont add already expired items
|
||||
if (maxAge > 0) {
|
||||
this.set(hit.k, hit.v, maxAge)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prune () {
|
||||
this[CACHE].forEach((value, key) => get(this, key, false))
|
||||
}
|
||||
}
|
||||
|
||||
const get = (self, key, doUse) => {
|
||||
const node = self[CACHE].get(key)
|
||||
if (node) {
|
||||
const hit = node.value
|
||||
if (isStale(self, hit)) {
|
||||
del(self, node)
|
||||
if (!self[ALLOW_STALE])
|
||||
return undefined
|
||||
} else {
|
||||
if (doUse) {
|
||||
if (self[UPDATE_AGE_ON_GET])
|
||||
node.value.now = Date.now()
|
||||
self[LRU_LIST].unshiftNode(node)
|
||||
}
|
||||
}
|
||||
return hit.value
|
||||
}
|
||||
}
|
||||
|
||||
const isStale = (self, hit) => {
|
||||
if (!hit || (!hit.maxAge && !self[MAX_AGE]))
|
||||
return false
|
||||
|
||||
const diff = Date.now() - hit.now
|
||||
return hit.maxAge ? diff > hit.maxAge
|
||||
: self[MAX_AGE] && (diff > self[MAX_AGE])
|
||||
}
|
||||
|
||||
const trim = self => {
|
||||
if (self[LENGTH] > self[MAX]) {
|
||||
for (let walker = self[LRU_LIST].tail;
|
||||
self[LENGTH] > self[MAX] && walker !== null;) {
|
||||
// We know that we're about to delete this one, and also
|
||||
// what the next least recently used key will be, so just
|
||||
// go ahead and set it now.
|
||||
const prev = walker.prev
|
||||
del(self, walker)
|
||||
walker = prev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const del = (self, node) => {
|
||||
if (node) {
|
||||
const hit = node.value
|
||||
if (self[DISPOSE])
|
||||
self[DISPOSE](hit.key, hit.value)
|
||||
|
||||
self[LENGTH] -= hit.length
|
||||
self[CACHE].delete(hit.key)
|
||||
self[LRU_LIST].removeNode(node)
|
||||
}
|
||||
}
|
||||
|
||||
class Entry {
|
||||
constructor (key, value, length, now, maxAge) {
|
||||
this.key = key
|
||||
this.value = value
|
||||
this.length = length
|
||||
this.now = now
|
||||
this.maxAge = maxAge || 0
|
||||
}
|
||||
}
|
||||
|
||||
const forEachStep = (self, fn, node, thisp) => {
|
||||
let hit = node.value
|
||||
if (isStale(self, hit)) {
|
||||
del(self, node)
|
||||
if (!self[ALLOW_STALE])
|
||||
hit = undefined
|
||||
}
|
||||
if (hit)
|
||||
fn.call(thisp, hit.value, hit.key, self)
|
||||
}
|
||||
|
||||
module.exports = LRUCache
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"_from": "lru-cache@^5.1.1",
|
||||
"_id": "lru-cache@5.1.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"_location": "/cacache/lru-cache",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "lru-cache@^5.1.1",
|
||||
"name": "lru-cache",
|
||||
"escapedName": "lru-cache",
|
||||
"rawSpec": "^5.1.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^5.1.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/cacache"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"_shasum": "1da27e6710271947695daf6848e847f01d84b920",
|
||||
"_spec": "lru-cache@^5.1.1",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\cacache",
|
||||
"author": {
|
||||
"name": "Isaac Z. Schlueter",
|
||||
"email": "i@izs.me"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/isaacs/node-lru-cache/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"yallist": "^3.0.2"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "A cache object that deletes the least-recently-used items.",
|
||||
"devDependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"tap": "^12.1.0"
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"homepage": "https://github.com/isaacs/node-lru-cache#readme",
|
||||
"keywords": [
|
||||
"mru",
|
||||
"lru",
|
||||
"cache"
|
||||
],
|
||||
"license": "ISC",
|
||||
"main": "index.js",
|
||||
"name": "lru-cache",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/isaacs/node-lru-cache.git"
|
||||
},
|
||||
"scripts": {
|
||||
"coveragerport": "tap --coverage-report=html",
|
||||
"postpublish": "git push origin --all; git push origin --tags",
|
||||
"postversion": "npm publish",
|
||||
"preversion": "npm test",
|
||||
"snap": "TAP_SNAPSHOT=1 tap test/*.js -J",
|
||||
"test": "tap test/*.js --100 -J"
|
||||
},
|
||||
"version": "5.1.1"
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
<a name="4.0.0"></a>
|
||||
# [4.0.0](https://github.com/yargs/y18n/compare/v3.2.1...v4.0.0) (2017-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow support for falsy values like 0 in tagged literal ([#45](https://github.com/yargs/y18n/issues/45)) ([c926123](https://github.com/yargs/y18n/commit/c926123))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **__:** added tagged template literal support ([#44](https://github.com/yargs/y18n/issues/44)) ([0598daf](https://github.com/yargs/y18n/commit/0598daf))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **__:** dropping Node 0.10/Node 0.12 support
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2015, Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# y18n
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Coverage Status][coveralls-image]][coveralls-url]
|
||||
[![NPM version][npm-image]][npm-url]
|
||||
[![js-standard-style][standard-image]][standard-url]
|
||||
[](https://conventionalcommits.org)
|
||||
|
||||
The bare-bones internationalization library used by yargs.
|
||||
|
||||
Inspired by [i18n](https://www.npmjs.com/package/i18n).
|
||||
|
||||
## Examples
|
||||
|
||||
_simple string translation:_
|
||||
|
||||
```js
|
||||
var __ = require('y18n').__
|
||||
|
||||
console.log(__('my awesome string %s', 'foo'))
|
||||
```
|
||||
|
||||
output:
|
||||
|
||||
`my awesome string foo`
|
||||
|
||||
_using tagged template literals_
|
||||
|
||||
```js
|
||||
var __ = require('y18n').__
|
||||
var str = 'foo'
|
||||
|
||||
console.log(__`my awesome string ${str}`)
|
||||
```
|
||||
|
||||
output:
|
||||
|
||||
`my awesome string foo`
|
||||
|
||||
_pluralization support:_
|
||||
|
||||
```js
|
||||
var __n = require('y18n').__n
|
||||
|
||||
console.log(__n('one fish %s', '%d fishes %s', 2, 'foo'))
|
||||
```
|
||||
|
||||
output:
|
||||
|
||||
`2 fishes foo`
|
||||
|
||||
## JSON Language Files
|
||||
|
||||
The JSON language files should be stored in a `./locales` folder.
|
||||
File names correspond to locales, e.g., `en.json`, `pirate.json`.
|
||||
|
||||
When strings are observed for the first time they will be
|
||||
added to the JSON file corresponding to the current locale.
|
||||
|
||||
## Methods
|
||||
|
||||
### require('y18n')(config)
|
||||
|
||||
Create an instance of y18n with the config provided, options include:
|
||||
|
||||
* `directory`: the locale directory, default `./locales`.
|
||||
* `updateFiles`: should newly observed strings be updated in file, default `true`.
|
||||
* `locale`: what locale should be used.
|
||||
* `fallbackToLanguage`: should fallback to a language-only file (e.g. `en.json`)
|
||||
be allowed if a file matching the locale does not exist (e.g. `en_US.json`),
|
||||
default `true`.
|
||||
|
||||
### y18n.\_\_(str, arg, arg, arg)
|
||||
|
||||
Print a localized string, `%s` will be replaced with `arg`s.
|
||||
|
||||
This function can also be used as a tag for a template literal. You can use it
|
||||
like this: <code>__`hello ${'world'}`</code>. This will be equivalent to
|
||||
`__('hello %s', 'world')`.
|
||||
|
||||
### y18n.\_\_n(singularString, pluralString, count, arg, arg, arg)
|
||||
|
||||
Print a localized string with appropriate pluralization. If `%d` is provided
|
||||
in the string, the `count` will replace this placeholder.
|
||||
|
||||
### y18n.setLocale(str)
|
||||
|
||||
Set the current locale being used.
|
||||
|
||||
### y18n.getLocale()
|
||||
|
||||
What locale is currently being used?
|
||||
|
||||
### y18n.updateLocale(obj)
|
||||
|
||||
Update the current locale with the key value pairs in `obj`.
|
||||
|
||||
## License
|
||||
|
||||
ISC
|
||||
|
||||
[travis-url]: https://travis-ci.org/yargs/y18n
|
||||
[travis-image]: https://img.shields.io/travis/yargs/y18n.svg
|
||||
[coveralls-url]: https://coveralls.io/github/yargs/y18n
|
||||
[coveralls-image]: https://img.shields.io/coveralls/yargs/y18n.svg
|
||||
[npm-url]: https://npmjs.org/package/y18n
|
||||
[npm-image]: https://img.shields.io/npm/v/y18n.svg
|
||||
[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg
|
||||
[standard-url]: https://github.com/feross/standard
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var util = require('util')
|
||||
|
||||
function Y18N (opts) {
|
||||
// configurable options.
|
||||
opts = opts || {}
|
||||
this.directory = opts.directory || './locales'
|
||||
this.updateFiles = typeof opts.updateFiles === 'boolean' ? opts.updateFiles : true
|
||||
this.locale = opts.locale || 'en'
|
||||
this.fallbackToLanguage = typeof opts.fallbackToLanguage === 'boolean' ? opts.fallbackToLanguage : true
|
||||
|
||||
// internal stuff.
|
||||
this.cache = {}
|
||||
this.writeQueue = []
|
||||
}
|
||||
|
||||
Y18N.prototype.__ = function () {
|
||||
if (typeof arguments[0] !== 'string') {
|
||||
return this._taggedLiteral.apply(this, arguments)
|
||||
}
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
var str = args.shift()
|
||||
var cb = function () {} // start with noop.
|
||||
|
||||
if (typeof args[args.length - 1] === 'function') cb = args.pop()
|
||||
cb = cb || function () {} // noop.
|
||||
|
||||
if (!this.cache[this.locale]) this._readLocaleFile()
|
||||
|
||||
// we've observed a new string, update the language file.
|
||||
if (!this.cache[this.locale][str] && this.updateFiles) {
|
||||
this.cache[this.locale][str] = str
|
||||
|
||||
// include the current directory and locale,
|
||||
// since these values could change before the
|
||||
// write is performed.
|
||||
this._enqueueWrite([this.directory, this.locale, cb])
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
|
||||
return util.format.apply(util, [this.cache[this.locale][str] || str].concat(args))
|
||||
}
|
||||
|
||||
Y18N.prototype._taggedLiteral = function (parts) {
|
||||
var args = arguments
|
||||
var str = ''
|
||||
parts.forEach(function (part, i) {
|
||||
var arg = args[i + 1]
|
||||
str += part
|
||||
if (typeof arg !== 'undefined') {
|
||||
str += '%s'
|
||||
}
|
||||
})
|
||||
return this.__.apply(null, [str].concat([].slice.call(arguments, 1)))
|
||||
}
|
||||
|
||||
Y18N.prototype._enqueueWrite = function (work) {
|
||||
this.writeQueue.push(work)
|
||||
if (this.writeQueue.length === 1) this._processWriteQueue()
|
||||
}
|
||||
|
||||
Y18N.prototype._processWriteQueue = function () {
|
||||
var _this = this
|
||||
var work = this.writeQueue[0]
|
||||
|
||||
// destructure the enqueued work.
|
||||
var directory = work[0]
|
||||
var locale = work[1]
|
||||
var cb = work[2]
|
||||
|
||||
var languageFile = this._resolveLocaleFile(directory, locale)
|
||||
var serializedLocale = JSON.stringify(this.cache[locale], null, 2)
|
||||
|
||||
fs.writeFile(languageFile, serializedLocale, 'utf-8', function (err) {
|
||||
_this.writeQueue.shift()
|
||||
if (_this.writeQueue.length > 0) _this._processWriteQueue()
|
||||
cb(err)
|
||||
})
|
||||
}
|
||||
|
||||
Y18N.prototype._readLocaleFile = function () {
|
||||
var localeLookup = {}
|
||||
var languageFile = this._resolveLocaleFile(this.directory, this.locale)
|
||||
|
||||
try {
|
||||
localeLookup = JSON.parse(fs.readFileSync(languageFile, 'utf-8'))
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
err.message = 'syntax error in ' + languageFile
|
||||
}
|
||||
|
||||
if (err.code === 'ENOENT') localeLookup = {}
|
||||
else throw err
|
||||
}
|
||||
|
||||
this.cache[this.locale] = localeLookup
|
||||
}
|
||||
|
||||
Y18N.prototype._resolveLocaleFile = function (directory, locale) {
|
||||
var file = path.resolve(directory, './', locale + '.json')
|
||||
if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) {
|
||||
// attempt fallback to language only
|
||||
var languageFile = path.resolve(directory, './', locale.split('_')[0] + '.json')
|
||||
if (this._fileExistsSync(languageFile)) file = languageFile
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
// this only exists because fs.existsSync() "will be deprecated"
|
||||
// see https://nodejs.org/api/fs.html#fs_fs_existssync_path
|
||||
Y18N.prototype._fileExistsSync = function (file) {
|
||||
try {
|
||||
return fs.statSync(file).isFile()
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Y18N.prototype.__n = function () {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
var singular = args.shift()
|
||||
var plural = args.shift()
|
||||
var quantity = args.shift()
|
||||
|
||||
var cb = function () {} // start with noop.
|
||||
if (typeof args[args.length - 1] === 'function') cb = args.pop()
|
||||
|
||||
if (!this.cache[this.locale]) this._readLocaleFile()
|
||||
|
||||
var str = quantity === 1 ? singular : plural
|
||||
if (this.cache[this.locale][singular]) {
|
||||
str = this.cache[this.locale][singular][quantity === 1 ? 'one' : 'other']
|
||||
}
|
||||
|
||||
// we've observed a new string, update the language file.
|
||||
if (!this.cache[this.locale][singular] && this.updateFiles) {
|
||||
this.cache[this.locale][singular] = {
|
||||
one: singular,
|
||||
other: plural
|
||||
}
|
||||
|
||||
// include the current directory and locale,
|
||||
// since these values could change before the
|
||||
// write is performed.
|
||||
this._enqueueWrite([this.directory, this.locale, cb])
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
|
||||
// if a %d placeholder is provided, add quantity
|
||||
// to the arguments expanded by util.format.
|
||||
var values = [str]
|
||||
if (~str.indexOf('%d')) values.push(quantity)
|
||||
|
||||
return util.format.apply(util, values.concat(args))
|
||||
}
|
||||
|
||||
Y18N.prototype.setLocale = function (locale) {
|
||||
this.locale = locale
|
||||
}
|
||||
|
||||
Y18N.prototype.getLocale = function () {
|
||||
return this.locale
|
||||
}
|
||||
|
||||
Y18N.prototype.updateLocale = function (obj) {
|
||||
if (!this.cache[this.locale]) this._readLocaleFile()
|
||||
|
||||
for (var key in obj) {
|
||||
this.cache[this.locale][key] = obj[key]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function (opts) {
|
||||
var y18n = new Y18N(opts)
|
||||
|
||||
// bind all functions to y18n, so that
|
||||
// they can be used in isolation.
|
||||
for (var key in y18n) {
|
||||
if (typeof y18n[key] === 'function') {
|
||||
y18n[key] = y18n[key].bind(y18n)
|
||||
}
|
||||
}
|
||||
|
||||
return y18n
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"_from": "y18n@^4.0.0",
|
||||
"_id": "y18n@4.0.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"_location": "/cacache/y18n",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "y18n@^4.0.0",
|
||||
"name": "y18n",
|
||||
"escapedName": "y18n",
|
||||
"rawSpec": "^4.0.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^4.0.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/cacache"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"_shasum": "95ef94f85ecc81d007c264e190a120f0a3c8566b",
|
||||
"_spec": "y18n@^4.0.0",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\cacache",
|
||||
"author": {
|
||||
"name": "Ben Coe",
|
||||
"email": "ben@npmjs.com"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/yargs/y18n/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"deprecated": false,
|
||||
"description": "the bare-bones internationalization library used by yargs",
|
||||
"devDependencies": {
|
||||
"chai": "^4.0.1",
|
||||
"coveralls": "^3.0.0",
|
||||
"mocha": "^4.0.1",
|
||||
"nyc": "^11.0.1",
|
||||
"rimraf": "^2.5.0",
|
||||
"standard": "^10.0.0-beta.0",
|
||||
"standard-version": "^4.2.0"
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"homepage": "https://github.com/yargs/y18n",
|
||||
"keywords": [
|
||||
"i18n",
|
||||
"internationalization",
|
||||
"yargs"
|
||||
],
|
||||
"license": "ISC",
|
||||
"main": "index.js",
|
||||
"name": "y18n",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/yargs/y18n.git"
|
||||
},
|
||||
"scripts": {
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"pretest": "standard",
|
||||
"release": "standard-version",
|
||||
"test": "nyc mocha"
|
||||
},
|
||||
"version": "4.0.0"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
# yallist
|
||||
|
||||
Yet Another Linked List
|
||||
|
||||
There are many doubly-linked list implementations like it, but this
|
||||
one is mine.
|
||||
|
||||
For when an array would be too big, and a Map can't be iterated in
|
||||
reverse order.
|
||||
|
||||
|
||||
[](https://travis-ci.org/isaacs/yallist) [](https://coveralls.io/github/isaacs/yallist)
|
||||
|
||||
## basic usage
|
||||
|
||||
```javascript
|
||||
var yallist = require('yallist')
|
||||
var myList = yallist.create([1, 2, 3])
|
||||
myList.push('foo')
|
||||
myList.unshift('bar')
|
||||
// of course pop() and shift() are there, too
|
||||
console.log(myList.toArray()) // ['bar', 1, 2, 3, 'foo']
|
||||
myList.forEach(function (k) {
|
||||
// walk the list head to tail
|
||||
})
|
||||
myList.forEachReverse(function (k, index, list) {
|
||||
// walk the list tail to head
|
||||
})
|
||||
var myDoubledList = myList.map(function (k) {
|
||||
return k + k
|
||||
})
|
||||
// now myDoubledList contains ['barbar', 2, 4, 6, 'foofoo']
|
||||
// mapReverse is also a thing
|
||||
var myDoubledListReverse = myList.mapReverse(function (k) {
|
||||
return k + k
|
||||
}) // ['foofoo', 6, 4, 2, 'barbar']
|
||||
|
||||
var reduced = myList.reduce(function (set, entry) {
|
||||
set += entry
|
||||
return set
|
||||
}, 'start')
|
||||
console.log(reduced) // 'startfoo123bar'
|
||||
```
|
||||
|
||||
## api
|
||||
|
||||
The whole API is considered "public".
|
||||
|
||||
Functions with the same name as an Array method work more or less the
|
||||
same way.
|
||||
|
||||
There's reverse versions of most things because that's the point.
|
||||
|
||||
### Yallist
|
||||
|
||||
Default export, the class that holds and manages a list.
|
||||
|
||||
Call it with either a forEach-able (like an array) or a set of
|
||||
arguments, to initialize the list.
|
||||
|
||||
The Array-ish methods all act like you'd expect. No magic length,
|
||||
though, so if you change that it won't automatically prune or add
|
||||
empty spots.
|
||||
|
||||
### Yallist.create(..)
|
||||
|
||||
Alias for Yallist function. Some people like factories.
|
||||
|
||||
#### yallist.head
|
||||
|
||||
The first node in the list
|
||||
|
||||
#### yallist.tail
|
||||
|
||||
The last node in the list
|
||||
|
||||
#### yallist.length
|
||||
|
||||
The number of nodes in the list. (Change this at your peril. It is
|
||||
not magic like Array length.)
|
||||
|
||||
#### yallist.toArray()
|
||||
|
||||
Convert the list to an array.
|
||||
|
||||
#### yallist.forEach(fn, [thisp])
|
||||
|
||||
Call a function on each item in the list.
|
||||
|
||||
#### yallist.forEachReverse(fn, [thisp])
|
||||
|
||||
Call a function on each item in the list, in reverse order.
|
||||
|
||||
#### yallist.get(n)
|
||||
|
||||
Get the data at position `n` in the list. If you use this a lot,
|
||||
probably better off just using an Array.
|
||||
|
||||
#### yallist.getReverse(n)
|
||||
|
||||
Get the data at position `n`, counting from the tail.
|
||||
|
||||
#### yallist.map(fn, thisp)
|
||||
|
||||
Create a new Yallist with the result of calling the function on each
|
||||
item.
|
||||
|
||||
#### yallist.mapReverse(fn, thisp)
|
||||
|
||||
Same as `map`, but in reverse.
|
||||
|
||||
#### yallist.pop()
|
||||
|
||||
Get the data from the list tail, and remove the tail from the list.
|
||||
|
||||
#### yallist.push(item, ...)
|
||||
|
||||
Insert one or more items to the tail of the list.
|
||||
|
||||
#### yallist.reduce(fn, initialValue)
|
||||
|
||||
Like Array.reduce.
|
||||
|
||||
#### yallist.reduceReverse
|
||||
|
||||
Like Array.reduce, but in reverse.
|
||||
|
||||
#### yallist.reverse
|
||||
|
||||
Reverse the list in place.
|
||||
|
||||
#### yallist.shift()
|
||||
|
||||
Get the data from the list head, and remove the head from the list.
|
||||
|
||||
#### yallist.slice([from], [to])
|
||||
|
||||
Just like Array.slice, but returns a new Yallist.
|
||||
|
||||
#### yallist.sliceReverse([from], [to])
|
||||
|
||||
Just like yallist.slice, but the result is returned in reverse.
|
||||
|
||||
#### yallist.toArray()
|
||||
|
||||
Create an array representation of the list.
|
||||
|
||||
#### yallist.toArrayReverse()
|
||||
|
||||
Create a reversed array representation of the list.
|
||||
|
||||
#### yallist.unshift(item, ...)
|
||||
|
||||
Insert one or more items to the head of the list.
|
||||
|
||||
#### yallist.unshiftNode(node)
|
||||
|
||||
Move a Node object to the front of the list. (That is, pull it out of
|
||||
wherever it lives, and make it the new head.)
|
||||
|
||||
If the node belongs to a different list, then that list will remove it
|
||||
first.
|
||||
|
||||
#### yallist.pushNode(node)
|
||||
|
||||
Move a Node object to the end of the list. (That is, pull it out of
|
||||
wherever it lives, and make it the new tail.)
|
||||
|
||||
If the node belongs to a list already, then that list will remove it
|
||||
first.
|
||||
|
||||
#### yallist.removeNode(node)
|
||||
|
||||
Remove a node from the list, preserving referential integrity of head
|
||||
and tail and other nodes.
|
||||
|
||||
Will throw an error if you try to have a list remove a node that
|
||||
doesn't belong to it.
|
||||
|
||||
### Yallist.Node
|
||||
|
||||
The class that holds the data and is actually the list.
|
||||
|
||||
Call with `var n = new Node(value, previousNode, nextNode)`
|
||||
|
||||
Note that if you do direct operations on Nodes themselves, it's very
|
||||
easy to get into weird states where the list is broken. Be careful :)
|
||||
|
||||
#### node.next
|
||||
|
||||
The next node in the list.
|
||||
|
||||
#### node.prev
|
||||
|
||||
The previous node in the list.
|
||||
|
||||
#### node.value
|
||||
|
||||
The data the node contains.
|
||||
|
||||
#### node.list
|
||||
|
||||
The list to which this node belongs. (Null if it does not belong to
|
||||
any list.)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
module.exports = function (Yallist) {
|
||||
Yallist.prototype[Symbol.iterator] = function* () {
|
||||
for (let walker = this.head; walker; walker = walker.next) {
|
||||
yield walker.value
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"_from": "yallist@^3.0.2",
|
||||
"_id": "yallist@3.1.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"_location": "/cacache/yallist",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "yallist@^3.0.2",
|
||||
"name": "yallist",
|
||||
"escapedName": "yallist",
|
||||
"rawSpec": "^3.0.2",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^3.0.2"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/cacache/lru-cache"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"_shasum": "dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd",
|
||||
"_spec": "yallist@^3.0.2",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\cacache\\node_modules\\lru-cache",
|
||||
"author": {
|
||||
"name": "Isaac Z. Schlueter",
|
||||
"email": "i@izs.me",
|
||||
"url": "http://blog.izs.me/"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/isaacs/yallist/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {},
|
||||
"deprecated": false,
|
||||
"description": "Yet Another Linked List",
|
||||
"devDependencies": {
|
||||
"tap": "^12.1.0"
|
||||
},
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"files": [
|
||||
"yallist.js",
|
||||
"iterator.js"
|
||||
],
|
||||
"homepage": "https://github.com/isaacs/yallist#readme",
|
||||
"license": "ISC",
|
||||
"main": "yallist.js",
|
||||
"name": "yallist",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/isaacs/yallist.git"
|
||||
},
|
||||
"scripts": {
|
||||
"postpublish": "git push origin --all; git push origin --tags",
|
||||
"postversion": "npm publish",
|
||||
"preversion": "npm test",
|
||||
"test": "tap test/*.js --100"
|
||||
},
|
||||
"version": "3.1.1"
|
||||
}
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
'use strict'
|
||||
module.exports = Yallist
|
||||
|
||||
Yallist.Node = Node
|
||||
Yallist.create = Yallist
|
||||
|
||||
function Yallist (list) {
|
||||
var self = this
|
||||
if (!(self instanceof Yallist)) {
|
||||
self = new Yallist()
|
||||
}
|
||||
|
||||
self.tail = null
|
||||
self.head = null
|
||||
self.length = 0
|
||||
|
||||
if (list && typeof list.forEach === 'function') {
|
||||
list.forEach(function (item) {
|
||||
self.push(item)
|
||||
})
|
||||
} else if (arguments.length > 0) {
|
||||
for (var i = 0, l = arguments.length; i < l; i++) {
|
||||
self.push(arguments[i])
|
||||
}
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
Yallist.prototype.removeNode = function (node) {
|
||||
if (node.list !== this) {
|
||||
throw new Error('removing node which does not belong to this list')
|
||||
}
|
||||
|
||||
var next = node.next
|
||||
var prev = node.prev
|
||||
|
||||
if (next) {
|
||||
next.prev = prev
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
prev.next = next
|
||||
}
|
||||
|
||||
if (node === this.head) {
|
||||
this.head = next
|
||||
}
|
||||
if (node === this.tail) {
|
||||
this.tail = prev
|
||||
}
|
||||
|
||||
node.list.length--
|
||||
node.next = null
|
||||
node.prev = null
|
||||
node.list = null
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
Yallist.prototype.unshiftNode = function (node) {
|
||||
if (node === this.head) {
|
||||
return
|
||||
}
|
||||
|
||||
if (node.list) {
|
||||
node.list.removeNode(node)
|
||||
}
|
||||
|
||||
var head = this.head
|
||||
node.list = this
|
||||
node.next = head
|
||||
if (head) {
|
||||
head.prev = node
|
||||
}
|
||||
|
||||
this.head = node
|
||||
if (!this.tail) {
|
||||
this.tail = node
|
||||
}
|
||||
this.length++
|
||||
}
|
||||
|
||||
Yallist.prototype.pushNode = function (node) {
|
||||
if (node === this.tail) {
|
||||
return
|
||||
}
|
||||
|
||||
if (node.list) {
|
||||
node.list.removeNode(node)
|
||||
}
|
||||
|
||||
var tail = this.tail
|
||||
node.list = this
|
||||
node.prev = tail
|
||||
if (tail) {
|
||||
tail.next = node
|
||||
}
|
||||
|
||||
this.tail = node
|
||||
if (!this.head) {
|
||||
this.head = node
|
||||
}
|
||||
this.length++
|
||||
}
|
||||
|
||||
Yallist.prototype.push = function () {
|
||||
for (var i = 0, l = arguments.length; i < l; i++) {
|
||||
push(this, arguments[i])
|
||||
}
|
||||
return this.length
|
||||
}
|
||||
|
||||
Yallist.prototype.unshift = function () {
|
||||
for (var i = 0, l = arguments.length; i < l; i++) {
|
||||
unshift(this, arguments[i])
|
||||
}
|
||||
return this.length
|
||||
}
|
||||
|
||||
Yallist.prototype.pop = function () {
|
||||
if (!this.tail) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
var res = this.tail.value
|
||||
this.tail = this.tail.prev
|
||||
if (this.tail) {
|
||||
this.tail.next = null
|
||||
} else {
|
||||
this.head = null
|
||||
}
|
||||
this.length--
|
||||
return res
|
||||
}
|
||||
|
||||
Yallist.prototype.shift = function () {
|
||||
if (!this.head) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
var res = this.head.value
|
||||
this.head = this.head.next
|
||||
if (this.head) {
|
||||
this.head.prev = null
|
||||
} else {
|
||||
this.tail = null
|
||||
}
|
||||
this.length--
|
||||
return res
|
||||
}
|
||||
|
||||
Yallist.prototype.forEach = function (fn, thisp) {
|
||||
thisp = thisp || this
|
||||
for (var walker = this.head, i = 0; walker !== null; i++) {
|
||||
fn.call(thisp, walker.value, i, this)
|
||||
walker = walker.next
|
||||
}
|
||||
}
|
||||
|
||||
Yallist.prototype.forEachReverse = function (fn, thisp) {
|
||||
thisp = thisp || this
|
||||
for (var walker = this.tail, i = this.length - 1; walker !== null; i--) {
|
||||
fn.call(thisp, walker.value, i, this)
|
||||
walker = walker.prev
|
||||
}
|
||||
}
|
||||
|
||||
Yallist.prototype.get = function (n) {
|
||||
for (var i = 0, walker = this.head; walker !== null && i < n; i++) {
|
||||
// abort out of the list early if we hit a cycle
|
||||
walker = walker.next
|
||||
}
|
||||
if (i === n && walker !== null) {
|
||||
return walker.value
|
||||
}
|
||||
}
|
||||
|
||||
Yallist.prototype.getReverse = function (n) {
|
||||
for (var i = 0, walker = this.tail; walker !== null && i < n; i++) {
|
||||
// abort out of the list early if we hit a cycle
|
||||
walker = walker.prev
|
||||
}
|
||||
if (i === n && walker !== null) {
|
||||
return walker.value
|
||||
}
|
||||
}
|
||||
|
||||
Yallist.prototype.map = function (fn, thisp) {
|
||||
thisp = thisp || this
|
||||
var res = new Yallist()
|
||||
for (var walker = this.head; walker !== null;) {
|
||||
res.push(fn.call(thisp, walker.value, this))
|
||||
walker = walker.next
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
Yallist.prototype.mapReverse = function (fn, thisp) {
|
||||
thisp = thisp || this
|
||||
var res = new Yallist()
|
||||
for (var walker = this.tail; walker !== null;) {
|
||||
res.push(fn.call(thisp, walker.value, this))
|
||||
walker = walker.prev
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
Yallist.prototype.reduce = function (fn, initial) {
|
||||
var acc
|
||||
var walker = this.head
|
||||
if (arguments.length > 1) {
|
||||
acc = initial
|
||||
} else if (this.head) {
|
||||
walker = this.head.next
|
||||
acc = this.head.value
|
||||
} else {
|
||||
throw new TypeError('Reduce of empty list with no initial value')
|
||||
}
|
||||
|
||||
for (var i = 0; walker !== null; i++) {
|
||||
acc = fn(acc, walker.value, i)
|
||||
walker = walker.next
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
Yallist.prototype.reduceReverse = function (fn, initial) {
|
||||
var acc
|
||||
var walker = this.tail
|
||||
if (arguments.length > 1) {
|
||||
acc = initial
|
||||
} else if (this.tail) {
|
||||
walker = this.tail.prev
|
||||
acc = this.tail.value
|
||||
} else {
|
||||
throw new TypeError('Reduce of empty list with no initial value')
|
||||
}
|
||||
|
||||
for (var i = this.length - 1; walker !== null; i--) {
|
||||
acc = fn(acc, walker.value, i)
|
||||
walker = walker.prev
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
Yallist.prototype.toArray = function () {
|
||||
var arr = new Array(this.length)
|
||||
for (var i = 0, walker = this.head; walker !== null; i++) {
|
||||
arr[i] = walker.value
|
||||
walker = walker.next
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
Yallist.prototype.toArrayReverse = function () {
|
||||
var arr = new Array(this.length)
|
||||
for (var i = 0, walker = this.tail; walker !== null; i++) {
|
||||
arr[i] = walker.value
|
||||
walker = walker.prev
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
Yallist.prototype.slice = function (from, to) {
|
||||
to = to || this.length
|
||||
if (to < 0) {
|
||||
to += this.length
|
||||
}
|
||||
from = from || 0
|
||||
if (from < 0) {
|
||||
from += this.length
|
||||
}
|
||||
var ret = new Yallist()
|
||||
if (to < from || to < 0) {
|
||||
return ret
|
||||
}
|
||||
if (from < 0) {
|
||||
from = 0
|
||||
}
|
||||
if (to > this.length) {
|
||||
to = this.length
|
||||
}
|
||||
for (var i = 0, walker = this.head; walker !== null && i < from; i++) {
|
||||
walker = walker.next
|
||||
}
|
||||
for (; walker !== null && i < to; i++, walker = walker.next) {
|
||||
ret.push(walker.value)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
Yallist.prototype.sliceReverse = function (from, to) {
|
||||
to = to || this.length
|
||||
if (to < 0) {
|
||||
to += this.length
|
||||
}
|
||||
from = from || 0
|
||||
if (from < 0) {
|
||||
from += this.length
|
||||
}
|
||||
var ret = new Yallist()
|
||||
if (to < from || to < 0) {
|
||||
return ret
|
||||
}
|
||||
if (from < 0) {
|
||||
from = 0
|
||||
}
|
||||
if (to > this.length) {
|
||||
to = this.length
|
||||
}
|
||||
for (var i = this.length, walker = this.tail; walker !== null && i > to; i--) {
|
||||
walker = walker.prev
|
||||
}
|
||||
for (; walker !== null && i > from; i--, walker = walker.prev) {
|
||||
ret.push(walker.value)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
Yallist.prototype.splice = function (start, deleteCount /*, ...nodes */) {
|
||||
if (start > this.length) {
|
||||
start = this.length - 1
|
||||
}
|
||||
if (start < 0) {
|
||||
start = this.length + start;
|
||||
}
|
||||
|
||||
for (var i = 0, walker = this.head; walker !== null && i < start; i++) {
|
||||
walker = walker.next
|
||||
}
|
||||
|
||||
var ret = []
|
||||
for (var i = 0; walker && i < deleteCount; i++) {
|
||||
ret.push(walker.value)
|
||||
walker = this.removeNode(walker)
|
||||
}
|
||||
if (walker === null) {
|
||||
walker = this.tail
|
||||
}
|
||||
|
||||
if (walker !== this.head && walker !== this.tail) {
|
||||
walker = walker.prev
|
||||
}
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
walker = insert(this, walker, arguments[i])
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Yallist.prototype.reverse = function () {
|
||||
var head = this.head
|
||||
var tail = this.tail
|
||||
for (var walker = head; walker !== null; walker = walker.prev) {
|
||||
var p = walker.prev
|
||||
walker.prev = walker.next
|
||||
walker.next = p
|
||||
}
|
||||
this.head = tail
|
||||
this.tail = head
|
||||
return this
|
||||
}
|
||||
|
||||
function insert (self, node, value) {
|
||||
var inserted = node === self.head ?
|
||||
new Node(value, null, node, self) :
|
||||
new Node(value, node, node.next, self)
|
||||
|
||||
if (inserted.next === null) {
|
||||
self.tail = inserted
|
||||
}
|
||||
if (inserted.prev === null) {
|
||||
self.head = inserted
|
||||
}
|
||||
|
||||
self.length++
|
||||
|
||||
return inserted
|
||||
}
|
||||
|
||||
function push (self, item) {
|
||||
self.tail = new Node(item, self.tail, null, self)
|
||||
if (!self.head) {
|
||||
self.head = self.tail
|
||||
}
|
||||
self.length++
|
||||
}
|
||||
|
||||
function unshift (self, item) {
|
||||
self.head = new Node(item, null, self.head, self)
|
||||
if (!self.tail) {
|
||||
self.tail = self.head
|
||||
}
|
||||
self.length++
|
||||
}
|
||||
|
||||
function Node (value, prev, next, list) {
|
||||
if (!(this instanceof Node)) {
|
||||
return new Node(value, prev, next, list)
|
||||
}
|
||||
|
||||
this.list = list
|
||||
this.value = value
|
||||
|
||||
if (prev) {
|
||||
prev.next = this
|
||||
this.prev = prev
|
||||
} else {
|
||||
this.prev = null
|
||||
}
|
||||
|
||||
if (next) {
|
||||
next.prev = this
|
||||
this.next = next
|
||||
} else {
|
||||
this.next = null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// add if support for Symbol.iterator is present
|
||||
require('./iterator.js')(Yallist)
|
||||
} catch (er) {}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"_from": "cacache@^12.0.2",
|
||||
"_id": "cacache@12.0.4",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
|
||||
"_location": "/cacache",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "cacache@^12.0.2",
|
||||
"name": "cacache",
|
||||
"escapedName": "cacache",
|
||||
"rawSpec": "^12.0.2",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^12.0.2"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/terser-webpack-plugin",
|
||||
"/uglifyjs-webpack-plugin"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
|
||||
"_shasum": "668bcbd105aeb5f1d92fe25570ec9525c8faa40c",
|
||||
"_spec": "cacache@^12.0.2",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\uglifyjs-webpack-plugin",
|
||||
"author": {
|
||||
"name": "Kat Marchán",
|
||||
"email": "kzm@sykosomatic.org"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/npm/cacache/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"cache-version": {
|
||||
"content": "2",
|
||||
"index": "5"
|
||||
},
|
||||
"config": {
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"node_modules/**",
|
||||
"test/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Charlotte Spencer",
|
||||
"email": "charlottelaspencer@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Rebecca Turner",
|
||||
"email": "me@re-becca.org"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"bluebird": "^3.5.5",
|
||||
"chownr": "^1.1.1",
|
||||
"figgy-pudding": "^3.5.1",
|
||||
"glob": "^7.1.4",
|
||||
"graceful-fs": "^4.1.15",
|
||||
"infer-owner": "^1.0.3",
|
||||
"lru-cache": "^5.1.1",
|
||||
"mississippi": "^3.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"move-concurrently": "^1.0.1",
|
||||
"promise-inflight": "^1.0.1",
|
||||
"rimraf": "^2.6.3",
|
||||
"ssri": "^6.0.1",
|
||||
"unique-filename": "^1.1.1",
|
||||
"y18n": "^4.0.0"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Fast, fault-tolerant, cross-platform, disk-based, data-agnostic, content-addressable cache.",
|
||||
"devDependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"chalk": "^2.4.2",
|
||||
"cross-env": "^5.1.4",
|
||||
"require-inject": "^1.4.4",
|
||||
"standard": "^12.0.1",
|
||||
"standard-version": "^6.0.1",
|
||||
"tacks": "^1.3.0",
|
||||
"tap": "^12.7.0"
|
||||
},
|
||||
"files": [
|
||||
"*.js",
|
||||
"lib",
|
||||
"locales"
|
||||
],
|
||||
"homepage": "https://github.com/npm/cacache#readme",
|
||||
"keywords": [
|
||||
"cache",
|
||||
"caching",
|
||||
"content-addressable",
|
||||
"sri",
|
||||
"sri hash",
|
||||
"subresource integrity",
|
||||
"cache",
|
||||
"storage",
|
||||
"store",
|
||||
"file store",
|
||||
"filesystem",
|
||||
"disk cache",
|
||||
"disk storage"
|
||||
],
|
||||
"license": "ISC",
|
||||
"main": "index.js",
|
||||
"name": "cacache",
|
||||
"publishConfig": {
|
||||
"tag": "legacy"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/npm/cacache.git"
|
||||
},
|
||||
"scripts": {
|
||||
"benchmarks": "node test/benchmarks",
|
||||
"postrelease": "npm publish && git push --follow-tags",
|
||||
"prerelease": "npm t",
|
||||
"pretest": "standard",
|
||||
"release": "standard-version -s",
|
||||
"test": "cross-env CACACHE_UPDATE_LOCALE_FILES=true tap --coverage --nyc-arg=--all -J test/*.js",
|
||||
"test-docker": "docker run -it --rm --name pacotest -v \"$PWD\":/tmp -w /tmp node:latest npm test"
|
||||
},
|
||||
"version": "12.0.4"
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
'use strict'
|
||||
|
||||
const figgyPudding = require('figgy-pudding')
|
||||
const index = require('./lib/entry-index')
|
||||
const memo = require('./lib/memoization')
|
||||
const write = require('./lib/content/write')
|
||||
const to = require('mississippi').to
|
||||
|
||||
const PutOpts = figgyPudding({
|
||||
algorithms: {
|
||||
default: ['sha512']
|
||||
},
|
||||
integrity: {},
|
||||
memoize: {},
|
||||
metadata: {},
|
||||
pickAlgorithm: {},
|
||||
size: {},
|
||||
tmpPrefix: {},
|
||||
single: {},
|
||||
sep: {},
|
||||
error: {},
|
||||
strict: {}
|
||||
})
|
||||
|
||||
module.exports = putData
|
||||
function putData (cache, key, data, opts) {
|
||||
opts = PutOpts(opts)
|
||||
return write(cache, data, opts).then(res => {
|
||||
return index.insert(
|
||||
cache, key, res.integrity, opts.concat({ size: res.size })
|
||||
).then(entry => {
|
||||
if (opts.memoize) {
|
||||
memo.put(cache, entry, data, opts)
|
||||
}
|
||||
return res.integrity
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.stream = putStream
|
||||
function putStream (cache, key, opts) {
|
||||
opts = PutOpts(opts)
|
||||
let integrity
|
||||
let size
|
||||
const contentStream = write.stream(
|
||||
cache, opts
|
||||
).on('integrity', int => {
|
||||
integrity = int
|
||||
}).on('size', s => {
|
||||
size = s
|
||||
})
|
||||
let memoData
|
||||
let memoTotal = 0
|
||||
const stream = to((chunk, enc, cb) => {
|
||||
contentStream.write(chunk, enc, () => {
|
||||
if (opts.memoize) {
|
||||
if (!memoData) { memoData = [] }
|
||||
memoData.push(chunk)
|
||||
memoTotal += chunk.length
|
||||
}
|
||||
cb()
|
||||
})
|
||||
}, cb => {
|
||||
contentStream.end(() => {
|
||||
index.insert(cache, key, integrity, opts.concat({ size })).then(entry => {
|
||||
if (opts.memoize) {
|
||||
memo.put(cache, entry, Buffer.concat(memoData, memoTotal), opts)
|
||||
}
|
||||
stream.emit('integrity', integrity)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
})
|
||||
let erred = false
|
||||
stream.once('error', err => {
|
||||
if (erred) { return }
|
||||
erred = true
|
||||
contentStream.emit('error', err)
|
||||
})
|
||||
contentStream.once('error', err => {
|
||||
if (erred) { return }
|
||||
erred = true
|
||||
stream.emit('error', err)
|
||||
})
|
||||
return stream
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
const BB = require('bluebird')
|
||||
|
||||
const index = require('./lib/entry-index')
|
||||
const memo = require('./lib/memoization')
|
||||
const path = require('path')
|
||||
const rimraf = BB.promisify(require('rimraf'))
|
||||
const rmContent = require('./lib/content/rm')
|
||||
|
||||
module.exports = entry
|
||||
module.exports.entry = entry
|
||||
function entry (cache, key) {
|
||||
memo.clearMemoized()
|
||||
return index.delete(cache, key)
|
||||
}
|
||||
|
||||
module.exports.content = content
|
||||
function content (cache, integrity) {
|
||||
memo.clearMemoized()
|
||||
return rmContent(cache, integrity)
|
||||
}
|
||||
|
||||
module.exports.all = all
|
||||
function all (cache) {
|
||||
memo.clearMemoized()
|
||||
return rimraf(path.join(cache, '*(content-*|index-*)'))
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = require('./lib/verify')
|
||||
Reference in New Issue
Block a user