@stacksjs/storage 0.75.224 → 0.75.226
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/README.md +29 -0
- package/dist/bounded-text-file.d.ts +17 -0
- package/dist/bounded-text-file.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -47,6 +47,35 @@ import {
|
|
|
47
47
|
|
|
48
48
|
To view the full documentation, please visit [<https://stacksjs.com/storag>e](https://stacksjs.com/storage).
|
|
49
49
|
|
|
50
|
+
## Bounded local text reads
|
|
51
|
+
|
|
52
|
+
Use the standalone entrypoint for local text supplied by a user or worker:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { readBoundedTextFile } from '@stacksjs/storage/bounded-text-file'
|
|
56
|
+
|
|
57
|
+
const journal = await readBoundedTextFile(inputPath, { maxBytes: 4 * 1024 * 1024 })
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`maxBytes` is required and counts encoded bytes. It accepts nonnegative integers
|
|
61
|
+
below the runtime's `node:buffer` `constants.MAX_LENGTH`. The function checks the
|
|
62
|
+
opened descriptor is a regular file and its size fits, then reads at most
|
|
63
|
+
`maxBytes + 1` bytes to catch growth. It rejects malformed UTF-8 and always closes
|
|
64
|
+
the opened descriptor. A UTF-8 BOM counts toward the limit and is omitted from
|
|
65
|
+
the decoded string, following `TextDecoder` behavior.
|
|
66
|
+
|
|
67
|
+
The final path component cannot be a symlink. Nonblocking opening rejects FIFOs
|
|
68
|
+
without waiting for a writer. Platforms without `O_NOFOLLOW` and `O_NONBLOCK`
|
|
69
|
+
support fail closed. Parent-directory symlinks are followed; callers still own
|
|
70
|
+
path authorization. Concurrent edits are not an atomic content snapshot.
|
|
71
|
+
|
|
72
|
+
`BoundedTextFileError.code` identifies `NOT_REGULAR_FILE`, `FILE_TOO_LARGE`,
|
|
73
|
+
`INVALID_UTF8`, or `UNSUPPORTED_PLATFORM`. Invalid limits throw `RangeError`.
|
|
74
|
+
Native filesystem failures propagate unchanged, including `ENOENT` and the
|
|
75
|
+
platform's final-symlink error. The function and its types are also exported
|
|
76
|
+
from `@stacksjs/storage`. Existing `readTextFile` and disk readers keep their
|
|
77
|
+
current behavior.
|
|
78
|
+
|
|
50
79
|
## 🧪 Testing
|
|
51
80
|
|
|
52
81
|
```bash
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a local regular file as strict UTF-8, consuming at most maxBytes + 1.
|
|
3
|
+
* Reject final-component symlinks and open special files without waiting for
|
|
4
|
+
* a FIFO writer. Parent directories are followed: callers own path authorization.
|
|
5
|
+
* This is a bounded read of a changing file, not an atomic content snapshot.
|
|
6
|
+
* Native open/stat/read/close errors propagate unchanged; the descriptor closes
|
|
7
|
+
* on every path after a successful open. Unsupported safety flags fail closed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readBoundedTextFile(path: string, options: BoundedTextFileOptions): Promise<string>;
|
|
10
|
+
export declare interface BoundedTextFileOptions {
|
|
11
|
+
maxBytes: number
|
|
12
|
+
}
|
|
13
|
+
export type BoundedTextFileErrorCode = 'NOT_REGULAR_FILE' | 'FILE_TOO_LARGE' | 'INVALID_UTF8' | 'UNSUPPORTED_PLATFORM';
|
|
14
|
+
export declare class BoundedTextFileError extends Error {
|
|
15
|
+
readonly code: BoundedTextFileErrorCode;
|
|
16
|
+
constructor(code: BoundedTextFileErrorCode, message: string, options?: ErrorOptions);
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{constants as bufferConstants}from"node:buffer";import{constants}from"node:fs";import{open}from"node:fs/promises";export class BoundedTextFileError extends Error{code;constructor(code,message,options){super(message,options);this.name="BoundedTextFileError";this.code=code}}export async function readBoundedTextFile(path,options){const{maxBytes}=options;if(!Number.isSafeInteger(maxBytes)||maxBytes<0||maxBytes>=bufferConstants.MAX_LENGTH)throw RangeError(`maxBytes must be an integer between 0 and ${bufferConstants.MAX_LENGTH-1}`);if(!constants.O_NOFOLLOW||!constants.O_NONBLOCK)throw new BoundedTextFileError("UNSUPPORTED_PLATFORM","Bounded text reads require O_NOFOLLOW and O_NONBLOCK support");const file=await open(path,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK);try{const stat=await file.stat();if(!stat.isFile())throw new BoundedTextFileError("NOT_REGULAR_FILE","Text input must be a regular file");if(stat.size>maxBytes)throw new BoundedTextFileError("FILE_TOO_LARGE",`Text file exceeds ${maxBytes} bytes`);const bytes=new Uint8Array(maxBytes+1);let length=0;while(length<bytes.length){const{bytesRead}=await file.read(bytes,length,bytes.length-length,null);if(bytesRead===0)break;length+=bytesRead}if(length>maxBytes)throw new BoundedTextFileError("FILE_TOO_LARGE",`Text file exceeds ${maxBytes} bytes`);try{return new TextDecoder("utf-8",{fatal:!0}).decode(bytes.subarray(0,length))}catch(cause){throw new BoundedTextFileError("INVALID_UTF8","Text file must contain valid UTF-8",{cause})}}finally{await file.close()}}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./copy";export*from"./delete";export*from"./files";export*from"./folders";export*from"./fs";export*from"./temporary-workspaces";export*from"./glob";export*from"./hash";export*from"./helpers";export* as storage from"./storage";export*from"./zip";export{ensureBucket,ensureConfiguredBuckets}from"./provision";export*from"./adapters";export*from"./types";export*from"./drivers";export{serveFile}from"./static-serve";export{Storage,StorageManager}from"./facade";export{UploadedFile,uploadedFile,uploadedFiles}from"./uploaded-file";export{azureDisk,backblazeDisk,configFromEnv,filebaseDisk,gcsDisk,hetznerDisk,localDisk,r2Disk,s3Disk}from"./types/filesystem";export{clearRevokedSignedStorageTokens,createSignedStorageToken,isSignedStorageTokenRevoked,revokeSignedStorageToken,verifySignedStorageToken}from"./signed-url";export{parseDiskPath,PathSanitizeError,sanitizePresignedDir,sanitizePresignedFilename}from"./path-sanitize";export{fileInfo,fileInfoFromBytes,fileKind,isPreviewable,resolveMime}from"./file-info";export{detectMimeFromMagicBytes,verifyUploadedMime}from"./mime-verify";export{signS3PresignedPost}from"./s3-presigned-post";
|
|
1
|
+
export*from"./copy";export*from"./delete";export*from"./files";export*from"./bounded-text-file";export*from"./folders";export*from"./fs";export*from"./temporary-workspaces";export*from"./glob";export*from"./hash";export*from"./helpers";export* as storage from"./storage";export*from"./zip";export{ensureBucket,ensureConfiguredBuckets}from"./provision";export*from"./adapters";export*from"./types";export*from"./drivers";export{serveFile}from"./static-serve";export{Storage,StorageManager}from"./facade";export{UploadedFile,uploadedFile,uploadedFiles}from"./uploaded-file";export{azureDisk,backblazeDisk,configFromEnv,filebaseDisk,gcsDisk,hetznerDisk,localDisk,r2Disk,s3Disk}from"./types/filesystem";export{clearRevokedSignedStorageTokens,createSignedStorageToken,isSignedStorageTokenRevoked,revokeSignedStorageToken,verifySignedStorageToken}from"./signed-url";export{parseDiskPath,PathSanitizeError,sanitizePresignedDir,sanitizePresignedFilename}from"./path-sanitize";export{fileInfo,fileInfoFromBytes,fileKind,isPreviewable,resolveMime}from"./file-info";export{detectMimeFromMagicBytes,verifyUploadedMime}from"./mime-verify";export{signS3PresignedPost}from"./s3-presigned-post";
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/storage",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.226",
|
|
6
6
|
"description": "The Stacks file system.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -61,20 +61,20 @@
|
|
|
61
61
|
"prepublishOnly": "bun run build"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@stacksjs/arrays": "0.75.
|
|
65
|
-
"@stacksjs/cli": "0.75.
|
|
66
|
-
"@stacksjs/config": "0.75.
|
|
67
|
-
"@stacksjs/env": "0.75.
|
|
68
|
-
"@stacksjs/error-handling": "0.75.
|
|
69
|
-
"@stacksjs/logging": "0.75.
|
|
70
|
-
"@stacksjs/path": "0.75.
|
|
71
|
-
"@stacksjs/strings": "0.75.
|
|
64
|
+
"@stacksjs/arrays": "0.75.226",
|
|
65
|
+
"@stacksjs/cli": "0.75.226",
|
|
66
|
+
"@stacksjs/config": "0.75.226",
|
|
67
|
+
"@stacksjs/env": "0.75.226",
|
|
68
|
+
"@stacksjs/error-handling": "0.75.226",
|
|
69
|
+
"@stacksjs/logging": "0.75.226",
|
|
70
|
+
"@stacksjs/path": "0.75.226",
|
|
71
|
+
"@stacksjs/strings": "0.75.226",
|
|
72
72
|
"@stacksjs/ts-cloud": "^0.16.58",
|
|
73
73
|
"@stacksjs/ts-xml": "^0.1.0",
|
|
74
74
|
"ts-images": "^0.2.23"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@stacksjs/types": "0.75.
|
|
77
|
+
"@stacksjs/types": "0.75.226",
|
|
78
78
|
"better-dx": "^0.2.24"
|
|
79
79
|
}
|
|
80
80
|
}
|