Skip to content

Commit

Permalink
Fixed resolveBackendConfig not checking options
Browse files Browse the repository at this point in the history
Removed old _InMemory
Removed getMount and getMounts
Began updating readme
  • Loading branch information
james-pre committed Mar 7, 2024
1 parent 9642da3 commit 6c65f3d
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 70 deletions.
57 changes: 26 additions & 31 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,10 @@ console.log(contents);
A `InMemory` backend is created by default. If you would like to use a different one, you must configure BrowserFS. It is recommended to do so using the `configure` function. Here is an example using the `Storage` backend from `@browserfs/fs-dom`:

```js
import { configure, fs, registerBackend } from '@browserfs/core';
import { StorageFileSystem } from '@browserfs/fs-dom';
registerBackend(StorageFileSystem);
import { configure, fs } from '@browserfs/core';
import { StorageStore } from '@browserfs/fs-dom';

// you can also add a callback as the last parameter instead of using promises
await configure({ fs: 'Storage' });
await configure({ backend: StorageStore });

if (!fs.existsSync('/test.txt')) {
fs.writeFileSync('/test.txt', 'This will persist across reloads!');
Expand All @@ -69,23 +67,19 @@ console.log(contents);
You can use multiple backends by passing an object to `configure` which maps paths to file systems. The following example mounts a zip file to `/zip`, in-memory storage to `/tmp`, and IndexedDB storage to `/home` (note that `/` has the default in-memory backend):

```js
import { configure, registerBackend } from '@browserfs/core';
import { IndexedDBFileSystem } from '@browserfs/fs-dom';
import { ZipFS } from '@browserfs/fs-zip';
import Buffer from 'buffer';
registerBackend(IndexedDBFileSystem, ZipFS);
import { configure } from '@browserfs/core';
import { IndexedDD } from '@browserfs/fs-dom';
import { Zip } from '@browserfs/fs-zip';

const zipData = await (await fetch('mydata.zip')).arrayBuffer();

