/** * # FS Protocol * * The filesystem protocol revolves around sending and receiving operations to * update the {@link MemoryFS} instance on Pitcher's clients and server. * This allows operations to be conflict resolving by the commutative and * monotonic properties of MemoryFS's CRDT tree structure. * * ## Load Project * * To populate MemoryFS with an initial snapshot of the project's files, we send * {@link LoadProject} to request the list of MemoryFS nodes and children. These * contain the file paths, ids, metadata, but _not_ the file contents. * * This allows us to operate on files by their ids, allowing us not to worry * about their name or path at any given moment in time. * * ## Operations * * To inform the server of new filesystem operations, {@link SendOps} is used to * send a list of {@link OpMove}s generated by MemoryFS. * * To keep the client up-to-date, the {@link ListenOps} is used to subscribe to * new operations sent by other clients to the server, which can then be applied * to the client's MemoryFS. */ import { bedrockFS, Id } from "@codesandbox/pitcher-common"; import { PitcherErrorCode } from "../errors"; import { ProtocolError, TMessage, TNotification } from "../protocol"; export type FsCapabilities = { reading: boolean; writing: boolean; watching: boolean; }; export type FsServerCapabilities = { reading: boolean; watching: boolean; writing: boolean; }; export type FsClientCapabilities = { [key: string]: unknown; }; export type SearchResult = { fileId: Id; lines: { text: string; }; lineNumber: number; absoluteOffset: number; submatches: SearchSubMatch[]; }; export type StreamingSearchResult = { fileId?: Id; filepath: string; lines: { text: string; }; lineNumber: number; absoluteOffset: number; submatches: SearchSubMatch[]; }; export type SearchSubMatch = { match: { text: string; }; start: number; end: number; }; export type CommonError = ProtocolError; export type InvalidIdError = { code: PitcherErrorCode.INVALID_ID; }; export type RawFileSystemError = { code: PitcherErrorCode.RAWFS_ERROR; data: { errno: number | null; }; }; export interface FSReadResult { treeNodes: bedrockFS.JSONNode[]; clock: number; } /** Retreive the latest snapshot of the server's MemoryFS file and children list */ export type FSReadMessage = TMessage<"fs/read", null, { result: FSReadResult; error: CommonError; }>; export interface FSCreateOperation { type: "create"; parentId: Id; newEntry: { id: Id; type: bedrockFS.NodeType; name: string; }; } export interface FSDeleteOperation { type: "delete"; id: Id; } export interface FSMoveOperation { type: "move"; id: Id; parentId?: Id; name?: string; } export type FSOperation = FSCreateOperation | FSDeleteOperation | FSMoveOperation; export declare enum FSOperationResponseCode { Success = 0, Ignored = 1 } /** Send a list of tree operations reflecting filesystem operations */ export type FSOperationMessage = TMessage<"fs/operation", { operation: FSOperation; }, { result: { code: FSOperationResponseCode; clock: number; } | { code: FSOperationResponseCode.Ignored; }; error: CommonError; }>; export interface FSSearchParams { text: string; glob?: string; isRegex?: boolean; caseSensitivity?: "smart" | "enabled" | "disabled"; } export type FSSearchMessage = TMessage<"fs/search", FSSearchParams, { result: SearchResult[]; error: CommonError; }>; export interface FSStreamingSearchParams { searchId: Id; text: string; glob?: string; isRegex?: boolean; caseSensitivity?: "smart" | "enabled" | "disabled"; /** * That default limit is 10_000 results */ maxResults?: number; } export type FSStreamingSearchMessage = TMessage<"fs/streamingSearch", FSStreamingSearchParams, { result: { searchId: Id; }; error: CommonError; }>; export type FSCancelStreamingSearchMessage = TMessage<"fs/cancelStreamingSearch", { searchId: Id; }, { result: { searchId: Id; }; error: CommonError; }>; export interface PathSearchMatch { path: string; submatches: SearchSubMatch[]; } export interface PathSearchResult { matches: PathSearchMatch[]; } export interface PathSearchParams { text: string; } export type PathSearchMessage = TMessage<"fs/pathSearch", PathSearchParams, { result: PathSearchResult; error: CommonError; }>; export type FSUploadMessage = TMessage<"fs/upload", { parentId: Id; filename: string; content: Uint8Array; }, { result: { fileId: Id; }; error: CommonError | InvalidIdError; }>; export type FSDownloadMessage = TMessage<"fs/download", { path: string; /** * Glob patterns of files/folders to exclude from the download. Defaults to * *\*\/node_modules/\*\*. */ excludes?: string[]; }, { result: { downloadUrl: string; }; error: CommonError; }>; export type FSReadFileParams = { path: string; }; export type FSReadFileResult = { content: Uint8Array; }; export type FSReadFileMessage = TMessage<"fs/readFile", FSReadFileParams, { result: FSReadFileResult; error: CommonError | RawFileSystemError; }>; export type FSReadDirParams = { path: string; }; export type FSReadDirResult = { entries: { name: string; type: bedrockFS.NodeType; isSymlink: boolean; }[]; }; export type FSReadDirMessage = TMessage<"fs/readdir", FSReadDirParams, { result: FSReadDirResult; error: CommonError | RawFileSystemError; }>; export type FSWriteFileParams = { path: string; content: Uint8Array; create?: boolean; overwrite?: boolean; }; export type FSWRiteFileResult = Record; export type FSWriteFileMessage = TMessage<"fs/writeFile", FSWriteFileParams, { result: FSWRiteFileResult; error: CommonError | RawFileSystemError; }>; export type FSStatParams = { path: string; }; export type FSStatResult = { type: bedrockFS.NodeType; isSymlink: boolean; size: number; mtime: number; ctime: number; atime: number; }; export type FSStatMessage = TMessage<"fs/stat", FSStatParams, { result: FSStatResult; error: CommonError | RawFileSystemError; }>; export type FSCopyParams = { from: string; to: string; recursive?: boolean; overwrite?: boolean; }; export type FSCopyResult = Record; export type FSCopyMessage = TMessage<"fs/copy", FSCopyParams, { result: FSCopyResult; error: CommonError | RawFileSystemError; }>; export type FSRenameParams = { from: string; to: string; overwrite?: boolean; }; export type FSRenameResult = Record; export type FSRenameMessage = TMessage<"fs/rename", FSRenameParams, { result: FSRenameResult; error: CommonError | RawFileSystemError; }>; export type FSRemoveParams = { path: string; recursive?: boolean; }; export type FSRemoveResult = Record; export type FSRemoveMessage = TMessage<"fs/remove", FSRemoveParams, { result: FSRemoveResult; error: CommonError | RawFileSystemError; }>; export type FSMkdirParams = { path: string; recursive?: boolean; }; export type FSMkdirResult = Record; export type FSMkdirMessage = TMessage<"fs/mkdir", FSMkdirParams, { result: FSMkdirResult; error: CommonError | RawFileSystemError; }>; export type FSWatchParams = { path: string; recursive?: boolean; excludes?: string[]; }; export type FSWatchResult = { watchId: string; }; export type FSWatchMessage = TMessage<"fs/watch", FSWatchParams, { result: FSWatchResult; error: CommonError | RawFileSystemError; }>; export type FSUnwatchParams = { watchId: string; }; export type FSUnwatchResult = Record; export type FSUnwatchMessage = TMessage<"fs/unwatch", FSUnwatchParams, { result: FSUnwatchResult; error: CommonError | RawFileSystemError; }>; type RawFsMessage = FSReadFileMessage | FSReadDirMessage | FSWriteFileMessage | FSStatMessage | FSCopyMessage | FSRenameMessage | FSRemoveMessage | FSMkdirMessage | FSWatchMessage | FSUnwatchMessage; type FsMessage = FSReadMessage | FSOperationMessage | FSSearchMessage | FSStreamingSearchMessage | FSCancelStreamingSearchMessage | PathSearchMessage | FSUploadMessage | FSDownloadMessage | RawFsMessage; export type FsRequest = FsMessage["request"]; export type FsResponse = FsMessage["response"]; export interface FSWatchEvent { paths: string[]; type: "add" | "change" | "remove"; } export interface FSOperationEvent { operation: FSOperation; clock: number; } /** * Listen for tree operations reflecting filesystem operations made by * other clients */ export type FSOperationsNotification = TNotification<"fs/operations", { operations: FSOperationEvent[]; }>; export type FSWatchNotifiction = TNotification<"fs/watchEvent", { watchId: string; events: FSWatchEvent[]; }>; export type FSSearchMatchesNotifiction = TNotification<"fs/searchMatches", { searchId: string; matches: StreamingSearchResult[]; }>; export type FSSearchFinishedNotifiction = TNotification<"fs/searchFinished", { searchId: string; hitLimit: boolean; }>; export type FsNotification = FSOperationsNotification | FSWatchNotifiction | FSSearchMatchesNotifiction | FSSearchFinishedNotifiction; export {};