fs
import {
readFile,
writeFile,
readStream,
stat,
readDir,
mkdir,
rmdir,
rename,
unlink,
exists,
} from 'mikro/fs'File I/O against the on-device LittleFS partition. Paths resolve against the app's filesystem root; .. segments are normalized and cannot escape it.
The /app read-only zone
Bundled code, assets, and mikro.config.json live under /app. Writes to any path under /app fail with FSError.AccessDenied. User data goes anywhere else: /data/, /logs/, or the root.
Reading
readFile(path)
Read a whole file as a Uint8Array.
function readFile(path: string): Result<Uint8Array, FSError>
function readFile(path: string, encoding: 'utf-8'): Result<string, FSError>Passing 'utf-8' returns a decoded string instead.
const data = readFile('/app/static/logo.pbm').orPanic('missing logo')
const config = readFile('/data/config.json', 'utf-8').orPanic('missing config')Files larger than fsReadMax (default 64 KiB) fail with FSError.TooLarge. Use readStream for bigger files.
readStream(path, options?)
Read a file as a stream of byte chunks.
function readStream(
path: string,
options?: {chunkSize?: number},
): Result<AsyncIterable<Result<Uint8Array, FSError>>, FSError>The outer Result wraps the initial open. Once iterating, each yielded item is itself a Result<Uint8Array, FSError>: ok-wrapped chunks on success, a single terminal err item if a mid-stream read fails. The underlying handle closes on EOF, error, or early consumer break.
Composes with mikro/stream for text and line processing:
const stream = readStream('/data/events.log').orPanic('log missing')
for await (const line of splitLines(decodeUtf8(stream))) {
if (!line.ok) {
console.error('read failed:', line.error)
break
}
console.log(line.value)
}Default chunkSize is 512 bytes.
Writing
writeFile(path, contents, options?)
Write a string or Uint8Array to a file.
function writeFile(
path: string,
contents: string | Uint8Array,
options?: {create?: boolean; append?: boolean},
): Result<void, FSError>Options:
create(defaulttrue): whenfalse, fail withNotFoundif the file doesn't existappend(defaultfalse): whentrue, append instead of truncating
writeFile('/data/config.json', JSON.stringify({count: 1})).orPanic('write')
writeFile('/data/events.log', `${Date.now()} boot\n`, {append: true})Metadata
stat(path)
Read file metadata.
function stat(path: string): Result<StatResult, FSError>
interface StatResult {
size: number
isFile: boolean
isDirectory: boolean
mtime?: number // ms since epoch; absent when unsupported
}const r = stat('/data/config.json')
if (r.ok) console.log(`${r.value.size} bytes`)exists(path)
Test whether a path exists. Returns a plain boolean; unreachable paths return false.
function exists(path: string): booleanif (exists('/data/config.json')) {
// ...
}Directories
readDir(path)
List a directory. Each entry carries its type.
function readDir(path: string): Result<DirEntry[], FSError>
interface DirEntry {
name: string
isFile: boolean
isDirectory: boolean
}const r = readDir('/data')
if (r.ok) {
for (const entry of r.value) {
if (entry.isFile) console.log(entry.name)
}
}mkdir(path, options?)
Create a directory.
function mkdir(path: string, options?: {recursive?: boolean}): Result<void, FSError>recursive: true creates intermediate directories as needed.
mkdir('/data/cache/images', {recursive: true}).orPanic('mkdir')rmdir(path)
Remove an empty directory. For non-empty directories, walk and unlink entries first.
function rmdir(path: string): Result<void, FSError>File operations
rename(from, to)
Move or rename a file.
function rename(from: string, to: string): Result<void, FSError>unlink(path)
Delete a file.
function unlink(path: string): Result<void, FSError>FSError
All fs errors share the FSError discriminated union. Discriminate on .name.
| Variant | Fields | Meaning |
|---|---|---|
NotFound | path | File or directory doesn't exist |
AlreadyExists | path | Target already exists |
AccessDenied | path | Write under /app, or permission denied |
NoSpace | path | Filesystem full |
TooLarge | path, limit? | readFile exceeded fsReadMax |
IsDirectory | path | File operation on a directory |
NotDirectory | path | Directory operation on a file |
BadFileDescriptor | {} | Operation on a closed handle |
Unknown | code, errno, message | Anything not covered above |
const r = writeFile('/data/log.txt', 'hello')
if (!r.ok) {
switch (r.error.name) {
case 'NoSpace':
console.error('disk full')
break
case 'AccessDenied':
console.error('readonly:', r.error.path)
break
default:
console.error('write failed:', r.error)
}
}Configuration
fsReadMax
Maximum size in bytes for a single readFile call. Files larger than this fail with FSError.TooLarge; use readStream for bigger files. Default 65536 (64 KiB).
// mikro.config.ts
import {defineConfig} from 'mikro'
export default defineConfig({
fsReadMax: '128k',
})Accepts a number (bytes) or a string with K/M/G suffix. Normalized to a plain number at build time.