await configure({
'/mnt/zip': {
fs: 'ZipFS',
options: {
zipData: Buffer.from(zipData)
}
backend: Zip,
zipData: zipData,
},
'/tmp': 'InMemory',
'/home': 'IndexedDB',
'/home': IndexedDB,
};
```
Expand All @@ -94,58 +88,59 @@ await configure({
The FS promises API is exposed as `promises`.
```js
import { configure, promises, registerBackend } from '@browserfs/core';
import { IndexedDBFileSystem } from '@browserfs/fs-dom';
registerBackend(IndexedDBFileSystem);
import { configure, promises } from '@browserfs/core';
import { IndexedDB } from '@browserfs/fs-dom';

await configure({ '/': 'IndexedDB' });
await configure({ '/': IndexedDB });

const exists = await promises.exists('/myfile.txt');
if (!exists) {
await promises.write('/myfile.txt', 'Lots of persistant data');
}
```
BrowserFS does _not_ provide a seperate method for importing promises in its built form. If you are using Typescript, you can import the promises API from source code (perhaps to reduce you bundle size). Doing so it not recommended as the files may be moved without notice.
BrowserFS does _not_ provide a seperate public import for importing promises in its built form. If you are using ESM, you can import promises functions from `dist/emulation/promises`, though this may change at any time and is not recommended.
#### Using asynchronous backends synchronously
You may have noticed that attempting to use a synchronous method on an asynchronous backend (e.g. IndexedDB) results in a "not supplied" error (`ENOTSUP`). If you wish to use an asynchronous backend synchronously you need to wrap it in an `AsyncMirror`:
You may have noticed that attempting to use a synchronous function on an asynchronous backend (e.g. IndexedDB) results in a "not supplied" error (`ENOTSUP`). If you wish to use an asynchronous backend synchronously you need to wrap it in an `AsyncMirror`:
```js
import { configure, fs } from '@browserfs/core';
import { IndexedDBFileSystem } from '@browserfs/fs-dom';
registerBackend(IndexedDBFileSystem);
import { IndexedDB } from '@browserfs/fs-dom';

await configure({
'/': { fs: 'AsyncMirror', options: { sync: { fs: 'InMemory' }, async: { fs: 'IndexedDB' } } }
'/': {
backend: 'AsyncMirror',
sync: 'InMemory',
async: IndexedDB
}
});

fs.writeFileSync('/persistant.txt', 'My persistant data'); // This fails if you configure the FS as IndexedDB
fs.writeFileSync('/persistant.txt', 'My persistant data'); // This fails if you configure with only IndexedDB
```
### Advanced usage
#### Creating backends
If you would like to create backends without configure, you may do so by importing the backend's class and calling its `Create` method. You can import the backend directly or with `backends`:
If you would like to create backends without configure, you may do so by importing the backend and calling `createBackend` with it. You can import the backend directly or with `backends`:
```js
import { configure, backends, InMemory } from '@browserfs/core';

console.log(backends.InMemory === InMemory) // they are the same

const inMemoryFS = await InMemory.Create();
const internalInMemoryFS = createBackend(InMemory);
```
> ⚠ Instances of backends follow the ***internal*** BrowserFS API. You should never use a backend's method unless you are extending a backend.
> ⚠ Instances of backends follow the ***internal*** BrowserFS API. You should never use a backend's methods unless you are extending a backend.
Coming soon:
```js
import { configure, InMemory } from '@browserfs/core';

const inMemoryFS = new InMemory();
await inMemoryFS.whenReady();
const internalInMemoryFS = new InMemory();
await internalInMemoryFS.ready();
```
#### Mounting
Expand Down
22 changes: 4 additions & 18 deletions src/backends/InMemory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export class InMemoryStore implements SyncStore, SimpleSyncStore {
}
}

/**
* A simple in-memory file system backed by an InMemoryStore.
* Files are not persisted across page loads.
*/
export const InMemory: Backend = {
name: 'InMemory',
isAvailable(): boolean {
Expand All @@ -49,21 +53,3 @@ export const InMemory: Backend = {
return new SyncStoreFileSystem({ store: new InMemoryStore(name) });
},
};

/**
* A simple in-memory file system backed by an InMemoryStore.
* Files are not persisted across page loads.
*/
export class _InMemory extends SyncStoreFileSystem {
public static isAvailable(): boolean {
return true;
}

public static create = createBackend.bind(this);

public static readonly options = {};

public constructor() {
super({ store: new InMemoryStore() });
}
}
5 changes: 4 additions & 1 deletion src/backends/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,8 @@ export async function resolveBackendConfig(options: BackendConfig): Promise<File
if (!backend) {
throw new ApiError(ErrorCode.EPERM, `Backend "${backend}" is not available`);
}
return backend.create(options);
checkOptions(backend, options);
const fs = backend.create(options);
await fs.ready();
return fs;
}
2 changes: 1 addition & 1 deletion src/emulation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ export * as promises from './promises.js';
export * as constants from './constants.js';
export * from './streams.js';
export * from './dir.js';
export { initialize, getMount, getMounts, mount, umount, _toUnixTimestamp } from './shared.js';
export { initialize, mounts, mount, umount, _toUnixTimestamp } from './shared.js';
export { Stats, BigIntStats } from '../stats.js';
46 changes: 29 additions & 17 deletions src/emulation/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type { File } from '../file.js';
/**
* converts Date or number to a integer UNIX timestamp
* Grabbed from NodeJS sources (lib/fs.js)
*
* @internal
*/
export function _toUnixTimestamp(time: Date | number): number {
if (typeof time === 'number') {
Expand All @@ -21,6 +23,10 @@ export function _toUnixTimestamp(time: Date | number): number {
throw new Error('Cannot parse time: ' + time);
}

/**
* Normalizes a mode
* @internal
*/
export function normalizeMode(mode: string | number | unknown, def?: number): number {
switch (typeof mode) {
case 'number':
Expand All @@ -41,6 +47,10 @@ export function normalizeMode(mode: string | number | unknown, def?: number): nu
throw new ApiError(ErrorCode.EINVAL, 'Invalid mode: ' + mode?.toString());
}

/**
* Normalizes a time
* @internal
*/
export function normalizeTime(time: string | number | Date): Date {
if (time instanceof Date) {
return time;
Expand All @@ -57,6 +67,10 @@ export function normalizeTime(time: string | number | Date): Date {
throw new ApiError(ErrorCode.EINVAL, 'Invalid time.');
}

/**
* Normalizes a path
* @internal
*/
export function normalizePath(p: string): string {
// Node doesn't allow null characters in paths.
if (p.indexOf('\u0000') >= 0) {
Expand All @@ -69,7 +83,11 @@ export function normalizePath(p: string): string {
return resolve(p);
}

export function normalizeOptions(options: any, defEnc: string | null, defFlag: string, defMode: number | null): { encoding: BufferEncoding; flag: string; mode: number } {
/**
* Normalizes options
* @internal
*/
export function normalizeOptions(options: unknown, defEnc: string | null, defFlag: string, defMode: number | null): { encoding: BufferEncoding; flag: string; mode: number } {
// typeof null === 'object' so special-case handing is needed.
switch (options === null ? 'null' : typeof options) {
case 'object':
Expand All @@ -80,15 +98,15 @@ export function normalizeOptions(options: any, defEnc: string | null, defFlag: s
};
case 'string':
return {
encoding: options,
encoding: <BufferEncoding>options,
flag: defFlag,
mode: defMode!,
};
case 'null':
case 'undefined':
case 'function':
return {
encoding: defEnc! as BufferEncoding,
encoding: <BufferEncoding>defEnc!,
flag: defFlag,
mode: defMode!,
};
Expand All @@ -97,6 +115,10 @@ export function normalizeOptions(options: any, defEnc: string | null, defFlag: s
}
}

/**
* Do nothing
* @internal
*/
export function nop() {
// do nothing
}
Expand Down Expand Up @@ -127,27 +149,17 @@ export interface MountMapping {
[point: string]: FileSystem;
}

/**
* The map of mount points
* @internal
*/
export const mounts: Map<string, FileSystem> = new Map();

/*
Set a default root.
*/
mount('/', InMemory.create({ name: 'root' }));

/**
* Gets the file system mounted at `mountPoint`
*/
export function getMount(mountPoint: string): FileSystem {
return mounts.get(mountPoint);
}

/**
* Gets an object of mount points (keys) and filesystems (values)
*/
export function getMounts(): MountMapping {
return Object.fromEntries(mounts.entries());
}

/**
* Mounts the file system at the given mount point.
*/
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { type MountMapping, setCred } from './emulation/shared.js';
*/
export function initialize(mounts: { [point: string]: FileSystem }, uid: number = 0, gid: number = 0) {
setCred(new Cred(uid, gid, uid, gid, uid, gid));
return fs.initialize(mounts);
fs.initialize(mounts);
}

/**
Expand Down Expand Up @@ -57,7 +57,7 @@ export async function configure(config: Configuration): Promise<void> {

config[point] = await resolveBackendConfig(value);
}
return initialize(config as MountMapping);
initialize(<MountMapping>config);
}

export * from './backends/index.js';
Expand Down

0 comments on commit 6c65f3d

Please sign in to comment.