Skip to content

@fkn/lib/fs

The signed-in account is not the one this call belongs to, so the call was refused and nothing was read, written or removed. Test err.code === STORAGE_ACCOUNT_CHANGED, or isAccountChanged(err).

Every storage call (list, read, write, remove) carries a pin of the account this page was on when the call was made, captured synchronously in the call itself. So a call made before this page learned of an account switch never lands in the new account: FKN refuses it, or the library refuses it before sending. The library never retries it under the new account by itself.

What stays the app’s to check is work it started before the notification and resumes after it (an await between reading state and writing it back). Keep a counter that an account.onChange handler bumps and compare it synchronously right before the storage call: the pin is captured in that same synchronous step, so the check and the call are atomic.

The notification also fires when the connection is renewed without a switch, so this can be thrown for a call that would have landed in the same account. Either way the answer is the same: re-read what the app is acting on, then call again.

  • Error
new StorageAccountChangedError(message?): StorageAccountChangedError;

string = 'storage: the signed-in account changed, so this call was not made'

StorageAccountChangedError

Error.constructor
code: string = STORAGE_ACCOUNT_CHANGED;

The FKN broker this page reached is older than the account pin, so no storage call was sent: an unpinned call could land in an account the app did not mean. It goes away once fkn.app updates.

  • Error
new StorageAccountPinUnsupportedError(message?): StorageAccountPinUnsupportedError;

string = 'storage: the FKN broker cannot pin calls to an account yet, so nothing was sent'

StorageAccountPinUnsupportedError

Error.constructor
code: string = STORAGE_ACCOUNT_PIN_UNSUPPORTED;
type AdoptSummary = object;
deleted: string[];
failed: object[];
error: string;
path: string;
kept: string[];
resolved: object[];
choice: "local" | "cloud" | "merged";
path: string;
unresolved: string[];
uploaded: string[];

type ConflictResolution = WriteData | null;

What should be at that path. Return one side’s bytes, or anything else as a merge; null leaves the file alone and the summary reports it unresolved.

Whatever comes back is what wins. Returning the very Blob that readLocal() or readCloud() handed you is recognised by identity, so picking a side costs one write rather than two. An answer that crossed a realm (osra) arrives as a different object, so it reads as a merge: one redundant write, never a wrong file.


type ConflictResolver = (conflict) =>
| Promise<ConflictResolution>
| ConflictResolution;

FileConflict

| Promise<ConflictResolution> | ConflictResolution


type ConflictSide = object;
size: number;
updatedAt: string | null;

type FileConflict = object;
cloud: ConflictSide;
local: ConflictSide;
path: string;
readCloud: () => Promise<Blob>;

Promise<Blob>

readLocal: () => Promise<Blob>;

Promise<Blob>


type SealedRead = object;

The bytes of a cloud object and the seal time they carry, as an ISO string or null.

data: Uint8Array;
sealedAt: string | null;

type Stats = object;
atimeMs: number;
ctimeMs: number;
isDirectory: () => boolean;

boolean

isFile: () => boolean;

boolean

isSymbolicLink: () => boolean;

boolean

mode: number;
mtime: Date;
mtimeMs: number;
size: number;
const adopt: () => Promise<AdoptSummary> = hybrid.adopt;

Pushes the local files the account lacks, after what replication owes. It acts for the account this page is on when it is called: an entry queued for another account is skipped and stays queued for it, and a switch while it runs refuses the rest with StorageAccountChangedError.

Promise<AdoptSummary>


const adoptable: () => Promise<{
bytes: number;
paths: string[];
}> = hybrid.adoptable;

Promise<{ bytes: number; paths: string[]; }>


appendFile: (path, data, a, b?) => void;

PathLike

WriteData

WriteOptions | Callback<void>

Callback<void>

void


appendFileSync: (path, data, options?) => void;

PathLike

WriteData

WriteOptions

void


available: () => Promise<boolean>;

Promise<boolean>


default: object;
adopt: () => Promise<AdoptSummary>;

