@eik/sink-gcs 1.2.31 → 2.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/CHANGELOG.md +19 -0
- package/README.md +34 -35
- package/lib/main.js +233 -207
- package/package.json +26 -19
- package/types/main.d.ts +39 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# [2.0.0](https://github.com/eik-lib/sink-gcs/compare/v1.2.32...v2.0.0) (2024-11-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* update dependencies ([#280](https://github.com/eik-lib/sink-gcs/issues/280)) ([8f2568a](https://github.com/eik-lib/sink-gcs/commit/8f2568aede7dd77e827cec575feb5b6f38070725))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### BREAKING CHANGES
|
|
10
|
+
|
|
11
|
+
* Requires Node >=20.5.0
|
|
12
|
+
|
|
13
|
+
## [1.2.32](https://github.com/eik-lib/sink-gcs/compare/v1.2.31...v1.2.32) (2024-08-09)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* add type definitions ([a24ea1e](https://github.com/eik-lib/sink-gcs/commit/a24ea1e2ed8d66b0bb4b1c3ef44640adaa56bac0))
|
|
19
|
+
|
|
1
20
|
## [1.2.31](https://github.com/eik-lib/sink-gcs/compare/v1.2.30...v1.2.31) (2024-07-03)
|
|
2
21
|
|
|
3
22
|
|
package/README.md
CHANGED
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
A sink for writing to and reading from [Google Cloud Storage][gcs].
|
|
4
4
|
|
|
5
|
-
[](https://david-dm.org/eik-lib/sink-gcs)
|
|
6
|
-
[](https://github.com/eik-lib/sink-gcs/actions?query=workflow%3A%22Run+Lint+and+Tests%22)
|
|
7
|
-
[](https://snyk.io/test/github/eik-lib/sink-gcs?targetFile=package.json)
|
|
8
|
-
|
|
9
5
|
The intention of the [Eik][eik] sink modules is to be able to write to and read from
|
|
10
6
|
files in different storage backends by swapping sink modules. Because each sink
|
|
11
7
|
implements the same public API it is possible to use this sink in one environment and
|
|
@@ -22,22 +18,25 @@ $ npm install @eik/sink-gcs
|
|
|
22
18
|
Read a file from [Google Cloud Storage][gcs] and serve it on HTTP:
|
|
23
19
|
|
|
24
20
|
```js
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
import { pipeline } from 'node:stream';
|
|
22
|
+
import express from 'express';
|
|
23
|
+
import Sink from '@eik/sink-gcs';
|
|
28
24
|
|
|
29
25
|
const app = express();
|
|
30
|
-
const sink = new Sink(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
const sink = new Sink(
|
|
27
|
+
{
|
|
28
|
+
credentials: {
|
|
29
|
+
client_email: 'a@email.address',
|
|
30
|
+
private_key: '[ ...snip... ]',
|
|
31
|
+
projectId: 'myProject',
|
|
32
|
+
},
|
|
35
33
|
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
34
|
+
{
|
|
35
|
+
writeTimeout: 20000,
|
|
36
|
+
},
|
|
37
|
+
);
|
|
39
38
|
|
|
40
|
-
app.get(
|
|
39
|
+
app.get('/file.js', async (req, res, next) => {
|
|
41
40
|
try {
|
|
42
41
|
const file = await sink.read('/path/to/file/file.js');
|
|
43
42
|
pipeline(file.stream, res, (error) => {
|
|
@@ -56,7 +55,7 @@ app.listen(8000);
|
|
|
56
55
|
Create a new Sink instance.
|
|
57
56
|
|
|
58
57
|
```js
|
|
59
|
-
|
|
58
|
+
import Sink from '@eik/sink-gcs';
|
|
60
59
|
|
|
61
60
|
const sink = new Sink({
|
|
62
61
|
credentials: {
|
|
@@ -71,12 +70,12 @@ const sink = new Sink({
|
|
|
71
70
|
|
|
72
71
|
This constructor takes the following arguments:
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
- `storageOptions` - Object - A Google Cloud Storage [storage options object][gcs-storage-options] - Required.
|
|
74
|
+
- `options` - Object - Other options related to storage and behavior - Optional.
|
|
75
|
+
- `writeTimeout` - Number - Timeout, in milliseconds, for write operations to the sink - Default: `60000` - Optional.
|
|
76
|
+
- `writeGzip` - Boolean - If files should be written with gzip compression - Default: `false` - Optional.
|
|
77
|
+
- `rootPath` - String - Root directory for where to store files in the GCS bucket - Default: `eik` - Optional.
|
|
78
|
+
- `bucket` - String - Name of the bucket to store files in - Default: `eik_files` - Optional.
|
|
80
79
|
|
|
81
80
|
## API
|
|
82
81
|
|
|
@@ -88,8 +87,8 @@ Async method for writing a file to storage.
|
|
|
88
87
|
|
|
89
88
|
This method takes the following arguments:
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
- `filePath` - String - Path to the file to be stored - Required.
|
|
91
|
+
- `contentType` - String - The content type of the file - Required.
|
|
93
92
|
|
|
94
93
|
Resolves with a writable stream.
|
|
95
94
|
|
|
@@ -115,14 +114,14 @@ Async method for reading a file from storage.
|
|
|
115
114
|
|
|
116
115
|
This method takes the following arguments:
|
|
117
116
|
|
|
118
|
-
|
|
117
|
+
- `filePath` - String - Path to the file to be read - Required.
|
|
119
118
|
|
|
120
119
|
Resolves with a [ReadFile][read-file] object which holds metadata about
|
|
121
120
|
the file and a readable stream with the byte stream of the file on the
|
|
122
121
|
`.stream` property.
|
|
123
122
|
|
|
124
123
|
```js
|
|
125
|
-
|
|
124
|
+
import { pipeline } from 'node:stream';
|
|
126
125
|
|
|
127
126
|
const toStream = new SomeWritableStream();
|
|
128
127
|
const sink = new Sink({ ... });
|
|
@@ -143,7 +142,7 @@ Async method for deleting a file in storage.
|
|
|
143
142
|
|
|
144
143
|
This method takes the following arguments:
|
|
145
144
|
|
|
146
|
-
|
|
145
|
+
- `filePath` - String - Path to the file to be deleted - Required.
|
|
147
146
|
|
|
148
147
|
Resolves if file is deleted and rejects if file could not be deleted.
|
|
149
148
|
|
|
@@ -163,7 +162,7 @@ Async method for checking if a file exist in the storage.
|
|
|
163
162
|
|
|
164
163
|
This method takes the following arguments:
|
|
165
164
|
|
|
166
|
-
|
|
165
|
+
- `filePath` - String - Path to the file to be checked for existence - Required.
|
|
167
166
|
|
|
168
167
|
Resolves if file exists and rejects if file does not exist.
|
|
169
168
|
|
|
@@ -216,13 +215,13 @@ The stream will emit an event of the following character for each metric:
|
|
|
216
215
|
|
|
217
216
|
The metric will have the following labels:
|
|
218
217
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
- `operation` - `String` - The operation which triggered the metric. Can be `write`, `read`, `delete` or `exist`.
|
|
219
|
+
- `success` - `Boolean` - If the operation was successfull in terms of being a valid operation and running the operation against the Google Cloud Storage without erroring.
|
|
220
|
+
- `access` - `Boolean` - If the operation triggered access to the Google Cloud Storage.
|
|
222
221
|
|
|
223
|
-
Do note that the `access` label is `true` when the Sink runs an operation against the
|
|
222
|
+
Do note that the `access` label is `true` when the Sink runs an operation against the
|
|
224
223
|
Google Cloud Storage which can generate a cost. In other words; this can be used to
|
|
225
|
-
monitor excessive access to prevent cost.
|
|
224
|
+
monitor excessive access to prevent cost.
|
|
226
225
|
|
|
227
226
|
## License
|
|
228
227
|
|
|
@@ -250,4 +249,4 @@ SOFTWARE.
|
|
|
250
249
|
[gcs-auth]: https://googlecloudplatform.github.io/google-cloud-node/#/docs/google-cloud/0.50.0/google-cloud
|
|
251
250
|
[gcs-storage-options]: https://googleapis.dev/nodejs/storage/latest/global.html#StorageOptions
|
|
252
251
|
[gcs]: https://cloud.google.com/storage/
|
|
253
|
-
[read-file]: https://github.com/eik-lib/common/blob/
|
|
252
|
+
[read-file]: https://github.com/eik-lib/common/blob/main/lib/classes/read-file.js
|
package/lib/main.js
CHANGED
|
@@ -1,216 +1,242 @@
|
|
|
1
|
-
import { Storage } from
|
|
2
|
-
import { ReadFile } from
|
|
3
|
-
import Metrics from
|
|
4
|
-
import Sink from
|
|
5
|
-
import path from
|
|
1
|
+
import { Storage } from "@google-cloud/storage";
|
|
2
|
+
import { ReadFile } from "@eik/common";
|
|
3
|
+
import Metrics from "@metrics/client";
|
|
4
|
+
import Sink from "@eik/sink";
|
|
5
|
+
import path from "node:path";
|
|
6
6
|
|
|
7
|
-
const DEFAULT_ROOT_PATH =
|
|
8
|
-
const DEFAULT_BUCKET =
|
|
7
|
+
const DEFAULT_ROOT_PATH = "eik";
|
|
8
|
+
const DEFAULT_BUCKET = "eik_files";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} SinkGCSOptions
|
|
12
|
+
* @property {number} [writeTimeout=60_000]
|
|
13
|
+
* @property {boolean} [writeGzip=false]
|
|
14
|
+
* @property {string} [rootPath="eik"]
|
|
15
|
+
* @property {string} [bucket="eik_files"]
|
|
16
|
+
*/
|
|
9
17
|
|
|
10
18
|
/**
|
|
11
19
|
* A sink for uploading files to Google Cloud Storage
|
|
12
20
|
* https://googleapis.dev/nodejs/storage/latest/
|
|
13
|
-
*
|
|
14
|
-
* @class SinkGCS
|
|
15
21
|
*/
|
|
16
|
-
|
|
17
22
|
const SinkGCS = class SinkGCS extends Sink {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @param {import('@google-cloud/storage').StorageOptions} storageOptions
|
|
26
|
+
* @param {SinkGCSOptions} sinkOptions
|
|
27
|
+
*/
|
|
28
|
+
constructor(
|
|
29
|
+
storageOptions,
|
|
30
|
+
{
|
|
31
|
+
writeTimeout = 60000,
|
|
32
|
+
writeGzip = false,
|
|
33
|
+
rootPath = DEFAULT_ROOT_PATH,
|
|
34
|
+
bucket = DEFAULT_BUCKET,
|
|
35
|
+
} = {},
|
|
36
|
+
) {
|
|
37
|
+
super();
|
|
38
|
+
if (typeof storageOptions !== "object" || storageOptions === null)
|
|
39
|
+
throw new Error('"storageOptions" argument must be provided');
|
|
40
|
+
this._writeTimeout = writeTimeout;
|
|
41
|
+
this._writeGzip = writeGzip;
|
|
42
|
+
this._rootPath = rootPath;
|
|
43
|
+
this._storage = new Storage(storageOptions);
|
|
44
|
+
this._bucket = this._storage.bucket(bucket);
|
|
45
|
+
this._metrics = new Metrics();
|
|
46
|
+
this._counter = this._metrics.counter({
|
|
47
|
+
name: "eik_core_sink_gcs",
|
|
48
|
+
description: "Counter measuring access to the Google Cloud Storage sink",
|
|
49
|
+
labels: {
|
|
50
|
+
operation: "n/a",
|
|
51
|
+
success: false,
|
|
52
|
+
access: false,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get metrics() {
|
|
58
|
+
return this._metrics;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
write(filePath, contentType) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const operation = "write";
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
Sink.validateFilePath(filePath);
|
|
67
|
+
Sink.validateContentType(contentType);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
this._counter.inc({ labels: { operation } });
|
|
70
|
+
reject(error);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
75
|
+
|
|
76
|
+
if (pathname.indexOf(this._rootPath) !== 0) {
|
|
77
|
+
this._counter.inc({ labels: { operation } });
|
|
78
|
+
reject(new Error(`Directory traversal - ${filePath}`));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const src = this._bucket.file(pathname);
|
|
83
|
+
const gcsStream = src.createWriteStream({
|
|
84
|
+
resumable: false,
|
|
85
|
+
metadata: {
|
|
86
|
+
cacheControl: "public, max-age=31536000",
|
|
87
|
+
contentType,
|
|
88
|
+
},
|
|
89
|
+
timeout: this._writeTimeout,
|
|
90
|
+
gzip: this._writeGzip,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
gcsStream.on("error", () => {
|
|
94
|
+
this._counter.inc({ labels: { access: true, operation } });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
gcsStream.on("finish", () => {
|
|
98
|
+
this._counter.inc({
|
|
99
|
+
labels: { success: true, access: true, operation },
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
resolve(gcsStream);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
read(filePath) {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const operation = "read";
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
Sink.validateFilePath(filePath);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this._counter.inc({ labels: { operation } });
|
|
115
|
+
reject(error);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
120
|
+
|
|
121
|
+
if (pathname.indexOf(this._rootPath) !== 0) {
|
|
122
|
+
this._counter.inc({ labels: { operation } });
|
|
123
|
+
reject(new Error(`Directory traversal - ${filePath}`));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let streamClosed = true;
|
|
128
|
+
|
|
129
|
+
const src = this._bucket.file(pathname);
|
|
130
|
+
const gcsStream = src.createReadStream();
|
|
131
|
+
|
|
132
|
+
gcsStream.on("readable", () => {
|
|
133
|
+
gcsStream.read();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
gcsStream.on("error", (error) => {
|
|
137
|
+
if (streamClosed) {
|
|
138
|
+
this._counter.inc({ labels: { access: true, operation } });
|
|
139
|
+
reject(error);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
gcsStream.on("response", (response) => {
|
|
144
|
+
this._counter.inc({
|
|
145
|
+
labels: { success: true, access: true, operation },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (response.statusCode === 200) {
|
|
149
|
+
streamClosed = false;
|
|
150
|
+
const obj = new ReadFile({
|
|
151
|
+
mimeType: response.headers["content-type"],
|
|
152
|
+
etag: response.headers.etag,
|
|
153
|
+
});
|
|
154
|
+
obj.stream = gcsStream;
|
|
155
|
+
resolve(obj);
|
|
156
|
+
} else {
|
|
157
|
+
reject(
|
|
158
|
+
new Error(
|
|
159
|
+
`Could not read file. Got http status code ${response.statusCode} from GCS`,
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
delete(filePath) {
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
const operation = "delete";
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
Sink.validateFilePath(filePath);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
this._counter.inc({ labels: { operation } });
|
|
175
|
+
reject(error);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
180
|
+
|
|
181
|
+
if (pathname.indexOf(this._rootPath) !== 0) {
|
|
182
|
+
this._counter.inc({ labels: { operation } });
|
|
183
|
+
reject(new Error(`Directory traversal - ${filePath}`));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this._counter.inc({
|
|
188
|
+
labels: { success: true, access: true, operation },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
this._bucket.deleteFiles(
|
|
192
|
+
{
|
|
193
|
+
prefix: pathname,
|
|
194
|
+
force: true,
|
|
195
|
+
},
|
|
196
|
+
(error) => {
|
|
197
|
+
if (error) return reject(error);
|
|
198
|
+
return resolve();
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
exist(filePath) {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
const operation = "exist";
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
Sink.validateFilePath(filePath);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
this._counter.inc({ labels: { operation } });
|
|
212
|
+
reject(error);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
217
|
+
|
|
218
|
+
if (pathname.indexOf(this._rootPath) !== 0) {
|
|
219
|
+
this._counter.inc({ labels: { operation } });
|
|
220
|
+
reject(new Error(`Directory traversal - ${filePath}`));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this._counter.inc({
|
|
225
|
+
labels: { success: true, access: true, operation },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const src = this._bucket.file(pathname);
|
|
229
|
+
|
|
230
|
+
src.exists((error, exists) => {
|
|
231
|
+
if (error) return reject(error);
|
|
232
|
+
if (exists) return resolve();
|
|
233
|
+
return reject();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
get [Symbol.toStringTag]() {
|
|
239
|
+
return "SinkGCS";
|
|
240
|
+
}
|
|
215
241
|
};
|
|
216
242
|
export default SinkGCS;
|
package/package.json
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eik/sink-gcs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Sink for Google Cloud Storage",
|
|
5
5
|
"main": "lib/main.js",
|
|
6
|
+
"types": "./types/main.d.ts",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"files": [
|
|
8
9
|
"CHANGELOG.md",
|
|
9
10
|
"package.json",
|
|
10
11
|
"README.md",
|
|
11
|
-
"lib"
|
|
12
|
+
"lib",
|
|
13
|
+
"types"
|
|
12
14
|
],
|
|
13
15
|
"scripts": {
|
|
16
|
+
"clean": "rimraf .tap node_modules types",
|
|
14
17
|
"test": "tap --disable-coverage --allow-empty-coverage",
|
|
15
|
-
"test:snapshots
|
|
18
|
+
"test:snapshots": "tap test/**/*.js --snapshot",
|
|
16
19
|
"lint:fix": "eslint --fix .",
|
|
17
|
-
"lint": "eslint ."
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"types": "run-s types:module types:test",
|
|
22
|
+
"types:module": "tsc",
|
|
23
|
+
"types:test": "tsc --project tsconfig.test.json"
|
|
18
24
|
},
|
|
19
25
|
"repository": {
|
|
20
26
|
"type": "git",
|
|
@@ -27,23 +33,24 @@
|
|
|
27
33
|
},
|
|
28
34
|
"homepage": "https://github.com/eik-lib/sink-gcs#readme",
|
|
29
35
|
"dependencies": {
|
|
30
|
-
"@eik/common": "
|
|
31
|
-
"@eik/sink": "1.2.
|
|
36
|
+
"@eik/common": "5.0.0",
|
|
37
|
+
"@eik/sink": "1.2.5",
|
|
32
38
|
"@google-cloud/storage": "6.12.0",
|
|
33
|
-
"@metrics/client": "
|
|
39
|
+
"@metrics/client": "2.5.4"
|
|
34
40
|
},
|
|
35
41
|
"devDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
38
|
-
"@semantic-release
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"eslint
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"semantic-release": "
|
|
46
|
-
"tap": "
|
|
47
|
-
"
|
|
42
|
+
"@eik/eslint-config": "1.0.2",
|
|
43
|
+
"@eik/prettier-config": "1.0.1",
|
|
44
|
+
"@eik/semantic-release-config": "1.0.0",
|
|
45
|
+
"@eik/typescript-config": "1.0.0",
|
|
46
|
+
"@types/readable-stream": "4.0.18",
|
|
47
|
+
"eslint": "9.14.0",
|
|
48
|
+
"npm-run-all2": "7.0.1",
|
|
49
|
+
"prettier": "3.3.3",
|
|
50
|
+
"rimraf": "6.0.1",
|
|
51
|
+
"semantic-release": "24.2.0",
|
|
52
|
+
"tap": "21.0.1",
|
|
53
|
+
"typescript": "5.6.3",
|
|
54
|
+
"unique-slug": "5.0.0"
|
|
48
55
|
}
|
|
49
56
|
}
|
package/types/main.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export default SinkGCS;
|
|
2
|
+
export type SinkGCSOptions = {
|
|
3
|
+
writeTimeout?: number;
|
|
4
|
+
writeGzip?: boolean;
|
|
5
|
+
rootPath?: string;
|
|
6
|
+
bucket?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} SinkGCSOptions
|
|
10
|
+
* @property {number} [writeTimeout=60_000]
|
|
11
|
+
* @property {boolean} [writeGzip=false]
|
|
12
|
+
* @property {string} [rootPath="eik"]
|
|
13
|
+
* @property {string} [bucket="eik_files"]
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* A sink for uploading files to Google Cloud Storage
|
|
17
|
+
* https://googleapis.dev/nodejs/storage/latest/
|
|
18
|
+
*/
|
|
19
|
+
declare const SinkGCS: {
|
|
20
|
+
new (storageOptions: import("@google-cloud/storage").StorageOptions, { writeTimeout, writeGzip, rootPath, bucket, }?: SinkGCSOptions): {
|
|
21
|
+
_writeTimeout: number;
|
|
22
|
+
_writeGzip: boolean;
|
|
23
|
+
_rootPath: string;
|
|
24
|
+
_storage: Storage;
|
|
25
|
+
_bucket: import("@google-cloud/storage").Bucket;
|
|
26
|
+
_metrics: Metrics;
|
|
27
|
+
_counter: Metrics.MetricsCounter;
|
|
28
|
+
readonly metrics: Metrics;
|
|
29
|
+
write(filePath: any, contentType: any): Promise<any>;
|
|
30
|
+
read(filePath: any): Promise<any>;
|
|
31
|
+
delete(filePath: any): Promise<any>;
|
|
32
|
+
exist(filePath: any): Promise<any>;
|
|
33
|
+
readonly [Symbol.toStringTag]: string;
|
|
34
|
+
};
|
|
35
|
+
validateFilePath(filePath: string): string;
|
|
36
|
+
validateContentType(contentType: string): string;
|
|
37
|
+
};
|
|
38
|
+
import { Storage } from "@google-cloud/storage";
|
|
39
|
+
import Metrics from "@metrics/client";
|