Skip to content

Node fs polyfill

@fkn/lib’s fs implements a common path-based subset of Node fs. @fkn/fs-polyfill packages it as a browser replacement for Node’s built-in fs: point a bundler’s fs and fs/promises aliases at it and code using the supported operations resolves to FKN storage.

// vite.config.ts - or any bundler's resolve aliases
export default {
resolve: {
alias: {
fs: '@fkn/fs-polyfill',
'fs/promises': '@fkn/fs-polyfill/promises',
},
},
}
// unchanged Node-style code, now running in the browser on FKN storage
import fs from 'fs'
import { mount } from '@fkn/fs-polyfill'
await mount() // hydrate the in-memory layer before sync reads
fs.writeFileSync('/save/state.json', JSON.stringify({ ok: true }))

@fkn/vite-plugin wires the equivalent @fkn/lib aliases, plus net, dgram, and http, in one line.

If you import @fkn/lib directly you do not need the polyfill package - fs is right there:

import {
(alias) namespace fs
import fs
fs
} from '@fkn/lib'
await
(alias) namespace fs
import fs
fs
.
index_d_exports.mount(): Promise<void>
export index_d_exports.mount
mount
()
(alias) namespace fs
import fs
fs
.
index_d_exports.writeFileSync(path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions): void
export index_d_exports.writeFileSync
writeFileSync
('/save/state.json',
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
level: number
level
: 7 }))
const
const buf: string | Buffer<ArrayBufferLike>
buf
=
(alias) namespace fs
import fs
fs
.
index_d_exports.readFileSync(path: import("node:fs").PathLike, options?: ReadOptions): Buffer | string
export index_d_exports.readFileSync
readFileSync
('/save/state.json') // Buffer
const
const txt: string | Buffer<ArrayBufferLike>
txt
=
(alias) namespace fs
import fs
fs
.
index_d_exports.readFileSync(path: import("node:fs").PathLike, options?: ReadOptions): Buffer | string
export index_d_exports.readFileSync
readFileSync
('/save/state.json', 'utf8') // decoded string
await
(alias) namespace fs
import fs
fs
.
const index_d_exports.promises: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
appendFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
stat: (path: import("node:fs").PathLike) => Promise<fs.Stats>;
lstat: (path: import("node:fs").PathLike) => Promise<fs.Stats>;
... 6 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
export index_d_exports.promises
promises
.
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>
writeFile
('/save/notes.txt', 'hello')

There is no synchronous disk in a browser, so the polyfill keeps an in-memory layer that synchronous calls read and write instantly. mount() hydrates it from storage; a sync read before it resolves sees ENOENT. The promise and callback forms await the mount internally. Writes flush back to storage on a debounce and on tab-hide. flush() attempts pending local or hybrid backing writes but catches backing failures and still resolves. Cloud replication from the hybrid backing is best-effort and can finish later. Use cloud.fs for an awaited cloud write.

The common file operations - read, write, append, stat, readdir, mkdir, rm, rename - in sync, callback, and promise forms. File descriptors (open / fd), streams (createReadStream), watch, and readdir’s withFileTypes are not implemented yet. There is no symlink support either: lstat is aliased to stat, and Stats.isSymbolicLink() always returns false.