Pushes the local files the account lacks, after what replication owes. It acts for the account this page is on when it is called: an entry queued for another account is skipped and stays queued for it, and a switch while it runs refuses the rest with StorageAccountChangedError.

Promise<AdoptSummary>

adoptable: () => Promise<{
bytes: number;
paths: string[];
}>;

Promise<{ bytes: number; paths: string[]; }>

appendFile: (path, data, a, b?) => void;

PathLike

WriteData

WriteOptions | Callback<void>

Callback<void>

void

appendFileSync: (path, data, options?) => void;

PathLike

WriteData

WriteOptions

void

available: () => Promise<boolean> = backing.available;

Promise<boolean>

exists: (path, callback) => void;

PathLike

(exists) => void

void

existsSync: (path) => boolean;

PathLike

boolean

flush: () => Promise<void> = store.flush;

Promise<void>

lstatSync: (path) => Stats = statSync;

PathLike

Stats

mkdir: (path, a, b?) => void;

PathLike

MakeOptions | Callback<void>

Callback<void>

void

mkdirSync: (path, options?) => void;

PathLike

MakeOptions

void

mount: () => Promise<void> = store.mount;

Promise<void>

onConflict: (next) => () => void;

ConflictResolver

() => void

pending: () => string[];

string[]

pendingDeletes: () => string[];

string[]

promises: object;
access: (path) => Promise<void>;

PathLike

Promise<void>

appendFile: (path, data, options?) => Promise<void>;

PathLike

WriteData

WriteOptions

Promise<void>

lstat: (path) => Promise<Stats>;

PathLike

Promise<Stats>

mkdir: (path, options?) => Promise<void>;

PathLike

MakeOptions

Promise<void>

readdir: (path) => Promise<string[]>;

PathLike

Promise<string[]>

readFile: (path, options?) => Promise<string | Buffer<ArrayBufferLike>>;

PathLike

ReadOptions

Promise<string | Buffer<ArrayBufferLike>>

rename: (from, to) => Promise<void>;

PathLike

PathLike

Promise<void>

rm: (path, options?) => Promise<void>;

PathLike

boolean

boolean

Promise<void>

rmdir: (path, options?) => Promise<void>;

PathLike

boolean

Promise<void>

stat: (path) => Promise<Stats>;

PathLike

Promise<Stats>

unlink: (path) => Promise<void>;

PathLike

Promise<void>

writeFile: (path, data, options?) => Promise<void>;

PathLike

WriteData

WriteOptions

Promise<void>

pull: (path) => Promise<Blob>;

string

Promise<Blob>

readdir: (path, a, b?) => void;

PathLike

unknown

Callback<string[]>

void

readdirSync: (path) => string[];

PathLike

string[]

readFile: (path, a, b?) => void;

PathLike

ReadOptions | Callback<string | Buffer<ArrayBufferLike>>

Callback<string | Buffer<ArrayBufferLike>>

void

readFileSealed: (path, opts?) => Promise<SealedRead>;

string

PinOptions

Promise<SealedRead>

readFileSync: (path, options?) => string | Buffer<ArrayBufferLike>;

PathLike

ReadOptions

string | Buffer<ArrayBufferLike>

remount: () => Promise<void> = store.remount;

Promise<void>

rename: (from, to, callback) => void;

PathLike

PathLike

Callback<void>

void

renameSync: (from, to) => void;

PathLike

PathLike

void

replicating: () => boolean;

boolean

rm: (path, a, b?) => void;

PathLike

| Callback<void> | { force?: boolean; recursive?: boolean; }

Callback<void>

void

rmdirSync: (path, options?) => void;

PathLike

boolean

void

rmSync: (path, options?) => void;

PathLike

boolean

boolean

void

stat: (path, callback) => void;

PathLike

Callback<Stats>

void

statSync: (path) => Stats;

PathLike

Stats

unlink: (path, callback) => void;

PathLike

Callback<void>

void

unlinkSync: (path) => void;

PathLike

void

writeFile: (path, data, a, b?) => void;

PathLike

WriteData

WriteOptions | Callback<void>

