File System (WNFS)

Working with the Webnative File System (WNFS)

The Web Native File System (WNFS) is a file system built on top of the InterPlanetary Linked Data model (IPLD). Each Webnative user has their own WNFS, and apps store user files and data in it.

Each file system has a public tree and a private tree, much like your macOS, Windows, or Linux desktop file system. The public tree is "live" and publicly accessible on the Internet. The private tree is encrypted so that only the owner can see the contents.

All information (links, data, metadata, etc.) in the private tree is encrypted. Decryption keys are stored so that access to a given directory grants access to all of its subdirectories.

WNFS is structured and functions similarly to a Unix-style file system, with one notable exception: it's a Directed Acyclic Graph (DAG), meaning that a given child can have more than one parent (think symlinks but without the "sym").

Permissions

A Webnative app assumes full access to the file system unless given a permissions object in the configuration, then it assumes authorization to parts of the filesystem will be granted when requesting capabilities from another app.

Paths

WNFS uses directory and file paths built from path segments by path functions.

const { Branch } = wn.path

// creates a directory path equivalent to "public/some/directory/"
const publicDirectoryPath = wn.path.directory(Branch.Public, "some", "directory")

// creates a directory path equivalent to "private/some/directory/"
const privateDirectoryPath = wn.path.directory(Branch.Private, "some", "directory")

// creates a file path equivalent to "public/some/file"
const publicFilePath = wn.path.file(Branch.Public, "some", "file")

// creates a file path equivalent to "private/some/file"
const privateFilePath = wn.path.file(Branch.Private, "some", "file")

All WNFS operations expect paths created by path functions. See the path API documentation for more path utility functions.

Path Objects. The path functions create objects like { directory: ["public", "some", "directory"] } or { file: ["public", "some", "file"] }. We recommend you use path functions because they validate paths to make sure they are well-formed.

File System Interface

WNFS exposes a POSIX-style interface:

  • add: add a file

  • cat: retrieve a file

  • exists: check if a file or directory exists

  • ls: list a directory

  • mkdir: create a directory

  • mv: move a file or directory

  • read: alias for cat

  • rm: remove a file or directory

  • write: alias for add

// App info is the namespace used when initializing the Webnative program
const appInfo = { creator: "Nullsoft", name: "Winamp" }

// After retrieving a session, or loading the file system manually
const fs = session.fs // or program.loadFileSystem(username)

// List the user's public files
await fs.ls(wn.path.directory(wn.path.Branch.Public))

// List the user's private files that belong to a specific app
await fs.ls(wn.path.appData(appInfo))

// Create a sub directory and add some content
await fs.write(
  wn.path.appData(appInfo, wn.path.file("Sub Directory", "hello.txt")),
  new TextEncoder().encode("👋")
)

// Announce the changes to the server
await fs.publish()

Publish

The publish function synchronizes your file system with the Fission API and IPFS. WNFS does not publish changes automatically because it is more practical to batch changes in some cases. For example, a large data set is better published once than over multiple calls to publish.

Returns: CID the updated root CID for the file system.

Remember to publish! If you do not call publish after making changes, user data will not be persisted to WNFS.

API Summary

Methods

Methods for interacting with the filesystem all use absolute paths.

Paths created by path functions have a FilePath or DirectoryPath type. Methods with a DistinctivePath param accept either a FilePath or a DirectoryPath.

The FileContentthat WNFS can store includes FileContentRaw, Blob, string, number, and boolean. FileContentRaw is Uint8Array. In addition, the private file system can store Objects.

add

Adds file content at a given path.

Params:

  • path: FilePath required

  • content: FileContentrequired

Returns: CID the updated root CID for the file system

Example:

const content = new TextEncoder().encode("hello world")

// create a file called "file" at "public/path/to/a/"
await fs.add(
  wn.path.file("public", "path", "to", "a", "file"),
  content
)

cat

Retrieves some file content at a given path.

Params:

  • path: FilePath required

Returns: FileContent

Example:

const content = await fs.cat(wn.path.file("public", "some", "file"))

exists

Checks if there is anything located at a given path.

Params:

  • path: DistinctivePath required

Returns: boolean

Example:

const bool = await fs.exists(wn.path.file("private", "some", "file"))

get

Retrieves the node at the given path, either a File or Tree object

Params:

  • path: DistinctivePath required

Returns: Tree | File | null

Example:

const node = await fs.get(wn.path.directory("public", "some", "directory"))

ls

Returns a list of links at a given directory path

Params:

  • path: DirectoryPath required

Returns: { [name: string]: Link } Object with the file name as the key and its Link as the value.

Example:

const { Branch } = wn.path
 
// public directory
const publicPath = wn.path.directory(Branch.Public, "some", "directory")
const publicLinksObject = await fs.ls(publicPath)

// private directory
const privatePath = wn.path.directory(Branch.private, "some", "directory")
const privateLinksObject = await fs.ls(privatePath)

// convert private links object to a list
const links = Object.entries(privateLinksObject)

// working with links
const data = await Promise.all(links.map(([name, _]) => {
  return fs.read(
    wn.path.file(Branch.private, "some", "directory", name)
  )
}))

mkdir

Creates a directory at the given path

Params:

  • path: DirectoryPath required

Returns: CID the updated root CID for the file system

Example:

// create a directory called "directory" at "public/some/"
const updatedCID = await fs.mkdir(wn.path.directory("public", "some", "directory"))

mv

Move a directory or file from one path to another.

Params:

  • from: DistinctivePath required

  • to: DistinctivePath required

Returns: CID the updated root CID for the file system

Example:

const fromPath = wn.path.file(Branch.Public, "doc.md")
const toPath = wn.path.file(Branch.private, "Documents", "notes.md")
const updatedCID = await fs.mv(fromPath, toPath)

rm

Removes a file or directory at a given path.

Params:

  • path: DistinctivePath required

Returns: CID the updated root CID for the file system

Example:

const updatedCID = await fs.rm(wn.path.file(Branch.private, "some", "file"))

write

Alias for add.

Params:

  • path: FilePath required

  • content: FileContent required

Returns: CID the updated root CID for the file system

Example:

const content = new TextEncoder.encode("hello world")
const updatedCID = await fs.write(
  wn.path.file(Branch.private, "some", "file"), 
  content
)

Versioning

Each file and directory has a history property, which you can use to get an earlier version of that item. We use the delta variable as the order index, primarily because the timestamps can be slightly out of sequence due to device inconsistencies.

const articlePath = wn.path.file(Branch.private, "Blog Posts", "article.md")
const file = await fs.get(articlePath)

file.history.list()
// { delta: -1, timestamp: 1606236743 }
// { delta: -2, timestamp: 1606236532 }

// List more than (by default) 5 versions
file.history.list(10)

// Get the previous version
file.history.back()

// Go back two versions
const delta = -2
file.history.back(delta)

// Get a version strictly before a timestamp
// The first version (delta -2) is prior to
// the second version (delta -1) timestamp
file.history.prior(1606236743)

Requesting many versions with file.history.list can be slow. The acceptable delay will depend on your application.

Last updated