One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

File System

Read, write, and organize files in the app's own folders on iOS, Android, and web

One.FileSystem works with files in the app’s private folders: documents, caches, application support, and a temporary folder. On iOS it is FileManager; on Android it is java.io.File under the app’s data folder; on web it is the browser’s Origin Private File System.

It has two layers. Flat promise functions such as writeFile and readDirectory cover everyday work and run on every platform. The File, Directory, and Paths classes add typed reads, synchronous calls, hashes, random access handles, and streams on iOS and Android.

Usage

import { One } from 'one'
const { File, Directory, Paths } = One.FileSystem
async function saveDraft(id: string, body: string) {
const drafts = new Directory(Paths.document, 'drafts')
if (!drafts.exists) drafts.create()
await new File(drafts, `${id}.txt`).write(body)
}
async function readDraft(id: string) {
const file = new File(Paths.document, 'drafts', `${id}.txt`)
return file.exists ? file.text() : null
}

A File or Directory is only a location until you act on it, so building one never touches the disk.

How it works

Locations. Every path is an absolute file:// URI. getDirectories() returns the four app folders, each ending in /. On Android they are a Documents folder inside the app’s files folder, the cache folder, the files folder, and a tmp folder inside the cache. Join names onto them with the class constructors or new URL(name, folder).

Threads. Async calls run on one serial background queue per app: a dispatch queue on iOS and a single-thread executor on Android. Because the queue is serial, calls you await in order see each other’s results. Sync calls, such as textSync, writeSync, list, and create, run on the JavaScript thread and block it until the disk answers.

Protected roots. The app’s home, Documents, Library, Caches, Application Support, and temporary folders cannot be moved or deleted; trying rejects with E_FILE_PERMISSION. Their contents can.

Reading bytes. Besides text(), bytes(), and base64(), One’s fetch on iOS and Android reads file:// URIs, including files that the document and image pickers return.

Web. Files live in the Origin Private File System, private to the site and invisible to people. URIs look like opfs://one/documents/. Every operation runs in order, and a write resolves only once its stream closes. Only the async members work there; the sync ones throw.

API reference

Flat functions

FunctionResolves
getDirectories()The four folders, at once
getInfo(uri)What is at the URI
readDirectory(uri)The entries in a folder
makeDirectory(uri, intermediates?)Once created
writeFile(uri, contents, encoding?)Once written
copy(from, to), move(from, to)Once done
delete(uri)Once removed, recursively
FileInfoTypeDescription
uristringThe URI asked about.
stringThe URI asked about.
existsbooleanWhether anything is there.
booleanWhether anything is there.
isDirectorybooleanWhether it is a folder.
booleanWhether it is a folder.
sizenumberSize in bytes.
numberSize in bytes.
modifiedAtnumberLast change, in Unix milliseconds.
numberLast change, in Unix milliseconds.
FileEntryTypeDescription
namestringThe file or folder name.
stringThe file or folder name.
uristringIts full URI.
stringIts full URI.
isDirectorybooleanWhether it is a folder.
booleanWhether it is a folder.

File

MemberDoes
uri, name, extensionRead the location
exists, size, typeRead live metadata
creationTime, lastModifiedTimes, or null
text, bytes, base64Read, with Sync twins
arrayBuffer, jsonRead as a buffer or JSON
write(content, options?)Write, with a Sync twin
digest(algorithm)A lowercase hex hash
create, delete, renameChange it, synchronously
copy, moveRelocate, with Sync twins
open(mode?)A FileHandle
readableStream, writableStreamStreams of bytes

Directory

MemberDoes
uri, name, exists, sizeRead the location and size
list()Sorted File and Directory children
create(options?)Create it
createFile, createDirectoryCreate and return a child
delete, renameChange it
copy, moveRelocate, with Sync twins

Paths has document, cache, and bundle folders, totalDiskSpace and availableDiskSpace in bytes, info(...uris), and string helpers that take files, folders, or strings: join, dirname, basename, extname, normalize, parse, relative, and isAbsolute.

FileWriteOptionsTypeDescription
encodingFileEncoding = "utf8"utf8 or base64, for string content.
FileEncoding = "utf8"utf8 or base64, for string content.
appendboolean = falseAdd to the end instead of replacing.
boolean = falseAdd to the end instead of replacing.
DirectoryCreateOptionsTypeDescription
intermediatesbooleanCreate missing parent folders.
booleanCreate missing parent folders.
overwritebooleanReplace what is already there.
booleanReplace what is already there.
idempotentbooleanSucceed if the folder exists.
booleanSucceed if the folder exists.

FileMode is ReadWrite (the default), ReadOnly, WriteOnly, Append, or Truncate; write-only modes create a missing file. digest takes 'MD5', 'SHA-1', 'SHA-256', 'SHA-384', or 'SHA-512'.

Failures reject with an error code: E_FILE_URI, E_FILE_ENCODING, E_FILE_NOT_FOUND, E_FILE_EXISTS, E_FILE_PERMISSION, or E_FILE_FAILED.

Platform differences

iOSAndroidWeb
StorageApp sandboxApp data folderPrivate browser storage
Flat functionsYesYesYes
Sync membersYesYesThrow
App bundleReadableRejects
MD5 digestYesYesRejects
ErrorsError codesError codesBrowser error names

Limits and gotchas

Examples

Cache a download

import { One } from 'one'
const { File, Paths } = One.FileSystem
async function cachedImage(url: string, name: string) {
const file = new File(Paths.cache, name)
if (file.exists) return file.uri
const response = await fetch(url)
await file.write(new Uint8Array(await response.arrayBuffer()))
return file.uri
}

The returned URI works as an image source. The cache folder suits it: the system can reclaim the space, and the next call downloads it again.

Copy a picked file into the app

import { One } from 'one'
const { File, Directory, Paths } = One.FileSystem
async function importFile(pickedUri: string) {
const inbox = new Directory(Paths.document, 'inbox')
inbox.create({ intermediates: true, idempotent: true })
const source = new File(pickedUri)
await source.copy(inbox, { overwrite: true })
return new File(inbox, source.name)
}

Copying into a directory keeps the source’s name. idempotent lets the folder already exist, so the function is safe to call again.

Read a large file in chunks

import { One } from 'one'
async function countBytes(uri: string) {
const reader = new One.FileSystem.File(uri).readableStream().getReader()
let total = 0
for (;;) {
const { done, value } = await reader.read()
if (done) return total
total += value.length
}
}

The stream reads 1024 bytes at a time and closes its handle when it ends, so a large file never sits in memory whole. For a hash of the whole file, digest('SHA-256') does the reading for you.

Write files on every platform

import { One } from 'one'
async function saveExport(json: string) {
const { documents } = One.FileSystem.getDirectories()
const folder = new URL('exports/', documents).href
await One.FileSystem.makeDirectory(folder)
await One.FileSystem.writeFile(new URL('latest.json', folder).href, json)
}

The flat functions are the part that also runs on web, where documents is an opfs:// folder, so shared code that must work in the browser uses them.

Credit

The path and stream helpers are adapted from Expo FileSystem and Node.js.

Edit this page on GitHub.