Callback<void>

void

writeFileSync: (path, data, options?) => void;

PathLike

WriteData

WriteOptions

void


exists: (path, callback) => void;

PathLike

(exists) => void

void


existsSync: (path) => boolean;

PathLike

boolean


flush: () => Promise<void>;

Promise<void>


lstatSync: (path) => Stats;

PathLike

Stats


mkdir: (path, a, b?) => void;

PathLike

MakeOptions | Callback<void>

Callback<void>

void


mkdirSync: (path, options?) => void;

PathLike

MakeOptions

void


mount: () => Promise<void>;

Promise<void>


const onConflict: (next) => () => void = hybrid.onConflict;

ConflictResolver

() => void


const pending: () => string[] = hybrid.pending;

string[]


const pendingDeletes: () => string[] = hybrid.pendingDeletes;

string[]


promises: object;
access: (path) => Promise<void>;

PathLike

Promise<void>

appendFile: (path, data, options?) => Promise<void>;

PathLike

WriteData

WriteOptions

Promise<void>

lstat: (path) => Promise<Stats>;

PathLike

Promise<Stats>

mkdir: (path, options?) => Promise<void>;

PathLike

MakeOptions

Promise<void>

readdir: (path) => Promise<string[]>;

PathLike

Promise<string[]>

readFile: (path, options?) => Promise<string | Buffer<ArrayBufferLike>>;

PathLike

ReadOptions

Promise<string | Buffer<ArrayBufferLike>>

rename: (from, to) => Promise<void>;

PathLike

PathLike

Promise<void>

rm: (path, options?) => Promise<void>;

PathLike

boolean

boolean

Promise<void>

rmdir: (path, options?) => Promise<void>;

PathLike

boolean

Promise<void>

stat: (path) => Promise<Stats>;

PathLike

Promise<Stats>

unlink: (path) => Promise<void>;

PathLike

Promise<void>

writeFile: (path, data, options?) => Promise<void>;

PathLike

WriteData

WriteOptions

Promise<void>


const pull: (path) => Promise<Blob> = hybrid.pull;

string

Promise<Blob>


readdir: (path, a, b?) => void;

PathLike

unknown

Callback<string[]>

void


readdirSync: (path) => string[];

PathLike

string[]


readFile: (path, a, b?) => void;

PathLike

ReadOptions | Callback<string | Buffer<ArrayBufferLike>>

Callback<string | Buffer<ArrayBufferLike>>

void


const readFileSealed: (path, opts?) => Promise<SealedRead> = hybrid.readFileSealed;

string

PinOptions

Promise<SealedRead>


readFileSync: (path, options?) => string | Buffer<ArrayBufferLike>;

PathLike

ReadOptions

string | Buffer<ArrayBufferLike>


remount: () => Promise<void>;

Promise<void>


rename: (from, to, callback) => void;

PathLike

PathLike

Callback<void>

void


renameSync: (from, to) => void;

PathLike

PathLike

void


const replicating: () => boolean = hybrid.replicating;

boolean


rm: (path, a, b?) => void;

PathLike

| Callback<void> | { force?: boolean; recursive?: boolean; }

Callback<void>

void


rmdirSync: (path, options?) => void;

PathLike

boolean

void


rmSync: (path, options?) => void;

PathLike

boolean

boolean

void


stat: (path, callback) => void;

PathLike

Callback<Stats>

void


statSync: (path) => Stats;

PathLike

Stats


unlink: (path, callback) => void;

PathLike

Callback<void>

void


unlinkSync: (path) => void;

PathLike

void


writeFile: (path, data, a, b?) => void;

PathLike

WriteData

WriteOptions | Callback<void>

Callback<void>

void


writeFileSync: (path, data, options?) => void;

PathLike

WriteData

WriteOptions

void

function isAccountChanged(error): boolean;

Whether an error from this module means the account changed under the call, which was not made.

unknown

boolean

Re-exports STORAGE_ACCOUNT_CHANGED


Re-exports STORAGE_ACCOUNT_PIN_UNSUPPORTED