@vltpkg/registry-client 0.0.0-0.1730239248325
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +122 -0
- package/dist/esm/add-header.d.ts +2 -0
- package/dist/esm/add-header.d.ts.map +1 -0
- package/dist/esm/add-header.js +24 -0
- package/dist/esm/add-header.js.map +1 -0
- package/dist/esm/cache-entry.d.ts +69 -0
- package/dist/esm/cache-entry.d.ts.map +1 -0
- package/dist/esm/cache-entry.js +335 -0
- package/dist/esm/cache-entry.js.map +1 -0
- package/dist/esm/env.d.ts +11 -0
- package/dist/esm/env.d.ts.map +1 -0
- package/dist/esm/env.js +16 -0
- package/dist/esm/env.js.map +1 -0
- package/dist/esm/get-header.d.ts +2 -0
- package/dist/esm/get-header.d.ts.map +1 -0
- package/dist/esm/get-header.js +37 -0
- package/dist/esm/get-header.js.map +1 -0
- package/dist/esm/handle-304-response.d.ts +4 -0
- package/dist/esm/handle-304-response.d.ts.map +1 -0
- package/dist/esm/handle-304-response.js +10 -0
- package/dist/esm/handle-304-response.js.map +1 -0
- package/dist/esm/index.d.ts +55 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +122 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/is-cacheable.d.ts +5 -0
- package/dist/esm/is-cacheable.d.ts.map +1 -0
- package/dist/esm/is-cacheable.js +10 -0
- package/dist/esm/is-cacheable.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/raw-header.d.ts +9 -0
- package/dist/esm/raw-header.d.ts.map +1 -0
- package/dist/esm/raw-header.js +35 -0
- package/dist/esm/raw-header.js.map +1 -0
- package/dist/esm/redirect.d.ts +23 -0
- package/dist/esm/redirect.d.ts.map +1 -0
- package/dist/esm/redirect.js +60 -0
- package/dist/esm/redirect.js.map +1 -0
- package/dist/esm/serdes.d.ts +15 -0
- package/dist/esm/serdes.d.ts.map +1 -0
- package/dist/esm/serdes.js +19 -0
- package/dist/esm/serdes.js.map +1 -0
- package/dist/esm/set-cache-headers.d.ts +4 -0
- package/dist/esm/set-cache-headers.d.ts.map +1 -0
- package/dist/esm/set-cache-headers.js +17 -0
- package/dist/esm/set-cache-headers.js.map +1 -0
- package/dist/esm/set-raw-header.d.ts +6 -0
- package/dist/esm/set-raw-header.d.ts.map +1 -0
- package/dist/esm/set-raw-header.js +20 -0
- package/dist/esm/set-raw-header.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Copyright (c) vlt technology, Inc.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
4
|
+
|
|
5
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
6
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
7
|
+
Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by:
|
|
8
|
+
|
|
9
|
+
(a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of contributors, in source or binary form) alone; or
|
|
10
|
+
(b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution.
|
|
11
|
+
Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this license, whether expressly, by implication, estoppel or otherwise.
|
|
12
|
+
|
|
13
|
+
DISCLAIMER
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# `@vltpkg/registry-client`
|
|
2
|
+
|
|
3
|
+
This is a very light wrapper around undici, optimized for
|
|
4
|
+
interfacing with an npm registry.
|
|
5
|
+
|
|
6
|
+
Any response with `immutable` in the `cache-control` header, or
|
|
7
|
+
with a `content-type` of `application/octet-stream` or a path
|
|
8
|
+
ending in `.tgz`, will be cached forever and never requested
|
|
9
|
+
again as long as the cache survives.
|
|
10
|
+
|
|
11
|
+
If the request has a cached response:
|
|
12
|
+
|
|
13
|
+
- Cached responses with `immutable` in the `cache-control`
|
|
14
|
+
header will be returned from cache without a network request,
|
|
15
|
+
no matter what.
|
|
16
|
+
- Cached responses with a `content-type` of
|
|
17
|
+
`application/octet-stream` will be returned from cache without
|
|
18
|
+
a network request, no matter what, because tarballs are
|
|
19
|
+
immutable.
|
|
20
|
+
- Cached responses with `max-age=<n>` or `s-max-age=<n>` will be
|
|
21
|
+
served from cache without a network request if it's less than
|
|
22
|
+
`<n>` seconds old.
|
|
23
|
+
- Otherwise, a network request to the registry will be made
|
|
24
|
+
- if an `etag` is present in the cached response, it will be
|
|
25
|
+
used as the `if-none-match` header.
|
|
26
|
+
- If a `last-modified` header is in the response, that will
|
|
27
|
+
be used as the `if-modified-since` request header.
|
|
28
|
+
- If there is no `last-modified` header, then use the `mtime`
|
|
29
|
+
of the cache file as the `if-modified-since` header.
|
|
30
|
+
|
|
31
|
+
This is the extent of the cache control logic. It is not a
|
|
32
|
+
full-featured spec-compliant caching HTTP client, because that is
|
|
33
|
+
not needed for this use case. Every response will be cached, even
|
|
34
|
+
if the registry headers don't technically allow it.
|
|
35
|
+
|
|
36
|
+
## Cache Unzipped
|
|
37
|
+
|
|
38
|
+
Client always sends `accept-encoding: gzip;q=1.0, *;q=0.5`
|
|
39
|
+
header when making requests, to save time on the wire.
|
|
40
|
+
|
|
41
|
+
If response has `content-encoding: gzip`, then we swap out the
|
|
42
|
+
body for the unzipped response body in the cache, as if it was
|
|
43
|
+
not gzipped in the first place. This _must_ be done before
|
|
44
|
+
returning the response, because you can't `JSON.parse()` a
|
|
45
|
+
gzipped response anyway.
|
|
46
|
+
|
|
47
|
+
If the response is `content-type: application/octet-stream` and
|
|
48
|
+
starts with the gzip header, then we return the raw body as we
|
|
49
|
+
received it, but as a best-effort background job, unzip it and
|
|
50
|
+
update the cache entry to be an unzipped response body. This is
|
|
51
|
+
done in the `@vltpkg/cache-unzip` worker.
|
|
52
|
+
|
|
53
|
+
So,
|
|
54
|
+
|
|
55
|
+
- json responses will always be un-zipped, in the response and in
|
|
56
|
+
the cache.
|
|
57
|
+
- artifact responses _may_ be gzipped (and thus, have to be
|
|
58
|
+
unzipped by the unpack operation), but will eventually be
|
|
59
|
+
cached as unzipped tarballs.
|
|
60
|
+
|
|
61
|
+
Thus, the `content-length` response header will _usually_ not
|
|
62
|
+
match the actual byte length of the response body.
|
|
63
|
+
|
|
64
|
+
## Integrity Options
|
|
65
|
+
|
|
66
|
+
An `integrity` option may be specified in a
|
|
67
|
+
`fetchOptions.context` object, or in the options provided to
|
|
68
|
+
`cache.set()`. For example:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
const integrity = `sha512-${base64hash}`
|
|
72
|
+
// Sets the key, and links to a file based on the integrity
|
|
73
|
+
// value. Any future fetches for the same integrity will yield
|
|
74
|
+
// this value.
|
|
75
|
+
cache.set(key, value, { integrity })
|
|
76
|
+
|
|
77
|
+
// Load an entry with this integrity value, if present in the
|
|
78
|
+
// on-disk cache, and when writing back to the disk, link to the
|
|
79
|
+
// appropriate integrity file as well.
|
|
80
|
+
const value = await cache.fetch(key, { context: { integrity } })
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If the integrity provided is obviously not a valid sha512
|
|
84
|
+
`Integrity` string, then it is ignored.
|
|
85
|
+
|
|
86
|
+
Integrity values are not calculated or verified. The caller must do this
|
|
87
|
+
check, if desired.
|
|
88
|
+
|
|
89
|
+
Note that the integrity provided to `cache.fetch()` or `cache.set()`
|
|
90
|
+
does _not_ typically match the calculated integrity of the object
|
|
91
|
+
being cached. Typically, the integrity is related to the body of
|
|
92
|
+
the response that a `@vltpkg/registry-client.CacheEntry` object
|
|
93
|
+
represents.
|
|
94
|
+
|
|
95
|
+
## USAGE
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { Cache } from '@vltpkg/cache'
|
|
99
|
+
|
|
100
|
+
// set in the memory cache, and return immediately. in the
|
|
101
|
+
// background, write the entry to disk.
|
|
102
|
+
cache.set(someKey, someBuffer)
|
|
103
|
+
|
|
104
|
+
// if you need to wait for all pending writes to finish pending,
|
|
105
|
+
// you can do that like this
|
|
106
|
+
await cache.promise()
|
|
107
|
+
|
|
108
|
+
// get an item from the memory cache if possible, falling back to
|
|
109
|
+
// loading by integrity/key from the disk. Integrity is optional.
|
|
110
|
+
const value = await cache.fetch(someKey, { context: { integrity }})
|
|
111
|
+
|
|
112
|
+
// get from the memory cache *only*, do not read from the disk
|
|
113
|
+
const value = cache.get(someKey)
|
|
114
|
+
|
|
115
|
+
// Synchronous form of cache.fetch()
|
|
116
|
+
const value = cache.fetchSync(someKey, { context: { integrity }})
|
|
117
|
+
|
|
118
|
+
// sync and async iteration is supported
|
|
119
|
+
// walks over all items on disk
|
|
120
|
+
for await (const item of cache) { ... }
|
|
121
|
+
for (const item of cache) { ... }
|
|
122
|
+
```
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const addHeader: <H extends [string, string[] | string][] | Iterable<[string, string[] | string | undefined]> | Record<string, string[] | string | undefined> | string[]>(headers: H | null | undefined, key: string, value: string) => H;
|
|
2
|
+
//# sourceMappingURL=add-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-header.d.ts","sourceRoot":"","sources":["../../src/add-header.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,SAAS,GACpB,CAAC,SACG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,GAC7B,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC,GACjD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC,GAC7C,MAAM,EAAE,WAEH,CAAC,GAAG,IAAI,GAAG,SAAS,OACxB,MAAM,SACJ,MAAM,KACZ,CAgBF,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const isIterable = (o) => !!o && !!o[Symbol.iterator];
|
|
2
|
+
// this does some rude things with types, but not much way around it,
|
|
3
|
+
// since the opts.headers type is so loosey goosey to begin with.
|
|
4
|
+
export const addHeader = (headers, key, value) => {
|
|
5
|
+
if (!headers)
|
|
6
|
+
return { [key]: value };
|
|
7
|
+
if (Array.isArray(headers)) {
|
|
8
|
+
if (!headers.length)
|
|
9
|
+
return [[key, value]];
|
|
10
|
+
if (Array.isArray(headers[0]))
|
|
11
|
+
headers.push([key, value]);
|
|
12
|
+
else
|
|
13
|
+
headers.push(key, value);
|
|
14
|
+
return headers;
|
|
15
|
+
}
|
|
16
|
+
else if (isIterable(headers)) {
|
|
17
|
+
return [...headers, [key, value]];
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
headers[key] = value;
|
|
21
|
+
return headers;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=add-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-header.js","sourceRoot":"","sources":["../../src/add-header.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,GAAG,CAAI,CAAM,EAAoB,EAAE,CACjD,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AAE7B,qEAAqE;AACrE,iEAAiE;AACjE,MAAM,CAAC,MAAM,SAAS,GAAG,CAOvB,OAA6B,EAC7B,GAAW,EACX,KAAa,EACV,EAAE;IACL,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAO,CAAA;IAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAiB,CAAA;QAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC1B,OAA8B,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;;YAC9C,OAAoB,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAC3C,OAAO,OAAO,CAAA;IAChB,CAAC;SAAM,IACL,UAAU,CAA0C,OAAO,CAAC,EAC5D,CAAC;QACD,OAAO,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,CAAiB,CAAA;IACnD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACpB,OAAO,OAAO,CAAA;IAChB,CAAC;AACH,CAAC,CAAA","sourcesContent":["const isIterable = <T>(o: any): o is Iterable<T> =>\n !!o && !!o[Symbol.iterator]\n\n// this does some rude things with types, but not much way around it,\n// since the opts.headers type is so loosey goosey to begin with.\nexport const addHeader = <\n H extends\n | [string, string[] | string][]\n | Iterable<[string, string[] | string | undefined]>\n | Record<string, string[] | string | undefined>\n | string[],\n>(\n headers: H | null | undefined,\n key: string,\n value: string,\n): H => {\n if (!headers) return { [key]: value } as H\n if (Array.isArray(headers)) {\n if (!headers.length) return [[key, value]] as unknown as H\n if (Array.isArray(headers[0]))\n (headers as [string, string][]).push([key, value])\n else (headers as string[]).push(key, value)\n return headers\n } else if (\n isIterable<[string, string[] | string | undefined]>(headers)\n ) {\n return [...headers, [key, value]] as unknown as H\n } else {\n headers[key] = value\n return headers\n }\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Integrity, JSONField } from '@vltpkg/types';
|
|
2
|
+
declare const kCustomInspect: unique symbol;
|
|
3
|
+
export declare class CacheEntry {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(statusCode: number, headers: Buffer[], integrity?: Integrity);
|
|
6
|
+
[kCustomInspect](...args: any[]): string;
|
|
7
|
+
/**
|
|
8
|
+
* `true` if the entry represents a cached response that is still
|
|
9
|
+
* valid to use.
|
|
10
|
+
*/
|
|
11
|
+
get valid(): boolean;
|
|
12
|
+
addBody(b: Buffer): void;
|
|
13
|
+
get statusCode(): number;
|
|
14
|
+
get headers(): Buffer[];
|
|
15
|
+
/**
|
|
16
|
+
* check that the sri integrity string that was provided to the ctor
|
|
17
|
+
* matches the body that we actually received. This should only be called
|
|
18
|
+
* AFTER the entire body has been completely downloaded.
|
|
19
|
+
*
|
|
20
|
+
* Note that this will *usually* not be true if the value is coming out of
|
|
21
|
+
* the cache, because the cache entries are un-gzipped in place. It should
|
|
22
|
+
* *only* be called for artifacts that come from an actual http response.
|
|
23
|
+
*/
|
|
24
|
+
checkIntegrity(): boolean;
|
|
25
|
+
get integrityActual(): Integrity;
|
|
26
|
+
get integrity(): `sha512-${string}` | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Give it a key, and it'll return the buffer of that header value
|
|
29
|
+
*/
|
|
30
|
+
getHeader(h: string): Buffer | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Set a header to a specific value
|
|
33
|
+
*/
|
|
34
|
+
setHeader(h: string, value: Buffer | string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Return the body of the entry as a Buffer
|
|
37
|
+
*/
|
|
38
|
+
buffer(): Buffer;
|
|
39
|
+
get body(): Buffer | Record<string, any>;
|
|
40
|
+
get isJSON(): boolean;
|
|
41
|
+
get isGzip(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Un-gzip encode the body.
|
|
44
|
+
* Returns true if it was previously gzip (so something was done), otherwise
|
|
45
|
+
* returns false.
|
|
46
|
+
*/
|
|
47
|
+
unzip(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Return the body of the entry as utf8 text
|
|
50
|
+
* Automatically unzips if the content is gzip encoded
|
|
51
|
+
*/
|
|
52
|
+
text(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Parse the entry body as JSON and return the result
|
|
55
|
+
*/
|
|
56
|
+
json(): Record<string, JSONField>;
|
|
57
|
+
/**
|
|
58
|
+
* Pass the contents of a @vltpkg/cache.Cache object as a buffer,
|
|
59
|
+
* and this static method will decode it into a CacheEntry representing
|
|
60
|
+
* the cached response.
|
|
61
|
+
*/
|
|
62
|
+
static decode(buffer: Buffer): CacheEntry;
|
|
63
|
+
/**
|
|
64
|
+
* Encode the entry as a single Buffer for writing to the cache
|
|
65
|
+
*/
|
|
66
|
+
encode(): Buffer;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=cache-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-entry.d.ts","sourceRoot":"","sources":["../../src/cache-entry.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AA+BpD,QAAA,MAAM,cAAc,eAA2C,CAAA;AAE/D,qBAAa,UAAU;;gBAUnB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,CAAC,EAAE,SAAS;IAiBvB,CAAC,cAAc,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM;IAYxC;;;OAGG;IACH,IAAI,KAAK,IAAI,OAAO,CAsBnB;IAED,OAAO,CAAC,CAAC,EAAE,MAAM;IAKjB,IAAI,UAAU,WAEb;IACD,IAAI,OAAO,aAEV;IAED;;;;;;;;OAQG;IACH,cAAc,IAAI,OAAO;IAIzB,IAAI,eAAe,IAAI,SAAS,CAM/B;IACD,IAAI,SAAS,mCAEZ;IAED;;OAEG;IACH,SAAS,CAAC,CAAC,EAAE,MAAM;IAInB;;OAEG;IACH,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAI3C;;OAEG;IACH,MAAM,IAAI,MAAM;IAWhB,IAAI,IAAI,IAAI,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAEvC;IAGD,IAAI,MAAM,IAAI,OAAO,CAcpB;IAGD,IAAI,MAAM,IAAI,OAAO,CAcpB;IAED;;;;OAIG;IACH,KAAK;IAmBL;;;OAGG;IACH,IAAI;IAKJ;;OAEG;IACH,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;IAgBjC;;;;OAIG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU;IAiCzC;;OAEG;IAGH,MAAM,IAAI,MAAM;CAoCjB"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// A response object in the cache.
|
|
2
|
+
//
|
|
3
|
+
// The cache stores Buffer objects, and it's convenient to have headers/body
|
|
4
|
+
// together, so we have a simple data structure for this.
|
|
5
|
+
//
|
|
6
|
+
// The shape of it is:
|
|
7
|
+
//
|
|
8
|
+
// [head length]
|
|
9
|
+
// <status code in ascii>
|
|
10
|
+
// [headers]
|
|
11
|
+
// [body]
|
|
12
|
+
//
|
|
13
|
+
// The [UInt32BE head length] is 4 bytes specifying the full length of the
|
|
14
|
+
// status code plus all header keys and values.
|
|
15
|
+
//
|
|
16
|
+
// The [headers] section is key/value/key2/value2/... where each key and value
|
|
17
|
+
// is a 4-byte Uint32BE length, followed by that many bytes.
|
|
18
|
+
//
|
|
19
|
+
// From there, the body can be of any indeterminate length, and is the rest
|
|
20
|
+
// of the file.
|
|
21
|
+
import { error } from '@vltpkg/error-cause';
|
|
22
|
+
import ccp from 'cache-control-parser';
|
|
23
|
+
import { createHash } from 'crypto';
|
|
24
|
+
import { inspect } from 'util';
|
|
25
|
+
import { gunzipSync } from 'zlib';
|
|
26
|
+
import { getRawHeader, setRawHeader } from './raw-header.js';
|
|
27
|
+
import { deserialize, serialize, serializedHeader } from './serdes.js';
|
|
28
|
+
const readSize = (buf, offset) => {
|
|
29
|
+
const a = buf[offset];
|
|
30
|
+
const b = buf[offset + 1];
|
|
31
|
+
const c = buf[offset + 2];
|
|
32
|
+
const d = buf[offset + 3];
|
|
33
|
+
// not possible, we check the length
|
|
34
|
+
/* c8 ignore start */
|
|
35
|
+
if (a === undefined ||
|
|
36
|
+
b === undefined ||
|
|
37
|
+
c === undefined ||
|
|
38
|
+
d === undefined) {
|
|
39
|
+
throw error('Invalid buffer, not long enough to readSize', {
|
|
40
|
+
found: buf.length,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/* c8 ignore stop */
|
|
44
|
+
return (a << 24) | (b << 16) | (c << 8) | d;
|
|
45
|
+
};
|
|
46
|
+
const kCustomInspect = Symbol.for('nodejs.util.inspect.custom');
|
|
47
|
+
export class CacheEntry {
|
|
48
|
+
#statusCode;
|
|
49
|
+
#headers;
|
|
50
|
+
#body = [];
|
|
51
|
+
#bodyLength = 0;
|
|
52
|
+
#integrity;
|
|
53
|
+
#integrityActual;
|
|
54
|
+
#json;
|
|
55
|
+
constructor(statusCode, headers, integrity) {
|
|
56
|
+
this.#integrity = integrity;
|
|
57
|
+
this.#statusCode = statusCode;
|
|
58
|
+
this.#headers = headers;
|
|
59
|
+
}
|
|
60
|
+
get #headersAsObject() {
|
|
61
|
+
const ret = [];
|
|
62
|
+
for (let i = 0; i < this.#headers.length - 1; i += 2) {
|
|
63
|
+
const key = String(this.#headers[i]);
|
|
64
|
+
const val = String(this.#headers[i + 1]);
|
|
65
|
+
ret.push([key, val]);
|
|
66
|
+
}
|
|
67
|
+
return ret;
|
|
68
|
+
}
|
|
69
|
+
[kCustomInspect](...args) {
|
|
70
|
+
const str = inspect({
|
|
71
|
+
statusCode: this.statusCode,
|
|
72
|
+
headers: this.#headersAsObject,
|
|
73
|
+
text: this.text(),
|
|
74
|
+
}, ...args);
|
|
75
|
+
return `@vltpkg/registry-client.CacheEntry ${str}`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `true` if the entry represents a cached response that is still
|
|
79
|
+
* valid to use.
|
|
80
|
+
*/
|
|
81
|
+
get valid() {
|
|
82
|
+
const cc_ = this.getHeader('cache-control')?.toString();
|
|
83
|
+
const cc = cc_ ? ccp.parse(cc_) : {};
|
|
84
|
+
const ct = this.getHeader('content-type')?.toString() ?? '';
|
|
85
|
+
const dh = this.getHeader('date')?.toString();
|
|
86
|
+
// immutable = never changes
|
|
87
|
+
if (cc.immutable)
|
|
88
|
+
return true;
|
|
89
|
+
// some registries do text/json, some do application/json,
|
|
90
|
+
// some do application/vnd.npm.install-v1+json
|
|
91
|
+
// If it's NOT json, it's an immutable tarball
|
|
92
|
+
if (ct !== '' && !/\bjson\b/.test(ct))
|
|
93
|
+
return true;
|
|
94
|
+
// see if the max-age has not yet been crossed
|
|
95
|
+
// default to 5m if maxage is not set, as some registries
|
|
96
|
+
// do not set a cache control header at all.
|
|
97
|
+
const ma = cc['max-age'] || cc['s-maxage'] || 300;
|
|
98
|
+
if (ma && dh) {
|
|
99
|
+
return Date.parse(dh) + ma * 1000 > Date.now();
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
addBody(b) {
|
|
104
|
+
this.#body.push(b);
|
|
105
|
+
this.#bodyLength += b.byteLength;
|
|
106
|
+
}
|
|
107
|
+
get statusCode() {
|
|
108
|
+
return this.#statusCode;
|
|
109
|
+
}
|
|
110
|
+
get headers() {
|
|
111
|
+
return this.#headers;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* check that the sri integrity string that was provided to the ctor
|
|
115
|
+
* matches the body that we actually received. This should only be called
|
|
116
|
+
* AFTER the entire body has been completely downloaded.
|
|
117
|
+
*
|
|
118
|
+
* Note that this will *usually* not be true if the value is coming out of
|
|
119
|
+
* the cache, because the cache entries are un-gzipped in place. It should
|
|
120
|
+
* *only* be called for artifacts that come from an actual http response.
|
|
121
|
+
*/
|
|
122
|
+
checkIntegrity() {
|
|
123
|
+
if (!this.#integrity)
|
|
124
|
+
return false;
|
|
125
|
+
return this.integrityActual === this.#integrity;
|
|
126
|
+
}
|
|
127
|
+
get integrityActual() {
|
|
128
|
+
if (this.#integrityActual)
|
|
129
|
+
return this.#integrityActual;
|
|
130
|
+
const hash = createHash('sha512');
|
|
131
|
+
for (const buf of this.#body)
|
|
132
|
+
hash.update(buf);
|
|
133
|
+
this.#integrityActual = `sha512-${hash.digest('base64')}`;
|
|
134
|
+
return this.#integrityActual;
|
|
135
|
+
}
|
|
136
|
+
get integrity() {
|
|
137
|
+
return this.#integrity;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Give it a key, and it'll return the buffer of that header value
|
|
141
|
+
*/
|
|
142
|
+
getHeader(h) {
|
|
143
|
+
return getRawHeader(this.#headers, h);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Set a header to a specific value
|
|
147
|
+
*/
|
|
148
|
+
setHeader(h, value) {
|
|
149
|
+
this.#headers = setRawHeader(this.#headers, h, value);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Return the body of the entry as a Buffer
|
|
153
|
+
*/
|
|
154
|
+
buffer() {
|
|
155
|
+
const b = this.#body[0];
|
|
156
|
+
if (!b)
|
|
157
|
+
return Buffer.allocUnsafe(0);
|
|
158
|
+
if (this.#body.length === 1)
|
|
159
|
+
return b;
|
|
160
|
+
const cat = Buffer.concat(this.#body, this.#bodyLength);
|
|
161
|
+
this.#body = [cat];
|
|
162
|
+
return cat;
|
|
163
|
+
}
|
|
164
|
+
// return the buffer if it's a tarball, or the parsed
|
|
165
|
+
// JSON if it's not.
|
|
166
|
+
get body() {
|
|
167
|
+
return this.isJSON ? this.json() : this.buffer();
|
|
168
|
+
}
|
|
169
|
+
#isJSON;
|
|
170
|
+
get isJSON() {
|
|
171
|
+
if (this.#isJSON !== undefined)
|
|
172
|
+
return this.#isJSON;
|
|
173
|
+
const ser = serializedHeader && this.getHeader(serializedHeader);
|
|
174
|
+
if (ser)
|
|
175
|
+
return (this.#isJSON = true);
|
|
176
|
+
const ct = this.getHeader('content-type')?.toString();
|
|
177
|
+
// if it says it's json, assume json
|
|
178
|
+
if (ct)
|
|
179
|
+
return /\bjson\b/.test(ct);
|
|
180
|
+
const text = this.text();
|
|
181
|
+
// don't cache, because we might just not have it yet.
|
|
182
|
+
if (!text)
|
|
183
|
+
return false;
|
|
184
|
+
// all registry json starts with {, and no tarball ever can.
|
|
185
|
+
this.#isJSON = text.startsWith('{');
|
|
186
|
+
if (this.#isJSON)
|
|
187
|
+
this.setHeader('content-type', 'text/json');
|
|
188
|
+
return this.#isJSON;
|
|
189
|
+
}
|
|
190
|
+
#isGzip;
|
|
191
|
+
get isGzip() {
|
|
192
|
+
if (this.#isGzip !== undefined)
|
|
193
|
+
return this.#isGzip;
|
|
194
|
+
const ce = this.getHeader('content-encoding')?.toString();
|
|
195
|
+
if (ce && !/\bgzip\b/.test(ce))
|
|
196
|
+
return (this.#isGzip = false);
|
|
197
|
+
const buf = this.buffer();
|
|
198
|
+
if (buf.length < 2)
|
|
199
|
+
return false;
|
|
200
|
+
this.#isGzip = buf[0] === 0x1f && buf[1] === 0x8b;
|
|
201
|
+
if (this.#isGzip) {
|
|
202
|
+
this.setHeader('content-encoding', 'gzip');
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.setHeader('content-encoding', 'identity');
|
|
206
|
+
this.setHeader('content-length', String(this.#bodyLength));
|
|
207
|
+
}
|
|
208
|
+
return this.#isGzip;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Un-gzip encode the body.
|
|
212
|
+
* Returns true if it was previously gzip (so something was done), otherwise
|
|
213
|
+
* returns false.
|
|
214
|
+
*/
|
|
215
|
+
unzip() {
|
|
216
|
+
if (this.isGzip) {
|
|
217
|
+
// we know that if we know it's gzip, that the body has been
|
|
218
|
+
// flattened to a single buffer, so save the extra call.
|
|
219
|
+
/* c8 ignore start */
|
|
220
|
+
if (this.#body[0] == null)
|
|
221
|
+
throw error('Invalid buffer, cant unzip');
|
|
222
|
+
/* c8 ignore stop */
|
|
223
|
+
const b = gunzipSync(this.#body[0]);
|
|
224
|
+
this.setHeader('content-encoding', 'identity');
|
|
225
|
+
this.#body = [b];
|
|
226
|
+
this.#bodyLength = b.byteLength;
|
|
227
|
+
this.setHeader('content-length', String(this.#bodyLength));
|
|
228
|
+
this.#isGzip = false;
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Return the body of the entry as utf8 text
|
|
235
|
+
* Automatically unzips if the content is gzip encoded
|
|
236
|
+
*/
|
|
237
|
+
text() {
|
|
238
|
+
this.unzip();
|
|
239
|
+
return this.buffer().toString();
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Parse the entry body as JSON and return the result
|
|
243
|
+
*/
|
|
244
|
+
json() {
|
|
245
|
+
if (this.#json !== undefined)
|
|
246
|
+
return this.#json;
|
|
247
|
+
const ser = serializedHeader && this.getHeader(serializedHeader);
|
|
248
|
+
if (ser) {
|
|
249
|
+
/* c8 ignore start - very rare, but theoretically possible to throw */
|
|
250
|
+
try {
|
|
251
|
+
return (this.#json = deserialize(ser));
|
|
252
|
+
}
|
|
253
|
+
catch { }
|
|
254
|
+
/* c8 ignore stop */
|
|
255
|
+
}
|
|
256
|
+
const obj = JSON.parse(this.text());
|
|
257
|
+
if (serializedHeader)
|
|
258
|
+
this.setHeader(serializedHeader, serialize(obj));
|
|
259
|
+
return obj;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Pass the contents of a @vltpkg/cache.Cache object as a buffer,
|
|
263
|
+
* and this static method will decode it into a CacheEntry representing
|
|
264
|
+
* the cached response.
|
|
265
|
+
*/
|
|
266
|
+
static decode(buffer) {
|
|
267
|
+
if (buffer.length < 4) {
|
|
268
|
+
return new CacheEntry(0, []);
|
|
269
|
+
}
|
|
270
|
+
const headSize = readSize(buffer, 0);
|
|
271
|
+
if (buffer.length < headSize) {
|
|
272
|
+
return new CacheEntry(0, []);
|
|
273
|
+
}
|
|
274
|
+
const statusCode = Number(buffer.subarray(4, 7).toString());
|
|
275
|
+
const headersBuffer = buffer.subarray(7, headSize);
|
|
276
|
+
// walk through the headers array, building up the rawHeaders Buffer[]
|
|
277
|
+
const headers = [];
|
|
278
|
+
let i = 0;
|
|
279
|
+
while (i < headersBuffer.length - 4) {
|
|
280
|
+
const size = readSize(headersBuffer, i);
|
|
281
|
+
headers.push(headersBuffer.subarray(i + 4, i + size));
|
|
282
|
+
i += size;
|
|
283
|
+
}
|
|
284
|
+
const c = new CacheEntry(statusCode, headers);
|
|
285
|
+
const body = buffer.subarray(headSize);
|
|
286
|
+
c.#body = [body];
|
|
287
|
+
c.#bodyLength = body.byteLength;
|
|
288
|
+
c.setHeader('content-length', String(c.#bodyLength));
|
|
289
|
+
if (c.isJSON) {
|
|
290
|
+
try {
|
|
291
|
+
c.json();
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return new CacheEntry(0, []);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return c;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Encode the entry as a single Buffer for writing to the cache
|
|
301
|
+
*/
|
|
302
|
+
// TODO: should this maybe not concat, and just return Buffer[]?
|
|
303
|
+
// Then we can writev it to the cache file and save the memory copy
|
|
304
|
+
encode() {
|
|
305
|
+
// store json results as a serialized object.
|
|
306
|
+
if (this.isJSON)
|
|
307
|
+
this.json();
|
|
308
|
+
const sb = Buffer.from(String(this.#statusCode));
|
|
309
|
+
const chunks = [sb];
|
|
310
|
+
let headLength = sb.byteLength + 4;
|
|
311
|
+
for (const h of this.#headers) {
|
|
312
|
+
const hlBuf = Buffer.allocUnsafe(4);
|
|
313
|
+
const hl = h.byteLength + 4;
|
|
314
|
+
headLength += hl;
|
|
315
|
+
hlBuf.set([
|
|
316
|
+
(hl >> 24) & 0xff,
|
|
317
|
+
(hl >> 16) & 0xff,
|
|
318
|
+
(hl >> 8) & 0xff,
|
|
319
|
+
hl & 0xff,
|
|
320
|
+
], 0);
|
|
321
|
+
chunks.push(hlBuf, h);
|
|
322
|
+
}
|
|
323
|
+
const hlBuf = Buffer.allocUnsafe(4);
|
|
324
|
+
hlBuf.set([
|
|
325
|
+
(headLength >> 24) & 0xff,
|
|
326
|
+
(headLength >> 16) & 0xff,
|
|
327
|
+
(headLength >> 8) & 0xff,
|
|
328
|
+
headLength & 0xff,
|
|
329
|
+
], 0);
|
|
330
|
+
chunks.unshift(hlBuf);
|
|
331
|
+
chunks.push(...this.#body);
|
|
332
|
+
return Buffer.concat(chunks, headLength + this.#bodyLength);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=cache-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-entry.js","sourceRoot":"","sources":["../../src/cache-entry.ts"],"names":[],"mappings":"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","sourcesContent":["// A response object in the cache.\n//\n// The cache stores Buffer objects, and it's convenient to have headers/body\n// together, so we have a simple data structure for this.\n//\n// The shape of it is:\n//\n// [head length]\n// <status code in ascii>\n// [headers]\n// [body]\n//\n// The [UInt32BE head length] is 4 bytes specifying the full length of the\n// status code plus all header keys and values.\n//\n// The [headers] section is key/value/key2/value2/... where each key and value\n// is a 4-byte Uint32BE length, followed by that many bytes.\n//\n// From there, the body can be of any indeterminate length, and is the rest\n// of the file.\n\nimport { error } from '@vltpkg/error-cause'\nimport { Integrity, JSONField } from '@vltpkg/types'\nimport ccp from 'cache-control-parser'\nimport { createHash } from 'crypto'\nimport { inspect } from 'util'\nimport { gunzipSync } from 'zlib'\nimport { getRawHeader, setRawHeader } from './raw-header.js'\nimport { deserialize, serialize, serializedHeader } from './serdes.js'\n\nconst readSize = (buf: Buffer, offset: number) => {\n const a = buf[offset]\n const b = buf[offset + 1]\n const c = buf[offset + 2]\n const d = buf[offset + 3]\n\n // not possible, we check the length\n /* c8 ignore start */\n if (\n a === undefined ||\n b === undefined ||\n c === undefined ||\n d === undefined\n ) {\n throw error('Invalid buffer, not long enough to readSize', {\n found: buf.length,\n })\n }\n /* c8 ignore stop */\n\n return (a << 24) | (b << 16) | (c << 8) | d\n}\n\nconst kCustomInspect = Symbol.for('nodejs.util.inspect.custom')\n\nexport class CacheEntry {\n #statusCode: number\n #headers: Buffer[]\n #body: Buffer[] = []\n #bodyLength = 0\n #integrity?: Integrity\n #integrityActual?: Integrity\n #json?: Record<string, JSONField>\n\n constructor(\n statusCode: number,\n headers: Buffer[],\n integrity?: Integrity,\n ) {\n this.#integrity = integrity\n this.#statusCode = statusCode\n this.#headers = headers\n }\n\n get #headersAsObject(): [string, string][] {\n const ret: [string, string][] = []\n for (let i = 0; i < this.#headers.length - 1; i += 2) {\n const key = String(this.#headers[i])\n const val = String(this.#headers[i + 1])\n ret.push([key, val])\n }\n return ret\n }\n\n [kCustomInspect](...args: any[]): string {\n const str = inspect(\n {\n statusCode: this.statusCode,\n headers: this.#headersAsObject,\n text: this.text(),\n },\n ...args,\n )\n return `@vltpkg/registry-client.CacheEntry ${str}`\n }\n\n /**\n * `true` if the entry represents a cached response that is still\n * valid to use.\n */\n get valid(): boolean {\n const cc_ = this.getHeader('cache-control')?.toString()\n const cc = cc_ ? ccp.parse(cc_) : {}\n const ct = this.getHeader('content-type')?.toString() ?? ''\n const dh = this.getHeader('date')?.toString()\n\n // immutable = never changes\n if (cc.immutable) return true\n\n // some registries do text/json, some do application/json,\n // some do application/vnd.npm.install-v1+json\n // If it's NOT json, it's an immutable tarball\n if (ct !== '' && !/\\bjson\\b/.test(ct)) return true\n\n // see if the max-age has not yet been crossed\n // default to 5m if maxage is not set, as some registries\n // do not set a cache control header at all.\n const ma = cc['max-age'] || cc['s-maxage'] || 300\n if (ma && dh) {\n return Date.parse(dh) + ma * 1000 > Date.now()\n }\n return false\n }\n\n addBody(b: Buffer) {\n this.#body.push(b)\n this.#bodyLength += b.byteLength\n }\n\n get statusCode() {\n return this.#statusCode\n }\n get headers() {\n return this.#headers\n }\n\n /**\n * check that the sri integrity string that was provided to the ctor\n * matches the body that we actually received. This should only be called\n * AFTER the entire body has been completely downloaded.\n *\n * Note that this will *usually* not be true if the value is coming out of\n * the cache, because the cache entries are un-gzipped in place. It should\n * *only* be called for artifacts that come from an actual http response.\n */\n checkIntegrity(): boolean {\n if (!this.#integrity) return false\n return this.integrityActual === this.#integrity\n }\n get integrityActual(): Integrity {\n if (this.#integrityActual) return this.#integrityActual\n const hash = createHash('sha512')\n for (const buf of this.#body) hash.update(buf)\n this.#integrityActual = `sha512-${hash.digest('base64')}`\n return this.#integrityActual\n }\n get integrity() {\n return this.#integrity\n }\n\n /**\n * Give it a key, and it'll return the buffer of that header value\n */\n getHeader(h: string) {\n return getRawHeader(this.#headers, h)\n }\n\n /**\n * Set a header to a specific value\n */\n setHeader(h: string, value: Buffer | string) {\n this.#headers = setRawHeader(this.#headers, h, value)\n }\n\n /**\n * Return the body of the entry as a Buffer\n */\n buffer(): Buffer {\n const b = this.#body[0]\n if (!b) return Buffer.allocUnsafe(0)\n if (this.#body.length === 1) return b\n const cat = Buffer.concat(this.#body, this.#bodyLength)\n this.#body = [cat]\n return cat\n }\n\n // return the buffer if it's a tarball, or the parsed\n // JSON if it's not.\n get body(): Buffer | Record<string, any> {\n return this.isJSON ? this.json() : this.buffer()\n }\n\n #isJSON?: boolean\n get isJSON(): boolean {\n if (this.#isJSON !== undefined) return this.#isJSON\n const ser = serializedHeader && this.getHeader(serializedHeader)\n if (ser) return (this.#isJSON = true)\n const ct = this.getHeader('content-type')?.toString()\n // if it says it's json, assume json\n if (ct) return /\\bjson\\b/.test(ct)\n const text = this.text()\n // don't cache, because we might just not have it yet.\n if (!text) return false\n // all registry json starts with {, and no tarball ever can.\n this.#isJSON = text.startsWith('{')\n if (this.#isJSON) this.setHeader('content-type', 'text/json')\n return this.#isJSON\n }\n\n #isGzip?: boolean\n get isGzip(): boolean {\n if (this.#isGzip !== undefined) return this.#isGzip\n const ce = this.getHeader('content-encoding')?.toString()\n if (ce && !/\\bgzip\\b/.test(ce)) return (this.#isGzip = false)\n const buf = this.buffer()\n if (buf.length < 2) return false\n this.#isGzip = buf[0] === 0x1f && buf[1] === 0x8b\n if (this.#isGzip) {\n this.setHeader('content-encoding', 'gzip')\n } else {\n this.setHeader('content-encoding', 'identity')\n this.setHeader('content-length', String(this.#bodyLength))\n }\n return this.#isGzip\n }\n\n /**\n * Un-gzip encode the body.\n * Returns true if it was previously gzip (so something was done), otherwise\n * returns false.\n */\n unzip() {\n if (this.isGzip) {\n // we know that if we know it's gzip, that the body has been\n // flattened to a single buffer, so save the extra call.\n /* c8 ignore start */\n if (this.#body[0] == null)\n throw error('Invalid buffer, cant unzip')\n /* c8 ignore stop */\n const b = gunzipSync(this.#body[0])\n this.setHeader('content-encoding', 'identity')\n this.#body = [b]\n this.#bodyLength = b.byteLength\n this.setHeader('content-length', String(this.#bodyLength))\n this.#isGzip = false\n return true\n }\n return false\n }\n\n /**\n * Return the body of the entry as utf8 text\n * Automatically unzips if the content is gzip encoded\n */\n text() {\n this.unzip()\n return this.buffer().toString()\n }\n\n /**\n * Parse the entry body as JSON and return the result\n */\n json(): Record<string, JSONField> {\n if (this.#json !== undefined) return this.#json\n const ser = serializedHeader && this.getHeader(serializedHeader)\n if (ser) {\n /* c8 ignore start - very rare, but theoretically possible to throw */\n try {\n return (this.#json = deserialize(ser))\n } catch {}\n /* c8 ignore stop */\n }\n const obj = JSON.parse(this.text())\n if (serializedHeader)\n this.setHeader(serializedHeader, serialize(obj))\n return obj\n }\n\n /**\n * Pass the contents of a @vltpkg/cache.Cache object as a buffer,\n * and this static method will decode it into a CacheEntry representing\n * the cached response.\n */\n static decode(buffer: Buffer): CacheEntry {\n if (buffer.length < 4) {\n return new CacheEntry(0, [])\n }\n const headSize = readSize(buffer, 0)\n if (buffer.length < headSize) {\n return new CacheEntry(0, [])\n }\n const statusCode = Number(buffer.subarray(4, 7).toString())\n const headersBuffer = buffer.subarray(7, headSize)\n // walk through the headers array, building up the rawHeaders Buffer[]\n const headers: Buffer[] = []\n let i = 0\n while (i < headersBuffer.length - 4) {\n const size = readSize(headersBuffer, i)\n headers.push(headersBuffer.subarray(i + 4, i + size))\n i += size\n }\n const c = new CacheEntry(statusCode, headers)\n const body = buffer.subarray(headSize)\n c.#body = [body]\n c.#bodyLength = body.byteLength\n c.setHeader('content-length', String(c.#bodyLength))\n if (c.isJSON) {\n try {\n c.json()\n } catch {\n return new CacheEntry(0, [])\n }\n }\n return c\n }\n\n /**\n * Encode the entry as a single Buffer for writing to the cache\n */\n // TODO: should this maybe not concat, and just return Buffer[]?\n // Then we can writev it to the cache file and save the memory copy\n encode(): Buffer {\n // store json results as a serialized object.\n if (this.isJSON) this.json()\n const sb = Buffer.from(String(this.#statusCode))\n const chunks: Buffer[] = [sb]\n let headLength = sb.byteLength + 4\n for (const h of this.#headers) {\n const hlBuf = Buffer.allocUnsafe(4)\n const hl = h.byteLength + 4\n headLength += hl\n hlBuf.set(\n [\n (hl >> 24) & 0xff,\n (hl >> 16) & 0xff,\n (hl >> 8) & 0xff,\n hl & 0xff,\n ],\n 0,\n )\n chunks.push(hlBuf, h)\n }\n\n const hlBuf = Buffer.allocUnsafe(4)\n hlBuf.set(\n [\n (headLength >> 24) & 0xff,\n (headLength >> 16) & 0xff,\n (headLength >> 8) & 0xff,\n headLength & 0xff,\n ],\n 0,\n )\n chunks.unshift(hlBuf)\n chunks.push(...this.#body)\n return Buffer.concat(chunks, headLength + this.#bodyLength)\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const isDeno: boolean;
|
|
2
|
+
export declare const isBun: boolean;
|
|
3
|
+
export declare const isNode: boolean;
|
|
4
|
+
export declare const bun: string | undefined;
|
|
5
|
+
export declare const deno: string | undefined;
|
|
6
|
+
export declare const node: string | undefined;
|
|
7
|
+
export declare const engine: {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
} | undefined;
|
|
11
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/env.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,MAAM,SAAc,CAAA;AACjC,eAAO,MAAM,KAAK,SAAwB,CAAA;AAG1C,eAAO,MAAM,MAAM,SAA+C,CAAA;AAGlE,eAAO,MAAM,GAAG,oBAAwC,CAAA;AACxD,eAAO,MAAM,IAAI,oBAA0C,CAAA;AAC3D,eAAO,MAAM,IAAI,oBAA0C,CAAA;AAE3D,eAAO,MAAM,MAAM;;;aAGN,CAAA"}
|
package/dist/esm/env.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import proc from 'node:process';
|
|
2
|
+
const { Deno, Bun } = globalThis;
|
|
3
|
+
const isObj = (v) => typeof v === 'object' && !!v;
|
|
4
|
+
export const isDeno = isObj(Deno);
|
|
5
|
+
export const isBun = !isDeno && isObj(Bun);
|
|
6
|
+
// bun and deno also report 'node' in process.versions so its only
|
|
7
|
+
// node if it is not bun or deno
|
|
8
|
+
export const isNode = !isDeno && !isBun && 'node' in proc.versions;
|
|
9
|
+
// All the runtimes put their versions into process.versions
|
|
10
|
+
export const bun = isBun ? proc.versions.bun : undefined;
|
|
11
|
+
export const deno = isDeno ? proc.versions.deno : undefined;
|
|
12
|
+
export const node = isNode ? proc.versions.node : undefined;
|
|
13
|
+
export const engine = isNode || isDeno ? { name: 'v8', version: proc.versions.v8 }
|
|
14
|
+
: isBun && bun ? { name: 'bun', version: bun }
|
|
15
|
+
: undefined;
|
|
16
|
+
//# sourceMappingURL=env.js.map
|