@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.
Files changed (52) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +122 -0
  3. package/dist/esm/add-header.d.ts +2 -0
  4. package/dist/esm/add-header.d.ts.map +1 -0
  5. package/dist/esm/add-header.js +24 -0
  6. package/dist/esm/add-header.js.map +1 -0
  7. package/dist/esm/cache-entry.d.ts +69 -0
  8. package/dist/esm/cache-entry.d.ts.map +1 -0
  9. package/dist/esm/cache-entry.js +335 -0
  10. package/dist/esm/cache-entry.js.map +1 -0
  11. package/dist/esm/env.d.ts +11 -0
  12. package/dist/esm/env.d.ts.map +1 -0
  13. package/dist/esm/env.js +16 -0
  14. package/dist/esm/env.js.map +1 -0
  15. package/dist/esm/get-header.d.ts +2 -0
  16. package/dist/esm/get-header.d.ts.map +1 -0
  17. package/dist/esm/get-header.js +37 -0
  18. package/dist/esm/get-header.js.map +1 -0
  19. package/dist/esm/handle-304-response.d.ts +4 -0
  20. package/dist/esm/handle-304-response.d.ts.map +1 -0
  21. package/dist/esm/handle-304-response.js +10 -0
  22. package/dist/esm/handle-304-response.js.map +1 -0
  23. package/dist/esm/index.d.ts +55 -0
  24. package/dist/esm/index.d.ts.map +1 -0
  25. package/dist/esm/index.js +122 -0
  26. package/dist/esm/index.js.map +1 -0
  27. package/dist/esm/is-cacheable.d.ts +5 -0
  28. package/dist/esm/is-cacheable.d.ts.map +1 -0
  29. package/dist/esm/is-cacheable.js +10 -0
  30. package/dist/esm/is-cacheable.js.map +1 -0
  31. package/dist/esm/package.json +3 -0
  32. package/dist/esm/raw-header.d.ts +9 -0
  33. package/dist/esm/raw-header.d.ts.map +1 -0
  34. package/dist/esm/raw-header.js +35 -0
  35. package/dist/esm/raw-header.js.map +1 -0
  36. package/dist/esm/redirect.d.ts +23 -0
  37. package/dist/esm/redirect.d.ts.map +1 -0
  38. package/dist/esm/redirect.js +60 -0
  39. package/dist/esm/redirect.js.map +1 -0
  40. package/dist/esm/serdes.d.ts +15 -0
  41. package/dist/esm/serdes.d.ts.map +1 -0
  42. package/dist/esm/serdes.js +19 -0
  43. package/dist/esm/serdes.js.map +1 -0
  44. package/dist/esm/set-cache-headers.d.ts +4 -0
  45. package/dist/esm/set-cache-headers.d.ts.map +1 -0
  46. package/dist/esm/set-cache-headers.js +17 -0
  47. package/dist/esm/set-cache-headers.js.map +1 -0
  48. package/dist/esm/set-raw-header.d.ts +6 -0
  49. package/dist/esm/set-raw-header.d.ts.map +1 -0
  50. package/dist/esm/set-raw-header.js +20 -0
  51. package/dist/esm/set-raw-header.js.map +1 -0
  52. 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"}
@@ -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