Docs Packages
@procedures/fs
File system operations as RPC procedures. Read, write, copy, move, glob files and directories via client.call().
Overview#
client-fs provides comprehensive file system access through the client procedure ecosystem with:
- File operations: Read, write, stat, exists, remove
- Directory operations: Create, list, copy, move
- Pattern matching: Glob file search via the Node.js
fs.globAPI (node:fs/promises, requires Node >= 22) - JSON utilities: Read and parse JSON files
- Zod validation: Runtime type safety on all inputs
The package auto-registers 11 procedures:
fs.read, fs.write, fs.exists, fs.stat, fs.mkdir, fs.rm, fs.readdir, fs.copy, fs.move, fs.glob, fs.read.json
Architecture#
graph TB
subgraph "Client Ecosystem"
Client["@procedures/client"]
Registry["Procedure Registry"]
end
subgraph "client-fs Package"
Register["register.ts<br/>(Auto-registers procedures)"]
Types["types.ts<br/>(Zod Schemas + Types)"]
subgraph "Procedure Categories"
FileOps["File Operations<br/>read, write, exists, stat, rm"]
DirOps["Directory Operations<br/>mkdir, readdir, copy, move"]
PatternOps["Pattern Operations<br/>glob, read.json"]
end
end
subgraph "Node.js Modules"
NodeFs["node:fs/promises"]
NodePath["node:path"]
NodeGlob["node:fs/promises glob<br/>(Node >= 22)"]
end
subgraph "File System"
FS["Local File System"]
end
Register --> Client
Register --> Registry
FileOps --> NodeFs
DirOps --> NodeFs
PatternOps --> NodeGlob
NodeFs --> NodePath
NodeFs --> FS
NodeGlob --> FS
Types -.validates.-> FileOps
Types -.validates.-> DirOps
Types -.validates.-> PatternOps
style Register fill:#e1f5ff
style Types fill:#f0f0f0Package Dependency Flow#
graph LR
App["Your Application"]
ClientFs["@procedures/fs"]
Client["@procedures/client"]
Zod["zod"]
NodeModules["node:fs/promises<br/>(glob: Node >= 22)"]
App --> ClientFs
ClientFs --> Client
ClientFs --> Zod
ClientFs --> NodeModules
App -.client.call.-> Client
Client -.executes.-> ClientFs
ClientFs -.validates.-> Zod
ClientFs -.file ops.-> NodeModulesInstallation#
npm install @procedures/fs
Peer Dependencies#
{
"@procedures/client": "github:mark1russell7/client#main",
"zod": "^3.24.0"
}
API Reference#
File Operations#
fs.read#
Read file contents as a string.
const result = await client.exec(
["fs", "read"],
{
path: "./config.json",
encoding: "utf8"
}
);
console.log(result.content);
console.log(result.stats.size);
Input:
interface ReadInput {
path: string; // File path (absolute or relative)
encoding?: string; // Default: "utf8"
}
Output:
interface ReadOutput {
content: string; // File contents
path: string; // Resolved absolute path
stats: StatOutput; // File statistics
}
fs.write#
Write content to a file.
await client.exec(
["fs", "write"],
{
path: "./output.txt",
content: "Hello, World!",
mode: "write" // or "append"
}
);
Input:
interface WriteInput {
path: string; // File path
content: string; // Content to write
encoding?: string; // Default: "utf8"
mode?: "write" | "append"; // Default: "write"
}
Output:
interface WriteOutput {
path: string; // Written file path
bytesWritten: number; // Number of bytes written
}
fs.exists#
Check if a path exists and get its type.
const result = await client.exec(
["fs", "exists"],
{ path: "./my-file.txt" }
);
if (result.exists) {
console.log(`Path exists and is a ${result.stats.type}`);
}
Input:
interface ExistsInput {
path: string;
}
Output:
type ExistsOutput =
| { exists: false }
| { exists: true; stats: StatOutput };
fs.stat#
Get file or directory statistics.
const result = await client.exec(
["fs", "stat"],
{ path: "./my-directory" }
);
console.log(result.type); // "file" | "directory" | "other"
console.log(result.size); // Size in bytes
Input:
interface StatInput {
path: string;
}
Output:
interface StatOutput {
path: string;
type: FileType; // "file" | "directory" | "other"
size: number; // Size in bytes
mtime: string; // Modified time (ISO)
ctime: string; // Created time (ISO)
atime: string; // Accessed time (ISO)
mode: number; // Unix permissions
}
enum FileType {
File = "file",
Directory = "directory",
Other = "other"
}
fs.rm#
Remove a file or directory.
// Remove file
await client.exec(
["fs", "rm"],
{ path: "./old-file.txt" }
);
// Remove directory recursively
await client.exec(
["fs", "rm"],
{
path: "./old-directory",
recursive: true,
force: true
}
);
Input:
interface RmInput {
path: string;
recursive?: boolean; // Default: false
force?: boolean; // Ignore if doesn't exist (default: false)
}
Output:
interface RmOutput {
path: string;
removed: boolean; // false if didn't exist with force=true
}
Directory Operations#
fs.mkdir#
Create a directory.
await client.exec(
["fs", "mkdir"],
{
path: "./new/nested/directory",
recursive: true // Create parents if needed
}
);
Input:
interface MkdirInput {
path: string;
recursive?: boolean; // Default: true
}
Output:
interface MkdirOutput {
path: string;
created: boolean; // false if already existed
}
fs.readdir#
List directory contents.
const result = await client.exec(
["fs", "readdir"],
{
path: "./src",
recursive: true,
includeStats: true
}
);
result.entries.forEach(entry => {
console.log(`${entry.type}: ${entry.path}`);
if (entry.stats) {
console.log(` Size: ${entry.stats.size} bytes`);
}
});
Input:
interface ReaddirInput {
path: string;
recursive?: boolean; // Default: false
includeStats?: boolean; // Default: false
}
Output:
interface ReaddirOutput {
path: string;
entries: ReaddirEntry[];
}
interface ReaddirEntry {
name: string; // Entry name
path: string; // Full path
type: FileType; // "file" | "directory" | "other"
stats?: StatOutput; // If includeStats=true
}
fs.copy#
Copy a file or directory.
// Copy file
await client.exec(
["fs", "copy"],
{
src: "./source.txt",
dest: "./destination.txt",
overwrite: true
}
);
// Copy directory recursively
await client.exec(
["fs", "copy"],
{
src: "./src-dir",
dest: "./dest-dir",
recursive: true
}
);
Input:
interface CopyInput {
src: string;
dest: string;
recursive?: boolean; // Default: true
overwrite?: boolean; // Default: false
}
Output:
interface CopyOutput {
src: string;
dest: string;
}
fs.move#
Move or rename a file or directory.
await client.exec(
["fs", "move"],
{
src: "./old-name.txt",
dest: "./new-name.txt",
overwrite: false
}
);
Input:
interface MoveInput {
src: string;
dest: string;
overwrite?: boolean; // Default: false
}
Output:
interface MoveOutput {
src: string;
dest: string;
}
Pattern Matching Operations#
fs.glob#
Find files matching a glob pattern using the Node.js fs.glob API (node:fs/promises, requires Node >= 22).
const result = await client.exec(
["fs", "glob"],
{
pattern: "src/**/*.{ts,tsx}",
cwd: "/my/project",
absolute: true,
dot: false
}
);
console.log(result.matches);
// ["/my/project/src/index.ts", "/my/project/src/components/App.tsx", ...]
Input:
interface GlobInput {
pattern: string; // Glob pattern (e.g., "**/*.ts")
cwd?: string; // Base directory (default: process.cwd())
absolute?: boolean; // Return absolute paths (default: false)
dot?: boolean; // Include dotfiles (default: false)
}
Output:
interface GlobOutput {
pattern: string;
matches: string[]; // Matching file paths
}
Glob Pattern Examples:
// All TypeScript files
"**/*.ts"
// All files in src directory
"src/**/*"
// TypeScript and JavaScript files
"**/*.{ts,js}"
// Note: node:fs/promises glob does NOT exclude node_modules by default;
// pass an `exclude` pattern or scope the pattern (e.g. "src/**/*.ts")
"src/**/*.ts"
// Include test files
"**/*.{test,spec}.ts"
fs.read.json#
Read and parse a JSON file.
const result = await client.exec(
["fs", "read.json"],
{ path: "./package.json" }
);
console.log(result.data); // Parsed JSON object
Input:
interface ReadJsonInput {
path: string;
}
Output:
interface ReadJsonOutput {
path: string;
data: unknown; // Parsed JSON (cast to your type)
stats: StatOutput;
}
Type-safe example:
interface PackageJson {
name: string;
version: string;
dependencies: Record<string, string>;
}
const result = await client.exec(
["fs", "read.json"],
{ path: "./package.json" }
);
const pkg = result.data as PackageJson;
console.log(pkg.name, pkg.version);
Usage Examples#
Basic File Operations#
import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
import "@procedures/fs"; // registers its procedures
const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
// Read a file
const { content } = await client.exec(
["fs", "read"],
{ path: "./README.md" }
);
// Write to a file
await client.exec(
["fs", "write"],
{
path: "./output.txt",
content: "Generated content\n",
mode: "write"
}
);
// Append to a file
await client.exec(
["fs", "write"],
{
path: "./log.txt",
content: `[${new Date().toISOString()}] Log entry\n`,
mode: "append"
}
);
// Check if file exists
const exists = await client.exec(
["fs", "exists"],
{ path: "./config.json" }
);
if (exists.exists) {
// Get file stats
const stats = await client.exec(
["fs", "stat"],
{ path: "./config.json" }
);
console.log(`File size: ${stats.size} bytes`);
}
Directory Operations#
// Create nested directories
await client.exec(
["fs", "mkdir"],
{
path: "./build/assets/images",
recursive: true
}
);
// List directory contents
const { entries } = await client.exec(
["fs", "readdir"],
{
path: "./src",
includeStats: true
}
);
entries.forEach(entry => {
if (entry.type === "file") {
console.log(`File: ${entry.name} (${entry.stats?.size} bytes)`);
} else {
console.log(`Directory: ${entry.name}`);
}
});
// Recursive directory listing
const { entries: allFiles } = await client.exec(
["fs", "readdir"],
{
path: "./src",
recursive: true
}
);
console.log(`Total items: ${allFiles.length}`);
Copy and Move Operations#
// Backup a file
await client.exec(
["fs", "copy"],
{
src: "./important-data.json",
dest: "./backups/important-data.backup.json"
}
);
// Copy entire directory
await client.exec(
["fs", "copy"],
{
src: "./templates",
dest: "./build/templates",
recursive: true,
overwrite: true
}
);
// Rename a file
await client.exec(
["fs", "move"],
{
src: "./old-name.txt",
dest: "./new-name.txt"
}
);
// Move to different directory
await client.exec(
["fs", "move"],
{
src: "./temp/file.txt",
dest: "./archive/file.txt",
overwrite: false
}
);
Pattern Matching with Glob#
// Find all TypeScript files
const { matches: tsFiles } = await client.exec(
["fs", "glob"],
{
pattern: "**/*.ts",
cwd: "./src",
absolute: true
}
);
console.log(`Found ${tsFiles.length} TypeScript files`);
// Find test files
const { matches: testFiles } = await client.exec(
["fs", "glob"],
{
pattern: "**/*.{test,spec}.{ts,js}",
cwd: "."
}
);
// Find config files
const { matches: configs } = await client.exec(
["fs", "glob"],
{
pattern: "**/*.config.{js,ts,json}",
dot: true // Include dotfiles like .eslintrc.json
}
);
JSON File Operations#
// Read package.json
const { data: pkg } = await client.exec(
["fs", "read.json"],
{ path: "./package.json" }
);
console.log(`Package: ${pkg.name}@${pkg.version}`);
// Read tsconfig.json
const { data: tsconfig } = await client.exec(
["fs", "read.json"],
{ path: "./tsconfig.json" }
);
console.log("Compiler options:", tsconfig.compilerOptions);
// Read and modify JSON
const { data: config } = await client.exec(
["fs", "read.json"],
{ path: "./config.json" }
);
config.lastUpdated = new Date().toISOString();
await client.exec(
["fs", "write"],
{
path: "./config.json",
content: JSON.stringify(config, null, 2)
}
);
Build Script Example#
// Clean build directory
await client.exec(
["fs", "rm"],
{
path: "./dist",
recursive: true,
force: true
}
);
// Create build directory
await client.exec(
["fs", "mkdir"],
{ path: "./dist" }
);
// Find all source files
const { matches: sourceFiles } = await client.exec(
["fs", "glob"],
{
pattern: "src/**/*.ts",
absolute: true
}
);
// Process each file...
for (const file of sourceFiles) {
const { content } = await client.exec(
["fs", "read"],
{ path: file }
);
// Transform content...
const transformed = content.replace(/\.ts$/, ".js");
await client.exec(
["fs", "write"],
{
path: file.replace("src/", "dist/").replace(".ts", ".js"),
content: transformed
}
);
}
// Copy assets
await client.exec(
["fs", "copy"],
{
src: "./public",
dest: "./dist/public",
recursive: true
}
);
Integration with Ecosystem#
Bundle Packages#
client-fs can be included in bundle packages:
// In bundle-dev/src/register.ts
import "@procedures/fs"; // registers its procedures
import "@procedures/shell"; // registers its procedures
import "@procedures/cli-run"; // registers its procedures
Dependencies#
Runtime Dependencies#
- @procedures/client - Core client procedure system
- zod (^3.24.0) - Runtime type validation
Implicit Dependencies#
- node:fs/promises - Node.js file system promises API
- node:path - Node.js path utilities
- node:fs/promises
glob- Glob pattern matching (used by thefs.globprocedure; requires Node >= 22)
Development Dependencies#
- @mark1russell7/cue - Configuration and type utilities
- typescript (^5.9.3) - TypeScript compiler
- @types/node (^22.0.0) - Node.js type definitions
Package Structure#
client-fs/
├── src/
│ ├── types.ts # Zod schemas + TypeScript types
│ ├── register.ts # Auto-registration
│ ├── index.ts # Main entry point
│ ├── procedures/
│ │ ├── fs.ts # Barrel export
│ │ └── fs/
│ │ ├── index.ts # Procedure exports
│ │ ├── read.ts # fs.read
│ │ ├── write.ts # fs.write
│ │ ├── exists.ts # fs.exists
│ │ ├── stat.ts # fs.stat
│ │ ├── mkdir.ts # fs.mkdir
│ │ ├── rm.ts # fs.rm
│ │ ├── readdir.ts # fs.readdir
│ │ ├── copy.ts # fs.copy
│ │ ├── move.ts # fs.move
│ │ ├── glob.ts # fs.glob
│ │ └── json.ts # fs.read.json
├── dist/ # Compiled output
├── package.json
├── tsconfig.json
└── README.md
Development#
Build#
npm run build
Type Check#
npm run typecheck
Clean#
npm run clean
License#
MIT
Author#
Mark Russell marktheprogrammer17@gmail.com