Skip to content

Commit

Permalink
Improved configure handling no mounts
Browse files Browse the repository at this point in the history
`configure` now gives a Typescript error when a mount's config is incorrect
Added `BackendConfiguration.disableAsyncCache`
Updated utilium
  • Loading branch information
james-pre committed May 19, 2024
1 parent af0f85d commit 824b9cd
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 32 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"buffer": "^6.0.3",
"minimatch": "^9.0.3",
"readable-stream": "^4.5.2",
"utilium": "^0.2.1"
"utilium": "^0.4.0"
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
Expand Down
7 changes: 4 additions & 3 deletions src/backends/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export interface Backend<FS extends FileSystem = FileSystem, TOptions extends ob

type OptionsOf<T extends Backend> = T extends Backend<FileSystem, infer TOptions> ? TOptions : never;

type FilesystemOf<T extends Backend> = T extends Backend<infer FS> ? FS : never;
export type FilesystemOf<T extends Backend> = T extends Backend<infer FS> ? FS : never;

/**
* @internal
Expand Down Expand Up @@ -138,13 +138,14 @@ export async function checkOptions<T extends Backend>(backend: T, opts: Record<s
*
* The option object for each file system corresponds to that file system's option object passed to its `Create()` method.
*/
export type BackendConfiguration<T extends Backend = Backend> = OptionsOf<T> & {
export type BackendConfiguration<T extends Backend> = OptionsOf<T> & {
backend: T;
disableAsyncCache?: boolean;
};

/**
* @internal
*/
export function isBackendConfig(arg: unknown): arg is BackendConfiguration {
export function isBackendConfig<T extends Backend>(arg: unknown): arg is BackendConfiguration<T> {
return arg != null && typeof arg == 'object' && 'backend' in arg && isBackend(arg.backend);
}
81 changes: 61 additions & 20 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { ErrnoError, Errno } from './error.js';
import type { Backend, BackendConfiguration } from './backends/backend.js';
import { type Entries } from 'utilium';
import type { Backend, BackendConfiguration, FilesystemOf } from './backends/backend.js';
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
import * as fs from './emulation/index.js';
import type { AbsolutePath } from './emulation/path.js';
import { setCred, type MountObject } from './emulation/shared.js';
import { FileSystem } from './filesystem.js';
import type { AbsolutePath } from './emulation/path.js';
import { Errno, ErrnoError } from './error.js';
import { FileSystem, type Async } from './filesystem.js';

/**
* Configuration for a specific mount point
*/
export type MountConfiguration<FS extends FileSystem = FileSystem, TOptions extends object = object> = FS | BackendConfiguration<Backend<FS, TOptions>> | Backend<FS, TOptions>;
export type MountConfiguration<T extends Backend> = FilesystemOf<T> | BackendConfiguration<T> | T;

function isMountConfig(arg: unknown): arg is MountConfiguration {
function isMountConfig<T extends Backend>(arg: unknown): arg is MountConfiguration<T> {
return isBackendConfig(arg) || isBackend(arg) || arg instanceof FileSystem;
}

/**
* Retrieve a file system with the given configuration.
* @param config A BackendConfig object.
*/
export async function resolveMountConfig<FS extends FileSystem, TOptions extends object = object>(config: MountConfiguration<FS, TOptions>, _depth = 0): Promise<FS> {
export async function resolveMountConfig<T extends Backend>(config: MountConfiguration<T>, _depth = 0): Promise<FilesystemOf<T>> {
if (typeof config !== 'object' || config == null) {
throw new ErrnoError(Errno.EINVAL, 'Invalid options on mount configuration');
}
Expand All @@ -33,7 +34,7 @@ export async function resolveMountConfig<FS extends FileSystem, TOptions extends
}

if (isBackend(config)) {
config = { backend: config } as BackendConfiguration<Backend<FS, TOptions>>;
config = { backend: config } as BackendConfiguration<T>;
}

for (const [key, value] of Object.entries(config)) {
Expand All @@ -58,40 +59,80 @@ export async function resolveMountConfig<FS extends FileSystem, TOptions extends
throw new ErrnoError(Errno.EPERM, 'Backend not available: ' + backend);
}
checkOptions(backend, config);
const mount = await backend.create(config);
const mount = (await backend.create(config)) as FilesystemOf<T>;
if ('_disableSync' in mount) {
type AsyncFS = InstanceType<ReturnType<typeof Async<new () => FilesystemOf<T>>>>;
(mount as AsyncFS)._disableSync = config.disableAsyncCache || false;
}
await mount.ready();
return mount;
}

type ConfigMounts = { [K in AbsolutePath]: Backend };

/**
* Configuration
*/
export interface Configuration {
mounts: Record<AbsolutePath, MountConfiguration>;
uid?: number;
gid?: number;
export interface Configuration<T extends ConfigMounts> {
/**
* An object mapping mount points to mount configuration
*/
mounts: { [K in keyof T & AbsolutePath]: MountConfiguration<T[K]> };
/**
* The uid to use
*/
uid: number;
/**
* The gid to use
*/
gid: number;
/**
* If set, disables the sync cache and sync operations on async file systems.
*/
disableAsyncCache: boolean;
}

/**
* Creates filesystems with the given configuration, and initializes ZenFS with it.
* @see Configuration for more info on the configuration object.
* Configures ZenFS with single mount point /
*/
export async function configure<T extends Backend>(config: MountConfiguration<T>): Promise<void>;

/**
* Configures ZenFS with the given configuration
* @see Configuration
*/
export async function configure<T extends ConfigMounts>(config: Partial<Configuration<T>>): Promise<void>;

/**
* Configures ZenFS with the given configuration
* @see Configuration
*/
export async function configure<T extends MountConfiguration | Configuration>(config: T | Configuration): Promise<void> {
export async function configure(config: MountConfiguration<Backend> | Partial<Configuration<ConfigMounts>>): Promise<void> {
const uid = 'uid' in config ? config.uid || 0 : 0;
const gid = 'gid' in config ? config.gid || 0 : 0;

if (isMountConfig(config)) {
// single FS
config = { mounts: { '/': config } };
config = { mounts: { '/': config } } as Partial<Configuration<ConfigMounts>>;
}

for (const [point, value] of Object.entries(config.mounts) as [AbsolutePath, MountConfiguration][]) {
setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });

if (!config.mounts) {
return;
}

for (const [point, mountConfig] of Object.entries(config.mounts) as Entries<typeof config.mounts>) {
if (!point.startsWith('/')) {
throw new ErrnoError(Errno.EINVAL, 'Mount points must have absolute paths');
}
config.mounts[point] = await resolveMountConfig(value);

if (isBackendConfig(mountConfig)) {
mountConfig.disableAsyncCache = config.disableAsyncCache || false;
}

config.mounts[point] = await resolveMountConfig(mountConfig);
}

fs.mountObject(config.mounts as MountObject);
setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
}
2 changes: 1 addition & 1 deletion src/emulation/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export function normalize(path: string): string {
return isAbsolute ? `/${path}` : path;
}

export function isAbsolute(path: string): boolean {
export function isAbsolute(path: string): path is AbsolutePath {
return path.startsWith('/');
}

Expand Down

0 comments on commit 824b9cd

Please sign in to comment.