feat(fs): add directory listing seam

This commit is contained in:
Yichen Jiang
2026-07-03 14:36:20 +08:00
parent d8ea99756d
commit 803ed4bd95
15 changed files with 268 additions and 23 deletions

View File

@@ -59,6 +59,7 @@
import { Context, Service } from 'cordis'
import type {
FsDirEntry,
FsEditOutcome,
FsEditRequest,
FsInfo,
@@ -76,6 +77,7 @@ export {
export type {
FsEditOutcome,
FsEditRequest,
FsDirEntry,
FsErrorCode,
FsInfo,
FsTarget,
@@ -131,7 +133,7 @@ declare module 'cordis' {
}
/**
* Abstract filesystem provider service. Subclass, implement the six text-storage
* Abstract filesystem provider service. Subclass, implement the seven storage
* primitives, and load the subclass as a plugin — it registers as `ctx.fs` (one
* implementation per context; loading a second throws, cordis' standard
* duplicate-service behavior).
@@ -145,6 +147,11 @@ declare module 'cordis' {
* - {@link readText}/{@link streamText} read the whole regular text file (the
* stream for large files); both own regular-file checks, UTF-8 decoding,
* binary/NUL rejection, and `FS_NOT_TEXT`.
* - {@link listDir} returns direct children of a directory in stable name order
* with resolved child targets and cheap metadata only. It never reads file
* contents. Missing targets throw `FS_NOT_FOUND`, non-directories throw
* `FS_NOT_DIRECTORY`, permission failures throw `FS_PERMISSION_DENIED`, and
* other backend I/O failures throw `FS_IO_ERROR`.
* - {@link writeText} is atomic temp-file + rename. `expected` is OPTIONAL:
* omit it for an unconditional create-or-overwrite (the bare-provider default),
* or supply a {@link FsWriteIntent} to guard the write.
@@ -190,6 +197,12 @@ export abstract class FileSystem extends Service {
*/
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
/**
* List direct children of a directory in stable name order. Returns resolved
* child targets plus cheap metadata only; never reads file contents.
*/
abstract listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>
/**
* Create or fully replace a UTF-8 text file atomically. `expected` is the
* create-vs-replace decision and stale guard when supplied; OMITTING it is an

View File

@@ -78,6 +78,23 @@ export interface FsInfo {
size?: number
}
/**
* One direct child returned by {@link FileSystem.listDir}. Listing returns
* metadata and resolved targets only; it must not read file contents.
*/
export interface FsDirEntry {
/** Basename of the child inside the listed directory. */
name: string
/** Whether the child is a regular file, a directory, or something else. */
type: 'file' | 'directory' | 'other'
/** Resolved child target for follow-up operations. */
target: FsTarget
/** Opaque freshness token when the backend can report metadata cheaply. */
version?: FsVersion
/** Byte size of a regular file, when the backend can report it. */
size?: number
}
/**
* The explicit intent of a guarded {@link FileSystem.writeText} call.
* `createIfAbsent` creates a missing target and rejects an existing one with
@@ -130,8 +147,11 @@ export interface FsEditOutcome {
*/
export type FsErrorCode =
| 'FS_NOT_FOUND'
| 'FS_NOT_DIRECTORY'
| 'FS_NOT_TEXT'
| 'FS_NOT_REGULAR_FILE'
| 'FS_PERMISSION_DENIED'
| 'FS_IO_ERROR'
| 'FS_STALE_VERSION'
| 'FS_NOT_OBSERVED'
| 'FS_AMBIGUOUS_EDIT'