Skip to content

fs ​

ts
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.

ts
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.

ts
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.

ts
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:

ts
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.

ts
function writeFile(
  path: string,
  contents: string | Uint8Array,
  options?: {create?: boolean; append?: boolean},
): Result<void, FSError>

Options:

  • create (default true): when false, fail with NotFound if the file doesn't exist
  • append (default false): when true, append instead of truncating
ts
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.

ts
function stat(path: string): Result<StatResult, FSError>

interface StatResult {
  size: number
  isFile: boolean
  isDirectory: boolean
  mtime?: number // ms since epoch; absent when unsupported
}
ts
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.

ts
function exists(path: string): boolean
ts
if (
exists
('/data/config.json')) {
// ... }

Directories ​

readDir(path) ​

List a directory. Each entry carries its type.

ts
function readDir(path: string): Result<DirEntry[], FSError>

interface DirEntry {
  name: string
  isFile: boolean
  isDirectory: boolean
}
ts
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.

ts
function mkdir(path: string, options?: {recursive?: boolean}): Result<void, FSError>

recursive: true creates intermediate directories as needed.

ts
mkdir
('/data/cache/images', {
recursive
: true}).
orPanic
('mkdir')

rmdir(path) ​

Remove an empty directory. For non-empty directories, walk and unlink entries first.

ts
function rmdir(path: string): Result<void, FSError>

File operations ​

rename(from, to) ​

Move or rename a file.

ts
function rename(from: string, to: string): Result<void, FSError>

Delete a file.

ts
function unlink(path: string): Result<void, FSError>

FSError ​

All fs errors share the FSError discriminated union. Discriminate on .name.

VariantFieldsMeaning
NotFoundpathFile or directory doesn't exist
AlreadyExistspathTarget already exists
AccessDeniedpathWrite under /app, or permission denied
NoSpacepathFilesystem full
TooLargepath, limit?readFile exceeded fsReadMax
IsDirectorypathFile operation on a directory
NotDirectorypathDirectory operation on a file
BadFileDescriptor{}Operation on a closed handle
Unknowncode, errno, messageAnything not covered above
ts
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).

ts
// 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.

Mikro.js is built with AI assistance, code and docs alike. Read the AI disclosure.