Skip to content

Commit

Permalink
Merge pull request #137 from zen-fs/context
Browse files Browse the repository at this point in the history
  • Loading branch information
james-pre authored Nov 25, 2024
2 parents 0194947 + 444ccfd commit 6331231
Show file tree
Hide file tree
Showing 14 changed files with 595 additions and 454 deletions.
47 changes: 47 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type ExtractProperties } from 'utilium';
import { credentials as defaultCredentials, type Credentials } from './credentials.js';
import * as fs from './emulation/index.js';
import type { AbsolutePath } from './emulation/path.js';

type Fn_FS = Omit<ExtractProperties<typeof fs, (...args: any[]) => any>, 'mountObject'>;
type Fn_Promises = ExtractProperties<typeof fs.promises, (...args: any[]) => any>;

/**
* Binds a this value for all of the functions in an object (not recursive)
* @internal
*/
function _bindFunctions<T extends Record<string, unknown>>(fns: T, thisValue: any): T {
return Object.fromEntries(Object.entries(fns).map(([k, v]) => [k, typeof v == 'function' ? v.bind(thisValue) : v])) as T;
}

export interface FSContext {
readonly root: AbsolutePath;
readonly credentials: Credentials;
}

export type V_Context = Partial<FSContext> | void | Record<string, unknown>;

/**
* Allows you to restrict operations to a specific root path and set of credentials.
* @experimental
*/
export interface BoundContext extends Fn_FS, FSContext {
promises: Fn_Promises;
}

/**
* Allows you to restrict operations to a specific root path and set of credentials.
* @experimental
*/
export function bindContext(root: AbsolutePath, credentials: Credentials = defaultCredentials): BoundContext {
const ctx = {
root,
credentials,
} satisfies FSContext;

const fn_fs = _bindFunctions<Fn_FS>(fs, ctx);
const fn_promises = _bindFunctions<Fn_Promises>(fs.promises, ctx);

return { ...ctx, ...fn_fs, promises: fn_promises };
}
378 changes: 205 additions & 173 deletions src/emulation/async.ts

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions src/emulation/dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Callback } from '../utils.js';
import { basename } from './path.js';
import { readdir } from './promises.js';
import { readdirSync } from './sync.js';
import type { V_Context } from '../context.js';

export class Dirent implements _Dirent {
public get name(): string {
Expand Down Expand Up @@ -57,7 +58,10 @@ export class Dir implements _Dir {

protected _entries?: Dirent[];

public constructor(public readonly path: string) {}
public constructor(
public readonly path: string,
protected readonly context: V_Context
) {}

/**
* Asynchronously close the directory's underlying resource handle.
Expand All @@ -83,10 +87,8 @@ export class Dir implements _Dir {

protected async _read(): Promise<Dirent | null> {
this.checkClosed();
this._entries ??= await readdir(this.path, { withFileTypes: true });
if (!this._entries.length) {
return null;
}
this._entries ??= await readdir.call<V_Context, [string, any], Promise<Dirent[]>>(this.context, this.path, { withFileTypes: true });

Check warning on line 90 in src/emulation/dir.ts

View workflow job for this annotation

GitHub Actions / CI

Unexpected any. Specify a different type
if (!this._entries.length) return null;
return this._entries.shift() ?? null;
}

Expand All @@ -112,10 +114,8 @@ export class Dir implements _Dir {
*/
public readSync(): Dirent | null {
this.checkClosed();
this._entries ??= readdirSync(this.path, { withFileTypes: true });
if (!this._entries.length) {
return null;
}
this._entries ??= readdirSync.call<V_Context, [string, any], Dirent[]>(this.context, this.path, { withFileTypes: true });

Check warning on line 117 in src/emulation/dir.ts

View workflow job for this annotation

GitHub Actions / CI

Unexpected any. Specify a different type
if (!this._entries.length) return null;
return this._entries.shift() ?? null;
}

Expand Down
232 changes: 123 additions & 109 deletions src/emulation/promises.ts

Large diffs are not rendered by default.

22 changes: 16 additions & 6 deletions src/emulation/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { Errno, ErrnoError } from '../error.js';
import type { File } from '../file.js';
import type { FileSystem } from '../filesystem.js';
import { normalizePath } from '../utils.js';
import { resolve, type AbsolutePath } from './path.js';
import { join, resolve, type AbsolutePath } from './path.js';
import { size_max } from './constants.js';
import type { V_Context } from '../context.js';
import { paths as pathCache } from './cache.js';

// descriptors
Expand Down Expand Up @@ -66,24 +67,32 @@ export function umount(mountPoint: string): void {
pathCache.clear();
}

export interface ResolvedMount {
fs: FileSystem;
path: string;
mountPoint: string;
root: string;
}

/**
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
*/
export function resolveMount(path: string): { fs: FileSystem; path: string; mountPoint: string } {
path = normalizePath(path);
export function resolveMount(path: string, ctx: V_Context): ResolvedMount {
const root = typeof ctx == 'object' && typeof ctx.root == 'string' ? ctx.root : '/';
path = normalizePath(join(root, path));
const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // descending order of the string length
for (const [mountPoint, fs] of sortedMounts) {
// We know path is normalized, so it would be a substring of the mount point.
if (mountPoint.length <= path.length && path.startsWith(mountPoint)) {
path = path.slice(mountPoint.length > 1 ? mountPoint.length : 0); // Resolve the path relative to the mount point
if (path === '') {
path = '/';
path = root;
}
return { fs, path, mountPoint };
return { fs, path, mountPoint, root };
}
}

throw new ErrnoError(Errno.EIO, 'ZenFS not initialized with a file system');
throw new ErrnoError(Errno.EIO, 'No file system');
}

/**
Expand Down Expand Up @@ -120,6 +129,7 @@ export function fixError<E extends ErrnoError>(e: E, paths: Record<string, strin
} catch {
// `message` is read only
}
if (e.path) e.path = fixPaths(e.path, paths);
return e;
}

Expand Down
Loading

0 comments on commit 6331231

Please sign in to comment.