-
Notifications
You must be signed in to change notification settings - Fork 46.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Flight] Add read-only fs methods #20412
Merged
Merged
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
3080861
Don't allocate the inner cache unnecessarily
gaearon bc0984c
Add fs.access
gaearon 5779169
Add fs.lstat
gaearon 127f5f3
Add fs.stat
gaearon 9fc45d1
Add fs.readdir
gaearon cc03a28
Add fs.readlink
gaearon 60d0b08
Add fs.realpath
gaearon 9becd74
Rename functions to disambiguate two caches
gaearon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,19 +20,19 @@ const Rejected = 2; | |
type PendingRecord = {| | ||
status: 0, | ||
value: Wakeable, | ||
cache: Array<mixed>, | ||
cache: null, | ||
|}; | ||
|
||
type ResolvedRecord<T> = {| | ||
status: 1, | ||
value: T, | ||
cache: Array<mixed>, | ||
cache: null | Array<mixed>, | ||
|}; | ||
|
||
type RejectedRecord = {| | ||
status: 2, | ||
value: mixed, | ||
cache: Array<mixed>, | ||
cache: null, | ||
|}; | ||
|
||
type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord; | ||
|
@@ -41,7 +41,7 @@ function createRecordFromThenable<T>(thenable: Thenable<T>): Record<T> { | |
const record: Record<T> = { | ||
status: Pending, | ||
value: thenable, | ||
cache: [], | ||
cache: null, | ||
}; | ||
thenable.then( | ||
value => { | ||
|
@@ -62,9 +62,10 @@ function createRecordFromThenable<T>(thenable: Thenable<T>): Record<T> { | |
return record; | ||
} | ||
|
||
function readRecordValue<T>(record: Record<T>): T { | ||
function readRecord<T>(record: Record<T>): ResolvedRecord<T> { | ||
if (record.status === Resolved) { | ||
return record.value; | ||
// This is just a type refinement. | ||
return record; | ||
} else { | ||
throw record.value; | ||
} | ||
|
@@ -91,6 +92,121 @@ function checkPathInDev(path: string) { | |
} | ||
} | ||
|
||
function createAccessCache(): Map<string, Array<number | Record<void>>> { | ||
return new Map(); | ||
} | ||
|
||
export function access(path: string, mode?: number): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I honestly don't know why this would be useful in a webapp. But it was there so I added it. |
||
checkPathInDev(path); | ||
if (mode == null) { | ||
mode = 0; // fs.constants.F_OK | ||
} | ||
const map = unstable_getCacheForType(createAccessCache); | ||
let accessCache = map.get(path); | ||
if (!accessCache) { | ||
accessCache = []; | ||
map.set(path, accessCache); | ||
} | ||
let record; | ||
for (let i = 0; i < accessCache.length; i += 2) { | ||
const cachedMode: number = (accessCache[i]: any); | ||
if (mode === cachedMode) { | ||
const cachedRecord: Record<void> = (accessCache[i + 1]: any); | ||
record = cachedRecord; | ||
break; | ||
} | ||
} | ||
if (!record) { | ||
const thenable = fs.access(path, mode); | ||
record = createRecordFromThenable(thenable); | ||
accessCache.push(mode, record); | ||
} | ||
readRecord(record); // No return value. | ||
} | ||
|
||
function createLstatCache(): Map<string, Array<boolean | Record<mixed>>> { | ||
return new Map(); | ||
} | ||
|
||
export function lstat(path: string, options?: {bigint?: boolean}): mixed { | ||
checkPathInDev(path); | ||
let bigint = false; | ||
if (options && options.bigint) { | ||
bigint = true; | ||
} | ||
const map = unstable_getCacheForType(createLstatCache); | ||
let lstatCache = map.get(path); | ||
if (!lstatCache) { | ||
lstatCache = []; | ||
map.set(path, lstatCache); | ||
} | ||
let record; | ||
for (let i = 0; i < lstatCache.length; i += 2) { | ||
const cachedBigint: boolean = (lstatCache[i]: any); | ||
if (bigint === cachedBigint) { | ||
const cachedRecord: Record<void> = (lstatCache[i + 1]: any); | ||
record = cachedRecord; | ||
break; | ||
} | ||
} | ||
if (!record) { | ||
const thenable = fs.lstat(path, {bigint}); | ||
record = createRecordFromThenable(thenable); | ||
lstatCache.push(bigint, record); | ||
} | ||
const stats = readRecord(record).value; | ||
return stats; | ||
} | ||
|
||
function createReaddirCache(): Map< | ||
string, | ||
Array<string | boolean | Record<mixed>>, | ||
> { | ||
return new Map(); | ||
} | ||
|
||
export function readdir( | ||
path: string, | ||
options?: string | {encoding?: string, withFileTypes?: boolean}, | ||
): mixed { | ||
checkPathInDev(path); | ||
let encoding = 'utf8'; | ||
let withFileTypes = false; | ||
if (typeof options === 'string') { | ||
encoding = options; | ||
} else if (options != null) { | ||
if (options.encoding) { | ||
encoding = options.encoding; | ||
} | ||
if (options.withFileTypes) { | ||
withFileTypes = true; | ||
} | ||
} | ||
const map = unstable_getCacheForType(createReaddirCache); | ||
let readdirCache = map.get(path); | ||
if (!readdirCache) { | ||
readdirCache = []; | ||
map.set(path, readdirCache); | ||
} | ||
let record; | ||
for (let i = 0; i < readdirCache.length; i += 3) { | ||
const cachedEncoding: string = (readdirCache[i]: any); | ||
const cachedWithFileTypes: boolean = (readdirCache[i + 1]: any); | ||
if (encoding === cachedEncoding && withFileTypes === cachedWithFileTypes) { | ||
const cachedRecord: Record<void> = (readdirCache[i + 2]: any); | ||
record = cachedRecord; | ||
break; | ||
} | ||
} | ||
if (!record) { | ||
const thenable = fs.readdir(path, {encoding, withFileTypes}); | ||
record = createRecordFromThenable(thenable); | ||
readdirCache.push(encoding, withFileTypes, record); | ||
} | ||
const files = readRecord(record).value; | ||
return files; | ||
} | ||
|
||
function createReadFileCache(): Map<string, Record<Buffer>> { | ||
return new Map(); | ||
} | ||
|
@@ -106,15 +222,16 @@ export function readFile( | |
signal?: mixed, // We'll have our own signal | ||
}, | ||
): string | Buffer { | ||
const map = unstable_getCacheForType(createReadFileCache); | ||
checkPathInDev(path); | ||
const map = unstable_getCacheForType(createReadFileCache); | ||
let record = map.get(path); | ||
if (!record) { | ||
const thenable = fs.readFile(path); | ||
record = createRecordFromThenable(thenable); | ||
map.set(path, record); | ||
} | ||
const buffer: Buffer = readRecordValue(record); | ||
const resolvedRecord = readRecord(record); | ||
const buffer: Buffer = resolvedRecord.value; | ||
if (!options) { | ||
return buffer; | ||
} | ||
|
@@ -136,7 +253,7 @@ export function readFile( | |
if (typeof encoding !== 'string') { | ||
return buffer; | ||
} | ||
const textCache = record.cache; | ||
const textCache = resolvedRecord.cache || (resolvedRecord.cache = []); | ||
for (let i = 0; i < textCache.length; i += 2) { | ||
if (textCache[i] === encoding) { | ||
return (textCache[i + 1]: any); | ||
|
@@ -146,3 +263,119 @@ export function readFile( | |
textCache.push(encoding, text); | ||
return text; | ||
} | ||
|
||
function createReadlinkCache(): Map<string, Array<string | Record<mixed>>> { | ||
return new Map(); | ||
} | ||
|
||
export function readlink( | ||
path: string, | ||
options?: string | {encoding?: string}, | ||
): mixed { | ||
checkPathInDev(path); | ||
let encoding = 'utf8'; | ||
if (typeof options === 'string') { | ||
encoding = options; | ||
} else if (options != null) { | ||
if (options.encoding) { | ||
encoding = options.encoding; | ||
} | ||
} | ||
const map = unstable_getCacheForType(createReadlinkCache); | ||
let readlinkCache = map.get(path); | ||
if (!readlinkCache) { | ||
readlinkCache = []; | ||
map.set(path, readlinkCache); | ||
} | ||
let record; | ||
for (let i = 0; i < readlinkCache.length; i += 2) { | ||
const cachedEncoding: string = (readlinkCache[i]: any); | ||
if (encoding === cachedEncoding) { | ||
const cachedRecord: Record<void> = (readlinkCache[i + 1]: any); | ||
record = cachedRecord; | ||
break; | ||
} | ||
} | ||
if (!record) { | ||
const thenable = fs.readlink(path, {encoding}); | ||
record = createRecordFromThenable(thenable); | ||
readlinkCache.push(encoding, record); | ||
} | ||
const linkString = readRecord(record).value; | ||
return linkString; | ||
} | ||
|
||
function createRealpathCache(): Map<string, Array<string | Record<mixed>>> { | ||
return new Map(); | ||
} | ||
|
||
export function realpath( | ||
path: string, | ||
options?: string | {encoding?: string}, | ||
): mixed { | ||
checkPathInDev(path); | ||
let encoding = 'utf8'; | ||
if (typeof options === 'string') { | ||
encoding = options; | ||
} else if (options != null) { | ||
if (options.encoding) { | ||
encoding = options.encoding; | ||
} | ||
} | ||
const map = unstable_getCacheForType(createRealpathCache); | ||
let realpathCache = map.get(path); | ||
if (!realpathCache) { | ||
realpathCache = []; | ||
map.set(path, realpathCache); | ||
} | ||
let record; | ||
for (let i = 0; i < realpathCache.length; i += 2) { | ||
const cachedEncoding: string = (realpathCache[i]: any); | ||
if (encoding === cachedEncoding) { | ||
const cachedRecord: Record<void> = (realpathCache[i + 1]: any); | ||
record = cachedRecord; | ||
break; | ||
} | ||
} | ||
if (!record) { | ||
const thenable = fs.realpath(path, {encoding}); | ||
record = createRecordFromThenable(thenable); | ||
realpathCache.push(encoding, record); | ||
} | ||
const resolvedPath = readRecord(record).value; | ||
return resolvedPath; | ||
} | ||
|
||
function createStatCache(): Map<string, Array<boolean | Record<mixed>>> { | ||
return new Map(); | ||
} | ||
|
||
export function stat(path: string, options?: {bigint?: boolean}): mixed { | ||
checkPathInDev(path); | ||
let bigint = false; | ||
if (options && options.bigint) { | ||
bigint = true; | ||
} | ||
const map = unstable_getCacheForType(createStatCache); | ||
let statCache = map.get(path); | ||
if (!statCache) { | ||
statCache = []; | ||
map.set(path, statCache); | ||
} | ||
let record; | ||
for (let i = 0; i < statCache.length; i += 2) { | ||
const cachedBigint: boolean = (statCache[i]: any); | ||
if (bigint === cachedBigint) { | ||
const cachedRecord: Record<void> = (statCache[i + 1]: any); | ||
record = cachedRecord; | ||
break; | ||
} | ||
} | ||
if (!record) { | ||
const thenable = fs.stat(path, {bigint}); | ||
record = createRecordFromThenable(thenable); | ||
statCache.push(bigint, record); | ||
} | ||
const stats = readRecord(record).value; | ||
return stats; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these encodings default to "utf8" which is the common case. So might make more sense to eagerly allocate these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note I don't end up using this field for most. It's actually only used for
readFile
.This is because the rest need a Promise-per-cache-entry. So it's the other way around for them.
Map { path => [mixed, Record, mixed, Record, ...] }