• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dex4er / js-fs-blob-storage / #925

11 Jul 2025 11:03PM UTC coverage: 95.42%. Remained the same
#925

push

web-flow
Merge a04fea286 into 44ec36f21

20 of 21 branches covered (95.24%)

Branch coverage included in aggregate %.

105 of 110 relevant lines covered (95.45%)

4.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.42
/src/fs-blob-storage.ts
1
/// <reference types="node" />
1✔
2

1✔
3
import * as fs from "node:fs"
1✔
4
import * as path from "node:path"
1✔
5

1✔
6
export interface FsBlobStorageOptions {
1✔
7
  ext?: string
1✔
8
  part?: string
1✔
9
  exclusive?: boolean
1✔
10
  path?: string
1✔
11
  fs?: typeof fs
1✔
12
}
1✔
13

1✔
14
export interface FsBlobStorageWriteStreamOptions {
1✔
15
  ext?: string
1✔
16
  part?: string
1✔
17
  encoding?: BufferEncoding
1✔
18
}
1✔
19

1✔
20
export interface FsBlobStorageReadStreamOptions {
1✔
21
  ext?: string
1✔
22
  encoding?: BufferEncoding
1✔
23
}
1✔
24

1✔
25
export interface FsBlobStorageCommitOptions {
1✔
26
  ext?: string
1✔
27
  part?: string
1✔
28
}
1✔
29

1✔
30
export interface FsBlobStorageRemoveOptions {
1✔
31
  ext?: string
1✔
32
}
1✔
33

1✔
34
export const DEFAULT_EXT = ""
1✔
35
export const DEFAULT_PART = ".part"
1✔
36

1✔
37
export class FsBlobStorage {
1✔
38
  protected ext: string
1✔
39
  protected part: string
1✔
40
  protected writeFlags: string
1✔
41
  protected fs: typeof fs
1✔
42
  protected path: string
1✔
43

1✔
44
  constructor(options: FsBlobStorageOptions = {}) {
1✔
45
    this.ext = options.ext !== undefined ? options.ext : DEFAULT_EXT
30✔
46
    this.part = options.part !== undefined ? options.part : DEFAULT_PART
30✔
47
    this.writeFlags = options.exclusive ? "wx" : "w"
30✔
48
    this.fs = options.fs || fs
30✔
49
    this.path = options.path || "."
30✔
50
  }
30✔
51

1✔
52
  async createWriteStream(key: string, options: FsBlobStorageWriteStreamOptions = {}): Promise<fs.WriteStream> {
1✔
53
    const {ext = this.ext, part = this.part, encoding} = options
9✔
54
    const filepath = path.join(this.path, key + ext)
9✔
55
    const dirpath = path.dirname(filepath)
9✔
56

9✔
57
    await this.fs.promises.mkdir(dirpath, {recursive: true})
9✔
58

9✔
59
    // for exclusive mode it will reject if file already exist
9✔
60
    const fd = await this.fs.promises.open(filepath, this.writeFlags)
9✔
61

8✔
62
    if (part) {
9✔
63
      // do `open` instead of `stat` to prevent race condition
7✔
64
      const fdPart = await this.fs.promises.open(filepath + part, this.writeFlags)
7✔
65

6✔
66
      // `close` before `rename` just for Windows
6✔
67
      await fdPart.close()
6✔
68

6✔
69
      // `rename` overwrites quietly the file
6✔
70
      await this.fs.promises.rename(filepath, filepath + part)
6✔
71
    }
6✔
72

7✔
73
    // first argument is ignored
7✔
74
    return this.fs.createWriteStream(filepath + part, {fd, encoding})
7✔
75
  }
7✔
76

1✔
77
  async createReadStream(key: string, options: FsBlobStorageReadStreamOptions = {}): Promise<fs.ReadStream> {
1✔
78
    const {ext = this.ext, encoding} = options
7✔
79
    const filepath = path.join(this.path, key + ext)
7✔
80

7✔
81
    const fd = await this.fs.promises.open(filepath, "r")
7✔
82

6✔
83
    const stats = await this.fs.promises.stat(filepath)
6✔
84

5✔
85
    if (!stats.size) {
7!
86
      throw Object.assign(new Error(`ENOENT: empty file, open '${filepath}'`), {
×
87
        code: "ENOENT",
×
88
        path: filepath,
×
89
      })
×
90
    }
×
91

5✔
92
    return this.fs.createReadStream(filepath, {fd, encoding})
5✔
93
  }
5✔
94

1✔
95
  async commit(key: string, options: FsBlobStorageCommitOptions = {}): Promise<void> {
1✔
96
    const {ext = this.ext, part = this.part} = options
7✔
97
    if (part) {
7✔
98
      const filepath = path.join(this.path, key + ext)
6✔
99
      return this.fs.promises.rename(filepath + part, filepath)
6✔
100
    }
6✔
101
  }
7✔
102

1✔
103
  async remove(key: string, options: FsBlobStorageRemoveOptions = {}): Promise<void> {
1✔
104
    const {ext = this.ext} = options
6✔
105
    const filepath = path.join(this.path, key + ext)
6✔
106
    return this.fs.promises.unlink(filepath)
6✔
107
  }
6✔
108
}
1✔
109

1✔
110
export default FsBlobStorage
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc