Skip to content

Commit 427d014

Browse files
authored
BigInts for all the things (#23)
* Use bigInts for ino IDs, fixes #21 * Support folder size output as BigInt
1 parent 43c04af commit 427d014

File tree

3 files changed

+115
-24
lines changed

3 files changed

+115
-24
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ If no errors were encountered, `errors` will be `null`. If errors were encounter
4545

4646
This method is great if you want to implement custom logic based on the errors that were encountered.
4747

48-
### `getFolderSize.loose(path, [options]): number`
48+
### `getFolderSize.loose(path, [options]): number | bigint`
4949
The `loose` method will return the folder size directly and ignore any errors it encounters, which means the returned folder size could be smaller than the real folder size.
5050

5151
This method is great if the precise size isn't too important, for example when used only to display the folder size to the user.
5252

53-
### `getFolderSize.strict(path, [options]): number`
53+
### `getFolderSize.strict(path, [options]): number | bigint`
5454
The `strict` method will return the folder size directly, but throw an error if it encounters any read errors.
5555

5656
This method is great if you need a very accurate number. You will have to implement some sort of error handling to use it reliably.
@@ -63,12 +63,15 @@ Any of the three methods can also take an `options` object:
6363
getFolderSize(
6464
'/path/to/folder',
6565
{
66+
bigint: true,
6667
ignore: /pattern/,
6768
fs: customFS,
6869
}
6970
)
7071
```
7172

73+
If the `bigint` option is set to true, the folder size is returned as a BigInt instead of the default Number.
74+
7275
The `ignore` option takes a regex pattern. Any file or folder with a path that matches the pattern will not be counted in the total folder size.
7376

7477
The `fs` option allows you to pass a different filesystem handler, such as [memfs](https://github.com/streamich/memfs), that will be used to read the folder size. The filesystem handler must incorporate `lstat` and `readdir` promise functions.

index.js

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { join as joinPaths } from 'path';
66
*
77
* If any errors are returned, the returned folder size is likely smaller than the real folder size.
88
*
9-
* @param {string} itemPath - Path of the folder.
10-
* @param {object} [options] - Options.
11-
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
12-
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
9+
* @param {string} itemPath - Path of the folder.
10+
* @param {object} [options] - Options.
11+
* @param {boolean} [options.bigint] - Should the folder size be returned as a BigInt instead of a Number.
12+
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
13+
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
1314
*
14-
* @returns {Promise<{size: number, errors: Array<Error> | null}>} - An object containing the size of the folder in bytes and a list of encountered errors.
15+
* @returns {Promise<{size: number | bigint, errors: Array<Error> | null}>} - An object containing the size of the folder in bytes and a list of encountered errors.
1516
*/
1617
export default async function getFolderSize (itemPath, options) { return await core(itemPath, options, {errors: true}) }
1718

@@ -20,12 +21,13 @@ export default async function getFolderSize (itemPath, options) { return await c
2021
*
2122
* The returned folder size might be smaller than the real folder size. It is impossible to know for sure, since errors are ignored.
2223
*
23-
* @param {string} itemPath - Path of the folder.
24-
* @param {object} [options] - Options.
25-
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
26-
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
24+
* @param {string} itemPath - Path of the folder.
25+
* @param {object} [options] - Options.
26+
* @param {boolean} [options.bigint] - Should the folder size be returned as a BigInt instead of a Number.
27+
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
28+
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
2729
*
28-
* @returns {Promise<number>} - The size of the folder in bytes.
30+
* @returns {Promise<number | bigint>} - The size of the folder in bytes.
2931
*/
3032
getFolderSize.loose = async (itemPath, options) => await core(itemPath, options);
3133

@@ -34,12 +36,13 @@ getFolderSize.loose = async (itemPath, options) => await core(itemPath, options)
3436
*
3537
* Because errors will otherwise make this method fail, the returned folder size will always be accurate.
3638
*
37-
* @param {string} itemPath - Path of the folder.
38-
* @param {object} [options] - Options.
39-
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
40-
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
39+
* @param {string} itemPath - Path of the folder.
40+
* @param {object} [options] - Options.
41+
* @param {boolean} [options.bigint] - Should the folder size be returned as a BigInt instead of a Number.
42+
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
43+
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
4144
*
42-
* @returns {Promise<number>} - The size of the folder in bytes.
45+
* @returns {Promise<number | bigint>} - The size of the folder in bytes.
4346
*/
4447
getFolderSize.strict = async (itemPath, options) => await core(itemPath, options, {strict: true});
4548

@@ -56,7 +59,7 @@ async function core (rootItemPath, options = {}, returnType = {}) {
5659
async function processItem(itemPath) {
5760
if(options.ignore?.test(itemPath)) return;
5861

59-
const stats = returnType.strict ? await fs.lstat(itemPath) : await fs.lstat(itemPath).catch(error => errors.push(error));
62+
const stats = returnType.strict ? await fs.lstat(itemPath, {bigint: true}) : await fs.lstat(itemPath, {bigint: true}).catch(error => errors.push(error));
6063
if(typeof stats !== 'object') return;
6164
fileSizes.set(stats.ino, stats.size);
6265

@@ -71,7 +74,19 @@ async function core (rootItemPath, options = {}, returnType = {}) {
7174
}
7275
}
7376

74-
const folderSize = Array.from(fileSizes.values()).reduce((total, fileSize) => total + fileSize, 0);
77+
let folderSize = Array.from(fileSizes.values()).reduce((total, fileSize) => total + fileSize, 0n);
78+
79+
if(!options.bigint) {
80+
if(folderSize > BigInt(Number.MAX_SAFE_INTEGER)){
81+
const error = new RangeError('The folder size is too large to return as a Number. You can instruct this package to return a BigInt instead.');
82+
if(returnType.strict){
83+
throw error;
84+
}else{
85+
errors.push(error);
86+
}
87+
}
88+
folderSize = Number(folderSize);
89+
}
7590

7691
if (returnType.errors) {
7792
return {

test/logic.js

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,27 @@ tap.test('basic folder', async () => {
6464

6565
});
6666

67+
tap.test('basic folder - with bigint', async () => {
68+
69+
tap.test('get file sizes', async () => {
70+
71+
tap.equal(await callAll('/fixture/8bytes.txt', {bigint: true, fs: basicFS}), 8n, 'should return the correct file size');
72+
tap.equal(await callAll('/fixture/500bytes.txt', {bigint: true, fs: basicFS}), 500n, 'should return the correct file size');
73+
tap.equal(await callAll('/fixture/6000bytes.txt', {bigint: true, fs: basicFS}), 6000n, 'should return the correct file size');
74+
tap.end();
75+
76+
});
77+
78+
tap.test('get folder size', async () => {
79+
80+
tap.equal(await callAll('/fixture', {bigint: true, fs: basicFS}), 6508n, 'should return the correct folder size');
81+
82+
tap.end();
83+
84+
});
85+
86+
});
87+
6788
const nestedFS = Volume.fromJSON(
6889
{
6990
'./8bytes.txt': B.repeat(8),
@@ -111,6 +132,58 @@ tap.test('ignore option', async () => {
111132

112133
});
113134

135+
const largeFSCore = Volume.fromJSON(
136+
{
137+
'./very.txt': B.repeat(200),
138+
'./large.txt': B.repeat(200),
139+
'./files.txt': B.repeat(200),
140+
},
141+
'/fixture',
142+
).promisesApi;
143+
144+
const largeFS = {
145+
lstat: async (itemPath, options) => {
146+
const result = await largeFSCore.lstat(itemPath, options);
147+
result.size = BigInt(Number.MAX_SAFE_INTEGER);
148+
return result;
149+
},
150+
readdir: largeFSCore.readdir,
151+
};
152+
153+
tap.test('handling very large filesystems', async () => {
154+
155+
tap.test('returning Number', async () => {
156+
157+
tap.type(await getFolderSize.loose('/fixture', {fs: largeFS}), 'number', 'should return Number');
158+
159+
tap.rejects(async () => {await getFolderSize.strict('/fixture', {fs: largeFS})}, /The folder size is too large to return as a Number. You can instruct this package to return a BigInt instead./, 'should throw appropriate error');
160+
161+
const { size, errors } = await getFolderSize('/fixture', {fs: largeFS});
162+
tap.type(size, 'number', 'should return Number');
163+
tap.type(errors, Array, 'should return Array of errors');
164+
tap.equal(errors.length, 1, 'should return one error');
165+
tap.equal(errors[0].message, 'The folder size is too large to return as a Number. You can instruct this package to return a BigInt instead.', 'should return appropriate error');
166+
167+
tap.end();
168+
169+
});
170+
171+
tap.test('returning BigInt', async () => {
172+
173+
tap.equal(await getFolderSize.loose('/fixture', {bigint: true, fs: largeFS}), BigInt(Number.MAX_SAFE_INTEGER) * 4n, 'should return size of 4 times max safe Number');
174+
175+
tap.equal(await getFolderSize.strict('/fixture', {bigint: true, fs: largeFS}), BigInt(Number.MAX_SAFE_INTEGER) * 4n, 'should return size of 4 times max safe Number');
176+
177+
const { size, errors } = await getFolderSize('/fixture', {bigint: true, fs: largeFS});
178+
tap.equal(size, BigInt(Number.MAX_SAFE_INTEGER) * 4n, 'should return size of 4 times max safe Number');
179+
tap.equal(errors, null, 'should return no errors');
180+
181+
tap.end();
182+
183+
});
184+
185+
});
186+
114187
const badFSCore = Volume.fromJSON(
115188
{
116189
'./pass/pass.md': B.repeat(200),
@@ -126,21 +199,21 @@ const badFSCore = Volume.fromJSON(
126199
).promisesApi;
127200

128201
const badFS = {
129-
lstat: async (itemPath) => {
202+
lstat: async (itemPath, options) => {
130203
if(itemPath.includes('failFile')){
131204
throw Error('Nah - File');
132205
}else{
133-
return await badFSCore.lstat(itemPath);
206+
return await badFSCore.lstat(itemPath, options);
134207
}
135208
},
136-
readdir: async (itemPath) => {
209+
readdir: async (itemPath, options) => {
137210
if(itemPath.includes('failDir')){
138211
throw Error('Nah - Directory');
139212
}else{
140-
return await badFSCore.readdir(itemPath);
213+
return await badFSCore.readdir(itemPath, options);
141214
}
142215
}
143-
}
216+
};
144217

145218
tap.test('error handling', async () => {
146219

0 commit comments

Comments
 (0)