@eik/sink-file-system 1.0.0 → 1.0.2

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 (3) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/lib/main.js +307 -307
  3. package/package.json +18 -18
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.0.2](https://github.com/eik-lib/sink-file-system/compare/v1.0.1...v1.0.2) (2024-11-13)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deps:** update dependency @metrics/client to v2.5.4 ([#16](https://github.com/eik-lib/sink-file-system/issues/16)) ([800b226](https://github.com/eik-lib/sink-file-system/commit/800b22602af2d7e326948d8914262ee0226356b2))
7
+
8
+ ## [1.0.1](https://github.com/eik-lib/sink-file-system/compare/v1.0.0...v1.0.1) (2024-07-29)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * publish types ([917c262](https://github.com/eik-lib/sink-file-system/commit/917c262010855c689ec762fe76cd1cabfa4dcf7f))
14
+
1
15
  # 1.0.0 (2024-07-29)
2
16
 
3
17
 
package/lib/main.js CHANGED
@@ -1,20 +1,20 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { ReadFile } from '@eik/common';
5
- import Sink from '@eik/sink';
6
- import Metrics from '@metrics/client';
7
- import mime from 'mime';
8
- import { rimraf } from 'rimraf';
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { ReadFile } from "@eik/common";
5
+ import Sink from "@eik/sink";
6
+ import Metrics from "@metrics/client";
7
+ import mime from "mime";
8
+ import { rimraf } from "rimraf";
9
9
 
10
10
  /**
11
11
  * @param {import('node:fs').Stats} stat
12
12
  * @returns {string} As etag
13
13
  */
14
14
  const etagFromFsStat = (stat) => {
15
- const mtime = stat.mtime.getTime().toString(16);
16
- const size = stat.size.toString(16);
17
- return `W/"${size}-${mtime}"`;
15
+ const mtime = stat.mtime.getTime().toString(16);
16
+ const size = stat.size.toString(16);
17
+ return `W/"${size}-${mtime}"`;
18
18
  };
19
19
 
20
20
  /**
@@ -38,300 +38,300 @@ const etagFromFsStat = (stat) => {
38
38
  * ```
39
39
  */
40
40
  export default class SinkFileSystem extends Sink {
41
- /**
42
- * @type {Required<SinkFileSystemOptions>}
43
- */
44
- _config;
45
-
46
- /** @type {import('@metrics/client')} */
47
- _metrics;
48
-
49
- /**
50
- * @param {SinkFileSystemOptions} options
51
- */
52
- constructor(options = {}) {
53
- super();
54
- this._config = {
55
- sinkFsRootPath: path.join(os.tmpdir(), '/eik-files'),
56
- ...options,
57
- };
58
- this._metrics = new Metrics();
59
- this._counter = this._metrics.counter({
60
- name: 'eik_core_sink_fs',
61
- description:
62
- 'Counter measuring access to the file system storage sink',
63
- labels: {
64
- operation: 'n/a',
65
- success: false,
66
- access: false,
67
- },
68
- });
69
- }
70
-
71
- get metrics() {
72
- return this._metrics;
73
- }
74
-
75
- /**
76
- * @param {string} filePath
77
- * @param {string} contentType
78
- * @returns {Promise<import('node:stream').Writable>}
79
- */
80
- write(filePath, contentType) {
81
- return new Promise((resolve, reject) => {
82
- const operation = 'write';
83
-
84
- try {
85
- Sink.validateFilePath(filePath);
86
- Sink.validateContentType(contentType);
87
- } catch (error) {
88
- this._counter.inc({ labels: { operation } });
89
- reject(error);
90
- return;
91
- }
92
-
93
- const pathname = path.join(this._config.sinkFsRootPath, filePath);
94
-
95
- if (pathname.indexOf(this._config.sinkFsRootPath) !== 0) {
96
- this._counter.inc({ labels: { operation } });
97
- reject(new Error(`Directory traversal - ${filePath}`));
98
- return;
99
- }
100
-
101
- const dir = path.dirname(pathname);
102
-
103
- fs.mkdir(
104
- dir,
105
- {
106
- recursive: true,
107
- },
108
- (error) => {
109
- if (error) {
110
- this._counter.inc({
111
- labels: { access: true, operation },
112
- });
113
- reject(
114
- new Error(`Could not create directory - ${dir}`),
115
- );
116
- return;
117
- }
118
-
119
- const stream = fs.createWriteStream(pathname, {
120
- autoClose: true,
121
- emitClose: true,
122
- });
123
-
124
- this._counter.inc({
125
- labels: {
126
- success: true,
127
- access: true,
128
- operation,
129
- },
130
- });
131
-
132
- resolve(stream);
133
- },
134
- );
135
- });
136
- }
137
-
138
- /**
139
- * @param {string} filePath
140
- * @throws {Error} if the file does not exist
141
- * @returns {Promise<import('@eik/common').ReadFile>}
142
- */
143
- read(filePath) {
144
- return new Promise((resolve, reject) => {
145
- const operation = 'read';
146
-
147
- try {
148
- Sink.validateFilePath(filePath);
149
- } catch (error) {
150
- this._counter.inc({ labels: { operation } });
151
- reject(error);
152
- return;
153
- }
154
-
155
- const pathname = path.join(this._config.sinkFsRootPath, filePath);
156
-
157
- if (pathname.indexOf(this._config.sinkFsRootPath) !== 0) {
158
- this._counter.inc({ labels: { operation } });
159
- reject(new Error(`Directory traversal - ${filePath}`));
160
- return;
161
- }
162
-
163
- const closeFd = (fd) => {
164
- fs.close(fd, (error) => {
165
- if (error) {
166
- this._counter.inc({
167
- labels: {
168
- access: true,
169
- operation,
170
- },
171
- });
172
- return;
173
- }
174
- this._counter.inc({
175
- labels: {
176
- success: true,
177
- access: true,
178
- operation,
179
- },
180
- });
181
- });
182
- };
183
-
184
- fs.open(pathname, 'r', (error, fd) => {
185
- if (error) {
186
- this._counter.inc({
187
- labels: {
188
- access: true,
189
- operation,
190
- },
191
- });
192
- reject(error);
193
- return;
194
- }
195
-
196
- fs.fstat(fd, (err, stat) => {
197
- if (err) {
198
- closeFd(fd);
199
- reject(err);
200
- return;
201
- }
202
-
203
- if (!stat.isFile()) {
204
- closeFd(fd);
205
- reject(new Error(`Not a file - ${pathname}`));
206
- return;
207
- }
208
-
209
- const mimeType =
210
- mime.getType(pathname) || 'application/octet-stream';
211
- const etag = etagFromFsStat(stat);
212
-
213
- const obj = new ReadFile({
214
- mimeType,
215
- etag,
216
- });
217
-
218
- obj.stream = fs.createReadStream(pathname, {
219
- autoClose: true,
220
- fd,
221
- });
222
-
223
- obj.stream.on('error', () => {
224
- this._counter.inc({
225
- labels: {
226
- access: true,
227
- operation,
228
- },
229
- });
230
- });
231
-
232
- obj.stream.on('end', () => {
233
- this._counter.inc({
234
- labels: {
235
- success: true,
236
- access: true,
237
- operation,
238
- },
239
- });
240
- });
241
-
242
- resolve(obj);
243
- });
244
- });
245
- });
246
- }
247
-
248
- /**
249
- * @param {string} filePath
250
- * @returns {Promise<void>}
251
- */
252
- delete(filePath) {
253
- return new Promise((resolve, reject) => {
254
- const operation = 'delete';
255
-
256
- try {
257
- Sink.validateFilePath(filePath);
258
- } catch (error) {
259
- this._counter.inc({ labels: { operation } });
260
- reject(error);
261
- return;
262
- }
263
-
264
- const pathname = path.join(this._config.sinkFsRootPath, filePath);
265
-
266
- if (pathname.indexOf(this._config.sinkFsRootPath) !== 0) {
267
- this._counter.inc({ labels: { operation } });
268
- reject(new Error(`Directory traversal - ${filePath}`));
269
- return;
270
- }
271
-
272
- rimraf(pathname)
273
- .then(() => {
274
- this._counter.inc({
275
- labels: {
276
- success: true,
277
- access: true,
278
- operation,
279
- },
280
- });
281
- resolve();
282
- })
283
- .catch((error) => {
284
- this._counter.inc({ labels: { access: true, operation } });
285
- reject(error);
286
- });
287
- });
288
- }
289
-
290
- /**
291
- * @param {string} filePath
292
- * @throws {Error} if the file does not exist
293
- * @returns {Promise<void>}
294
- */
295
- exist(filePath) {
296
- return new Promise((resolve, reject) => {
297
- const operation = 'exist';
298
-
299
- try {
300
- Sink.validateFilePath(filePath);
301
- } catch (error) {
302
- this._counter.inc({ labels: { operation } });
303
- reject(error);
304
- return;
305
- }
306
-
307
- const pathname = path.join(this._config.sinkFsRootPath, filePath);
308
-
309
- if (pathname.indexOf(this._config.sinkFsRootPath) !== 0) {
310
- this._counter.inc({ labels: { operation } });
311
- reject(new Error(`Directory traversal - ${filePath}`));
312
- return;
313
- }
314
-
315
- fs.stat(pathname, (error, stat) => {
316
- this._counter.inc({
317
- labels: { success: true, access: true, operation },
318
- });
319
-
320
- if (stat && stat.isFile()) {
321
- resolve();
322
- return;
323
- }
324
-
325
- if (error) {
326
- reject(error);
327
- return;
328
- }
329
- reject();
330
- });
331
- });
332
- }
333
-
334
- get [Symbol.toStringTag]() {
335
- return 'SinkFileSystem';
336
- }
41
+ /**
42
+ * @type {Required<SinkFileSystemOptions>}
43
+ */
44
+ _config;
45
+
46
+ /** @type {import('@metrics/client')} */
47
+ _metrics;
48
+
49
+ /**
50
+ * @param {SinkFileSystemOptions} options
51
+ */
52
+ constructor(options = {}) {
53
+ super();
54
+ this._config = {
55
+ sinkFsRootPath: path.join(os.tmpdir(), "/eik-files"),
56
+ ...options,
57
+ };
58
+ this._metrics = new Metrics();
59
+ this._counter = this._metrics.counter({
60
+ name: "eik_core_sink_fs",
61
+ description:
62
+ "Counter measuring access to the file system storage sink",
63
+ labels: {
64
+ operation: "n/a",
65
+ success: false,
66
+ access: false,
67
+ },
68
+ });
69
+ }
70
+
71
+ get metrics() {
72
+ return this._metrics;
73
+ }
74
+
75
+ /**
76
+ * @param {string} filePath
77
+ * @param {string} contentType
78
+ * @returns {Promise<import('node:stream').Writable>}
79
+ */
80
+ write(filePath, contentType) {
81
+ return new Promise((resolve, reject) => {
82
+ const operation = "write";
83
+
84
+ try {
85
+ Sink.validateFilePath(filePath);
86
+ Sink.validateContentType(contentType);
87
+ } catch (error) {
88
+ this._counter.inc({ labels: { operation } });
89
+ reject(error);
90
+ return;
91
+ }
92
+
93
+ const pathname = path.join(this._config.sinkFsRootPath, filePath);
94
+
95
+ if (pathname.indexOf(this._config.sinkFsRootPath) !== 0) {
96
+ this._counter.inc({ labels: { operation } });
97
+ reject(new Error(`Directory traversal - ${filePath}`));
98
+ return;
99
+ }
100
+
101
+ const dir = path.dirname(pathname);
102
+
103
+ fs.mkdir(
104
+ dir,
105
+ {
106
+ recursive: true,
107
+ },
108
+ (error) => {
109
+ if (error) {
110
+ this._counter.inc({
111
+ labels: { access: true, operation },
112
+ });
113
+ reject(
114
+ new Error(`Could not create directory - ${dir}`),
115
+ );
116
+ return;
117
+ }
118
+
119
+ const stream = fs.createWriteStream(pathname, {
120
+ autoClose: true,
121
+ emitClose: true,
122
+ });
123
+
124
+ this._counter.inc({
125
+ labels: {
126
+ success: true,
127
+ access: true,
128
+ operation,
129
+ },
130
+ });
131
+
132
+ resolve(stream);
133
+ },
134
+ );
135
+ });
136
+ }
137
+
138
+ /**
139
+ * @param {string} filePath
140
+ * @throws {Error} if the file does not exist
141
+ * @returns {Promise<import('@eik/common').ReadFile>}
142
+ */
143
+ read(filePath) {
144
+ return new Promise((resolve, reject) => {
145
+ const operation = "read";
146
+
147
+ try {
148
+ Sink.validateFilePath(filePath);
149
+ } catch (error) {
150
+ this._counter.inc({ labels: { operation } });
151
+ reject(error);
152
+ return;
153
+ }
154
+
155
+ const pathname = path.join(this._config.sinkFsRootPath, filePath);
156
+
157
+ if (pathname.indexOf(this._config.sinkFsRootPath) !== 0) {
158
+ this._counter.inc({ labels: { operation } });
159
+ reject(new Error(`Directory traversal - ${filePath}`));
160
+ return;
161
+ }
162
+
163
+ const closeFd = (fd) => {
164
+ fs.close(fd, (error) => {
165
+ if (error) {
166
+ this._counter.inc({
167
+ labels: {
168
+ access: true,
169
+ operation,
170
+ },
171
+ });
172
+ return;
173
+ }
174
+ this._counter.inc({
175
+ labels: {
176
+ success: true,
177
+ access: true,
178
+ operation,
179
+ },
180
+ });
181
+ });
182
+ };
183
+
184
+ fs.open(pathname, "r", (error, fd) => {
185
+ if (error) {
186
+ this._counter.inc({
187
+ labels: {
188
+ access: true,
189
+ operation,
190
+ },
191
+ });
192
+ reject(error);
193
+ return;
194
+ }
195
+
196
+ fs.fstat(fd, (err, stat) => {
197
+ if (err) {
198
+ closeFd(fd);
199
+ reject(err);
200
+ return;
201
+ }
202
+
203
+ if (!stat.isFile()) {
204
+ closeFd(fd);
205
+ reject(new Error(`Not a file - ${pathname}`));
206
+ return;
207
+ }
208
+
209
+ const mimeType =
210
+ mime.getType(pathname) || "application/octet-stream";
211
+ const etag = etagFromFsStat(stat);
212
+
213
+ const obj = new ReadFile({
214
+ mimeType,
215
+ etag,
216
+ });
217
+
218
+ obj.stream = fs.createReadStream(pathname, {
219
+ autoClose: true,
220
+ fd,
221
+ });
222
+
223
+ obj.stream.on("error", () => {
224
+ this._counter.inc({
225
+ labels: {
226
+ access: true,
227
+ operation,
228
+ },
229
+ });
230
+ });
231
+
232
+ obj.stream.on("end", () => {
233
+ this._counter.inc({
234
+ labels: {
235
+ success: true,
236
+ access: true,
237
+ operation,
238
+ },
239
+ });
240
+ });
241
+
242
+ resolve(obj);
243
+ });
244
+ });
245
+ });
246
+ }
247
+
248
+ /**
249
+ * @param {string} filePath
250
+ * @returns {Promise<void>}
251
+ */
252
+ delete(filePath) {
253
+ return new Promise((resolve, reject) => {
254
+ const operation = "delete";
255
+
256
+ try {
257
+ Sink.validateFilePath(filePath);
258
+ } catch (error) {
259
+ this._counter.inc({ labels: { operation } });
260
+ reject(error);
261
+ return;
262
+ }
263
+
264
+ const pathname = path.join(this._config.sinkFsRootPath, filePath);
265
+
266
+ if (pathname.indexOf(this._config.sinkFsRootPath) !== 0) {
267
+ this._counter.inc({ labels: { operation } });
268
+ reject(new Error(`Directory traversal - ${filePath}`));
269
+ return;
270
+ }
271
+
272
+ rimraf(pathname)
273
+ .then(() => {
274
+ this._counter.inc({
275
+ labels: {
276
+ success: true,
277
+ access: true,
278
+ operation,
279
+ },
280
+ });
281
+ resolve();
282
+ })
283
+ .catch((error) => {
284
+ this._counter.inc({ labels: { access: true, operation } });
285
+ reject(error);
286
+ });
287
+ });
288
+ }
289
+
290
+ /**
291
+ * @param {string} filePath
292
+ * @throws {Error} if the file does not exist
293
+ * @returns {Promise<void>}
294
+ */
295
+ exist(filePath) {
296
+ return new Promise((resolve, reject) => {
297
+ const operation = "exist";
298
+
299
+ try {
300
+ Sink.validateFilePath(filePath);
301
+ } catch (error) {
302
+ this._counter.inc({ labels: { operation } });
303
+ reject(error);
304
+ return;
305
+ }
306
+
307
+ const pathname = path.join(this._config.sinkFsRootPath, filePath);
308
+
309
+ if (pathname.indexOf(this._config.sinkFsRootPath) !== 0) {
310
+ this._counter.inc({ labels: { operation } });
311
+ reject(new Error(`Directory traversal - ${filePath}`));
312
+ return;
313
+ }
314
+
315
+ fs.stat(pathname, (error, stat) => {
316
+ this._counter.inc({
317
+ labels: { success: true, access: true, operation },
318
+ });
319
+
320
+ if (stat && stat.isFile()) {
321
+ resolve();
322
+ return;
323
+ }
324
+
325
+ if (error) {
326
+ reject(error);
327
+ return;
328
+ }
329
+ reject();
330
+ });
331
+ });
332
+ }
333
+
334
+ get [Symbol.toStringTag]() {
335
+ return "SinkFileSystem";
336
+ }
337
337
  }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@eik/sink-file-system",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Sink implementation that persists files on the local file system.",
5
5
  "main": "lib/main.js",
6
+ "types": "types/main.d.ts",
6
7
  "type": "module",
7
8
  "files": [
8
9
  "CHANGELOG.md",
@@ -11,12 +12,13 @@
11
12
  "lib"
12
13
  ],
13
14
  "scripts": {
15
+ "clean": "rimraf .tap node_modules types",
14
16
  "lint": "eslint .",
15
17
  "lint:fix": "eslint --fix .",
16
- "test": "run-s test:*",
17
- "test:unit": "tap --disable-coverage --allow-empty-coverage tests/**/*.js",
18
- "test:types": "tsc --project tsconfig.test.json",
19
- "types": "tsc --declaration --emitDeclarationOnly"
18
+ "test": "tap --disable-coverage --allow-empty-coverage tests/**/*.js",
19
+ "types": "run-s types:module types:test",
20
+ "types:module": "tsc",
21
+ "types:test": "tsc --project tsconfig.test.json"
20
22
  },
21
23
  "repository": {
22
24
  "type": "git",
@@ -34,22 +36,20 @@
34
36
  "dependencies": {
35
37
  "@eik/common": "3.0.1",
36
38
  "@eik/sink": "1.2.5",
37
- "@metrics/client": "2.5.3",
38
- "mime": "3.0.0",
39
- "rimraf": "5.0.8"
39
+ "@metrics/client": "2.5.4",
40
+ "mime": "3.0.0"
40
41
  },
41
42
  "devDependencies": {
42
- "@semantic-release/changelog": "6.0.3",
43
- "@semantic-release/git": "10.0.1",
43
+ "@eik/eslint-config": "1.0.4",
44
+ "@eik/prettier-config": "1.0.1",
45
+ "@eik/semantic-release-config": "1.0.0",
44
46
  "@types/mime": "3.0.4",
45
- "@types/readable-stream": "4.0.15",
46
- "eslint": "9.1.1",
47
- "eslint-config-prettier": "9.1.0",
48
- "eslint-plugin-prettier": "5.1.3",
49
- "globals": "15.0.0",
50
- "npm-run-all": "4.1.5",
51
- "prettier": "3.3.2",
52
- "semantic-release": "24.0.0",
47
+ "@types/readable-stream": "4.0.18",
48
+ "eslint": "9.11.1",
49
+ "npm-run-all2": "5.0.2",
50
+ "prettier": "3.3.3",
51
+ "rimraf": "6.0.1",
52
+ "semantic-release": "24.1.3",
53
53
  "tap": "18.8.0"
54
54
  }
55
55
  }