The Virtual File System (VFS) provides an in-memory file system that can be embedded in packed Oak binaries, enabling cross-platform file access without relying on the underlying operating system's file system.
- In-Memory Storage: Files are stored in memory as a simple key-value object
- Cross-Platform: Works on all platforms (Windows, Linux, macOS, Web/WASM)
- Path Normalization: Automatically normalizes paths (converts backslashes to forward slashes)
- Embeddable: Can be serialized as JSON and embedded in packed binaries
- Drop-in Replacement: Provides similar API to the standard
fsmodule
Virtual := import('Virtual')
// Create empty VFS
vfs := Virtual.createVirtualFS({})
// Create VFS with initial files
vfs := Virtual.createVirtualFS({
'config.json': '{"version": "1.0"}'
'data/test.txt': 'test data'
})
Read file contents from the VFS.
// Synchronous
content := vfs.readFile('config.json')
// Asynchronous
vfs.readFile('config.json') fn(content) {
// handle content
}
Write file contents to the VFS.
// Synchronous
vfs.writeFile('output.txt', 'Hello World')
// Asynchronous
vfs.writeFile('output.txt', 'Hello World') fn(result) {
// handle result
}
Check if a file exists in the VFS.
if vfs.exists?('config.json') {
true -> // file exists
}
Get file statistics.
// Synchronous
stat := vfs.statFile('config.json')
// Returns { name: path, size: length, dir?: false, mod: 0 }
// Asynchronous
vfs.statFile('config.json') fn(stat) {
// handle stat
}
List all files in the VFS.
// Synchronous
files := vfs.listFiles()
// Asynchronous
vfs.listFiles() fn(files) {
// handle files
}
Delete a file from the VFS.
// Synchronous
vfs.deleteFile('temp.txt')
// Asynchronous
vfs.deleteFile('temp.txt') fn(result) {
// handle result
}
Get all files as an object (for serialization).
allFiles := vfs.getFiles()
The VFS supports embedding files directly into Oak bundles and packed executables using the --includeVFS flag.
Bundle files into an Oak script or JavaScript bundle:
# Bundle a single file
oak build --entry app.oak --output bundle.oak --includeVFS data.txt
# Bundle multiple files (comma-separated)
oak build --entry app.oak --output bundle.oak --includeVFS config.json,data.txt
# Bundle with custom target names (using target:source syntax)
oak build --entry app.oak --output bundle.oak --includeVFS config:config.json,readme:README.md
# Bundle entire directories (recursively includes all files)
oak build --entry app.oak --output bundle.oak --includeVFS data:./data-dirWhen files are bundled with oak build, they are available via the global __Oak_VFS variable:
// Access bundled files
if __Oak_VFS? {
true -> {
content := __Oak_VFS.readFile('data.txt')
// Use the content...
}
_ -> println('No VFS data bundled')
}
Embed files into standalone executables:
# Pack with embedded files
oak pack --entry app.oak --output app --includeVFS config.json,assets:./assets
# The packed executable will contain the embedded files
./appFiles are embedded in the executable and automatically available at runtime.
The --includeVFS flag accepts specifications in the following formats:
- Simple file:
file.txt- bundlesfile.txtasfile.txtin the VFS - Target:source:
config:myconfig.json- bundlesmyconfig.jsonasconfigin the VFS - Directory:
assets:./assets-dir- recursively bundles all files fromassets-dirdirectory with the prefixassets/ - Multiple specs: Comma-separated list like
file1.txt,config:file2.json
Create a simple app that reads bundled configuration:
// app.oak
{
println: println
} := import('std')
if __Oak_VFS? {
true -> {
config := __Oak_VFS.readFile('config.json')
println('Config: ' << config)
}
}
Bundle it with a config file:
oak build --entry app.oak --output app.bundle.oak --includeVFS config.json
oak app.bundle.oakPack it as an executable:
oak pack --entry app.oak --output myapp --includeVFS config.json
./myapp
#### `setFiles(newFiles)`
Replace all files in the VFS.
```oak
vfs.setFiles({
'new.txt': 'new content'
})
The VFS is integrated with the oak pack command to embed files in standalone binaries.
Use the --include flag to embed files in the packed binary:
# Embed a single file
oak pack --entry main.oak --output app --include config.json
# Embed a directory
oak pack --entry main.oak --output app --include static:./static
# Embed multiple files/directories
oak pack --entry main.oak --output app --include "config.json,data:./data,lib:./lib"The --include flag accepts comma-separated specifications in these formats:
path- Embed file/directory at the same path in VFSname:path- Embed file/directory at a different name in VFS
Examples:
config.json→ VFS path:config.jsondata:./data→ VFS paths:data/file1.txt,data/file2.txt, etc.lib/helper.oak:./src/helper.oak→ VFS path:lib/helper.oak
When a packed binary runs, it can use the VFS through a runtime-provided module:
// Access the embedded VFS (if available)
vfs := ___packed_vfs()
if vfs {
? -> // No VFS embedded
_ -> {
// Use VFS
config := vfs.readFile('config.json')
// ... use config
}
}
In packed binaries, VFS data is stored as JSON at the end of the file:
[executable][oak bundle][bundle size][magic bytes][vfs json][vfs size][vfs magic]
- VFS JSON: Serialized object mapping paths to file contents
- VFS Size: 24-byte padded size of the VFS JSON
- VFS Magic: 8 bytes:
"vfs \x01\x00\x00\x00"
All paths are normalized to use forward slashes (/) and avoid double slashes:
vfs.writeFile('path\\to\\file.txt', 'data')
content := vfs.readFile('path/to/file.txt') // Works!
The VFS uses JSON for serialization to ensure cross-platform compatibility:
packUtils := import('pack-utils')
// Serialize
jsonString := packUtils.serializeVFS(vfsFiles)
// Deserialize
vfsFiles := packUtils.deserializeVFS(jsonString)
- Embedded Configuration: Ship configuration files with the binary
- Static Assets: Embed templates, images, or other assets
- Data Files: Include reference data or lookup tables
- Cross-Platform Deployment: Ensure files are available regardless of platform
- Single-File Distribution: Package entire applications as a single executable
- Files are loaded entirely into memory
- No streaming for large files
- No directory operations (directories are implicit from paths)
- Read-write in memory only (changes not persisted back to packed binary)
- File metadata is minimal (no permissions, timestamps, etc.)
Run VFS tests:
# Run all tests including VFS
make test-oak
# Run specific VFS tests
oak test/main.oak virtual pack// main.oak - Simple web server with embedded files
http := import('http')
vfs := ___packed_vfs()
// Serve files from embedded VFS
http.serve('0.0.0.0:8080', fn(req) {
path := if req.path = '/' {
true -> 'index.html'
_ -> req.path |> slice(1) // remove leading /
}
if content := vfs.readFile('static/' + path) {
? -> { status: 404, body: 'Not Found' }
_ -> { status: 200, body: content }
}
})
Pack with embedded files:
oak pack --entry main.oak --output server --include static:./public
./server # Run standalone server with embedded filesPotential future improvements:
- Compression of embedded files
- Encryption of sensitive embedded data
- Lazy loading of large embedded files
- Directory traversal APIs
- Pattern matching for file selection
- Virtual file mounting/overlays
- Write-back to real file system option