@renatorodrigues/cacheiro-store-gcs 1.0.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Renato Rodrigues
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # `@renatorodrigues/cacheiro-store-gcs`
2
+
3
+ Google Cloud Storage store for [`@renatorodrigues/cacheiro`](../cacheiro). Stores NX task artifacts in a GCS bucket.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { GcsStore } from '@renatorodrigues/cacheiro-store-gcs';
9
+
10
+ const store = new GcsStore({
11
+ bucket: 'my-nx-cache',
12
+ });
13
+
14
+ await store.mount();
15
+ ```
16
+
17
+ ## Config validation
18
+
19
+ This package exports a JSON Schema (draft-07) and a TypeScript type for the config shape. Use them to validate and cast a raw config object before constructing the store — the example below uses AJV, but any JSON Schema validator works:
20
+
21
+ ```ts
22
+ import { configSchema, type GcsStoreConfig } from '@renatorodrigues/cacheiro-store-gcs';
23
+ import { Ajv } from 'ajv';
24
+
25
+ const validate = new Ajv({ allErrors: true }).compile(configSchema);
26
+
27
+ // example — error handling is up to your runner
28
+ if (!validate(raw)) throw new Error('invalid store config');
29
+ const store = new GcsStore(raw as unknown as GcsStoreConfig);
30
+ ```
31
+
32
+ ## Development
33
+
34
+ ```sh
35
+ npm run watch # tsc --watch (hot rebuild)
36
+ npm run build # compile TypeScript
37
+ npm test # vitest run
38
+ npm run test:watch
39
+ npm run lint
40
+ npm run fmt
41
+ ```
42
+
43
+ ## Config
44
+
45
+ | Field | Type | Required | Description |
46
+ | --------------- | -------- | -------- | ------------------------------------------------------------------------------- |
47
+ | `bucket` | `string` | Yes | GCS bucket name. |
48
+ | `endpoint` | `string` | No | Custom GCS-compatible endpoint URL (e.g. for local emulators). |
49
+ | `prefix` | `string` | No | Key prefix for all cache entries. Useful when sharing a bucket across projects. |
50
+ | `encryptionKey` | `string` | No | AES-256-CBC encryption key. When set, all artifacts are encrypted at rest. |
51
+
52
+ See [Environment variables reference](#environment-variables-reference) for conventional env var names.
53
+
54
+ ## Encryption
55
+
56
+ GCS encrypts every object at rest by default with Google-managed keys — no configuration needed. For additional client-side encryption, set `encryptionKey`:
57
+
58
+ | `encryptionKey` | Behavior |
59
+ | --------------- | ------------------------------------------------------------ |
60
+ | unset | Google-managed server-side encryption only. |
61
+ | set | Client-side AES-256-CBC on top of Google-managed encryption. |
62
+
63
+ Client-side keys are stretched with `scrypt` (fixed salt, default cost) to a 32-byte AES key. The IV is randomly generated per write and prepended to the ciphertext: `[16-byte IV][ciphertext]`. The salt is scoped to this store (`cacheiro-store-gcs:v1`), so GCS and S3 stores with the same passphrase produce incompatible ciphertexts.
64
+
65
+ ## Environment variables reference
66
+
67
+ | Variable | Config field |
68
+ | -------------------- | --------------- |
69
+ | `GCS_BUCKET` | `bucket` |
70
+ | `GCS_ENDPOINT` | `endpoint` |
71
+ | `GCS_PREFIX` | `prefix` |
72
+ | `GCS_ENCRYPTION_KEY` | `encryptionKey` |
73
+
74
+ ---
75
+
76
+ <br/>
77
+ <p align="center">Crafted with 🤍 by a 🇧🇷 human in 🇩🇪, for the humans of the 🌐</p>
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "required": ["bucket"],
5
+ "additionalProperties": false,
6
+ "properties": {
7
+ "bucket": { "type": "string", "description": "GCS bucket name." },
8
+ "endpoint": { "type": "string", "description": "Custom GCS-compatible endpoint URL." },
9
+ "prefix": { "type": "string", "description": "Key prefix for all cache entries." },
10
+ "encryptionKey": {
11
+ "type": "string",
12
+ "description": "AES-256-CBC encryption key. Encrypts all artifacts at rest."
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ import { Transform, type TransformCallback } from 'node:stream';
2
+ export declare function deriveKey(key: string): Buffer;
3
+ export declare function encryptBuffer(data: Buffer, key: Buffer): Buffer;
4
+ export declare class DecryptTransform extends Transform {
5
+ private readonly key;
6
+ private decipher;
7
+ private ivBuf;
8
+ constructor(key: Buffer);
9
+ _transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
10
+ _flush(callback: TransformCallback): void;
11
+ }
@@ -0,0 +1,66 @@
1
+ import { Transform } from 'node:stream';
2
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
3
+ const ALGORITHM = 'aes-256-cbc';
4
+ const IV_LENGTH = 16;
5
+ const KEY_LENGTH = 32;
6
+ const KEY_SALT = Buffer.from('cacheiro-store-gcs:v1', 'utf8');
7
+ export function deriveKey(key) {
8
+ return scryptSync(key, KEY_SALT, KEY_LENGTH);
9
+ }
10
+ export function encryptBuffer(data, key) {
11
+ const iv = randomBytes(IV_LENGTH);
12
+ const cipher = createCipheriv(ALGORITHM, key, iv);
13
+ return Buffer.concat([iv, cipher.update(data), cipher.final()]);
14
+ }
15
+ function mapDecryptError(err) {
16
+ if (err &&
17
+ typeof err === 'object' &&
18
+ 'code' in err &&
19
+ err.code === 'ERR_OSSL_BAD_DECRYPT') {
20
+ return new Error('GcsStore: failed to decrypt — encryption key may be incorrect');
21
+ }
22
+ return err instanceof Error ? err : new Error(String(err));
23
+ }
24
+ export class DecryptTransform extends Transform {
25
+ key;
26
+ decipher = null;
27
+ ivBuf = Buffer.alloc(0);
28
+ constructor(key) {
29
+ super();
30
+ this.key = key;
31
+ }
32
+ _transform(chunk, _encoding, callback) {
33
+ try {
34
+ if (!this.decipher) {
35
+ this.ivBuf = Buffer.concat([this.ivBuf, chunk]);
36
+ if (this.ivBuf.length < IV_LENGTH) {
37
+ callback();
38
+ return;
39
+ }
40
+ const iv = this.ivBuf.subarray(0, IV_LENGTH);
41
+ const rest = this.ivBuf.subarray(IV_LENGTH);
42
+ this.decipher = createDecipheriv(ALGORITHM, this.key, iv);
43
+ if (rest.length > 0)
44
+ this.push(this.decipher.update(rest));
45
+ }
46
+ else {
47
+ this.push(this.decipher.update(chunk));
48
+ }
49
+ callback();
50
+ }
51
+ catch (err) {
52
+ callback(err);
53
+ }
54
+ }
55
+ _flush(callback) {
56
+ try {
57
+ if (this.decipher)
58
+ this.push(this.decipher.final());
59
+ callback();
60
+ }
61
+ catch (err) {
62
+ callback(mapDecryptError(err));
63
+ }
64
+ }
65
+ }
66
+ //# sourceMappingURL=encryption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.js","sourceRoot":"","sources":["../src/encryption.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function isNotFound(err: unknown): boolean;
2
+ export declare function classifyError(err: unknown, op: 'read' | 'write' | 'head', bucket: string): Error;
package/dist/errors.js ADDED
@@ -0,0 +1,45 @@
1
+ function asGcsError(err) {
2
+ return err && typeof err === 'object' ? err : null;
3
+ }
4
+ function unwrapJsonMessage(message) {
5
+ try {
6
+ const parsed = JSON.parse(message);
7
+ if (parsed && parsed.error && typeof parsed.error.message === 'string') {
8
+ return parsed.error.message;
9
+ }
10
+ }
11
+ catch {
12
+ // not JSON — return null
13
+ }
14
+ return null;
15
+ }
16
+ export function isNotFound(err) {
17
+ const e = asGcsError(err);
18
+ return e?.code === 404;
19
+ }
20
+ export function classifyError(err, op, bucket) {
21
+ const e = asGcsError(err);
22
+ if (!e)
23
+ return err instanceof Error ? err : new Error(String(err));
24
+ if (e.code === 'ERR_OSSL_BAD_DECRYPT') {
25
+ return new Error('GcsStore: failed to decrypt — encryption key may be incorrect');
26
+ }
27
+ if (e.code === 404) {
28
+ if (op === 'write')
29
+ return new Error(`GcsStore: bucket "${bucket}" not found`);
30
+ return new Error(`GcsStore: object not found`);
31
+ }
32
+ if (e.code === 403 || e.code === 401) {
33
+ return new Error(`GcsStore: access denied for ${op}`);
34
+ }
35
+ if (typeof e.message === 'string') {
36
+ if (e.message.toLowerCase().includes('credentials')) {
37
+ return new Error(`GcsStore: access denied for ${op}`);
38
+ }
39
+ const unwrapped = unwrapJsonMessage(e.message);
40
+ if (unwrapped)
41
+ return new Error(`GcsStore: ${unwrapped}`);
42
+ }
43
+ return err instanceof Error ? err : new Error(String(err));
44
+ }
45
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAKA,SAAS,UAAU,CAAC,GAAY;IAC9B,OAAO,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAoB,CAAC,CAAC,CAAC,IAAI,CAAC;AACvE,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAe;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAqC,CAAC;QACvE,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACvE,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QAC9B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,yBAAyB;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO,CAAC,EAAE,IAAI,KAAK,GAAG,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAY,EAAE,EAA6B,EAAE,MAAc;IACvF,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,CAAC;QAAE,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAEnE,IAAI,CAAC,CAAC,IAAI,KAAK,sBAAsB,EAAE,CAAC;QACtC,OAAO,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC;QACnB,IAAI,EAAE,KAAK,OAAO;YAAE,OAAO,IAAI,KAAK,CAAC,qBAAqB,MAAM,aAAa,CAAC,CAAC;QAC/E,OAAO,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC;QACrC,OAAO,IAAI,KAAK,CAAC,+BAA+B,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAClC,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACpD,OAAO,IAAI,KAAK,CAAC,+BAA+B,EAAE,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,SAAS;YAAE,OAAO,IAAI,KAAK,CAAC,aAAa,SAAS,EAAE,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type Readable } from 'node:stream';
2
+ import type { CacheiroStore, Describable } from '@renatorodrigues/cacheiro-types';
3
+ import configSchema from '../configSchema.json';
4
+ export { configSchema };
5
+ export interface GcsStoreConfig {
6
+ bucket: string;
7
+ endpoint?: string;
8
+ prefix?: string;
9
+ encryptionKey?: string;
10
+ }
11
+ export declare class GcsStore implements CacheiroStore, Describable {
12
+ private readonly config;
13
+ private storage;
14
+ private encryptionKey;
15
+ constructor(config: GcsStoreConfig);
16
+ mount(): Promise<void>;
17
+ unmount(): void;
18
+ describe(): [string, string][];
19
+ exists(hash: string): Promise<boolean>;
20
+ write(hash: string, data: Buffer): Promise<void>;
21
+ read(hash: string): Readable;
22
+ private fetchInto;
23
+ private buildKey;
24
+ private requireStorage;
25
+ }
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ import { PassThrough } from 'node:stream';
2
+ import { posix } from 'node:path';
3
+ import { Storage } from '@google-cloud/storage';
4
+ import configSchema from '../configSchema.json' with { type: 'json' };
5
+ import { DecryptTransform, deriveKey, encryptBuffer } from './encryption.js';
6
+ import { classifyError, isNotFound } from './errors.js';
7
+ export { configSchema };
8
+ export class GcsStore {
9
+ config;
10
+ storage;
11
+ encryptionKey;
12
+ constructor(config) {
13
+ this.config = config;
14
+ }
15
+ async mount() {
16
+ if (!this.config.bucket)
17
+ throw new Error('GcsStore: "bucket" is required');
18
+ if (typeof this.config.encryptionKey === 'string' && this.config.encryptionKey.length > 0) {
19
+ this.encryptionKey = deriveKey(this.config.encryptionKey);
20
+ }
21
+ const options = {};
22
+ if (this.config.endpoint)
23
+ options.apiEndpoint = this.config.endpoint;
24
+ this.storage = new Storage(options);
25
+ }
26
+ unmount() {
27
+ this.storage = undefined;
28
+ }
29
+ describe() {
30
+ const rows = [['bucket', this.config.bucket]];
31
+ if (this.config.endpoint)
32
+ rows.push(['endpoint', this.config.endpoint]);
33
+ if (this.config.prefix)
34
+ rows.push(['prefix', this.config.prefix]);
35
+ rows.push(['encryption', this.encryptionKey ? 'client-side aes-256-cbc' : 'none']);
36
+ return rows;
37
+ }
38
+ async exists(hash) {
39
+ const file = this.requireStorage().bucket(this.config.bucket).file(this.buildKey(hash));
40
+ try {
41
+ const [exists] = await file.exists();
42
+ return exists;
43
+ }
44
+ catch (err) {
45
+ if (isNotFound(err))
46
+ return false;
47
+ throw classifyError(err, 'head', this.config.bucket);
48
+ }
49
+ }
50
+ async write(hash, data) {
51
+ const body = this.encryptionKey ? encryptBuffer(data, this.encryptionKey) : data;
52
+ const file = this.requireStorage().bucket(this.config.bucket).file(this.buildKey(hash));
53
+ try {
54
+ await file.save(body);
55
+ }
56
+ catch (err) {
57
+ throw classifyError(err, 'write', this.config.bucket);
58
+ }
59
+ }
60
+ read(hash) {
61
+ const out = new PassThrough();
62
+ void this.fetchInto(hash, out);
63
+ return out;
64
+ }
65
+ async fetchInto(hash, out) {
66
+ let storage;
67
+ try {
68
+ storage = this.requireStorage();
69
+ }
70
+ catch (err) {
71
+ out.destroy(err);
72
+ return;
73
+ }
74
+ try {
75
+ const body = storage
76
+ .bucket(this.config.bucket)
77
+ .file(this.buildKey(hash))
78
+ .createReadStream();
79
+ const onErr = (e) => out.destroy(classifyError(e, 'read', this.config.bucket));
80
+ if (this.encryptionKey) {
81
+ const decrypt = new DecryptTransform(this.encryptionKey);
82
+ body.on('error', onErr);
83
+ decrypt.on('error', onErr);
84
+ body.pipe(decrypt).pipe(out);
85
+ }
86
+ else {
87
+ body.on('error', onErr);
88
+ body.pipe(out);
89
+ }
90
+ }
91
+ catch (err) {
92
+ out.destroy(classifyError(err, 'read', this.config.bucket));
93
+ }
94
+ }
95
+ buildKey(hash) {
96
+ if (!hash || hash.includes('/') || hash.includes('\\') || hash.includes('..')) {
97
+ throw new Error('Invalid hash');
98
+ }
99
+ return this.config.prefix ? posix.join(this.config.prefix, hash) : hash;
100
+ }
101
+ requireStorage() {
102
+ if (!this.storage)
103
+ throw new Error('GcsStore: not mounted — call mount() first');
104
+ return this.storage;
105
+ }
106
+ }
107
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@renatorodrigues/cacheiro-store-gcs",
3
+ "version": "1.0.0",
4
+ "description": "Google Cloud Storage store implementation for @renatorodrigues/cacheiro",
5
+ "homepage": "https://github.com/rerodrigues/nx-remote-cache-server/tree/main/packages/cacheiro-store-gcs#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/rerodrigues/nx-remote-cache-server/issues"
8
+ },
9
+ "license": "MIT",
10
+ "author": {
11
+ "name": "Renato Rodrigues",
12
+ "email": "renato@renatorodrigues.com",
13
+ "url": "http://renatorodrigues.com/"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git@github.com:rerodrigues/nx-remote-cache-server.git"
18
+ },
19
+ "directories": {
20
+ "lib": "dist",
21
+ "test": "__tests__"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "configSchema.json"
26
+ ],
27
+ "type": "module",
28
+ "main": "dist/index.js",
29
+ "types": "dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ }
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "watch": "tsc --watch",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "lint": "oxlint",
45
+ "lint:fix": "oxlint --fix",
46
+ "fmt": "oxfmt",
47
+ "fmt:check": "oxfmt --check"
48
+ },
49
+ "dependencies": {
50
+ "@google-cloud/storage": "^7.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@renatorodrigues/cacheiro-types": "1.0.0",
54
+ "@types/node": "^22",
55
+ "lint-staged": "^16.4.0",
56
+ "oxfmt": "^0.53.0",
57
+ "oxlint": "^1.68.0",
58
+ "typescript": "^6.0.3",
59
+ "vitest": "^4.1.8"
60
+ },
61
+ "engines": {
62
+ "node": ">=22"
63
+ }
64
+ }