@zenfs/core 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backends/Index.d.ts +3 -0
- package/dist/browser.min.js +3 -3
- package/dist/browser.min.js.map +3 -3
- package/dist/filesystem.d.ts +1 -1
- package/dist/filesystem.js +6 -6
- package/package.json +2 -9
- package/src/ApiError.ts +310 -0
- package/src/backends/AsyncStore.ts +635 -0
- package/src/backends/InMemory.ts +56 -0
- package/src/backends/Index.ts +500 -0
- package/src/backends/Locked.ts +181 -0
- package/src/backends/Overlay.ts +591 -0
- package/src/backends/SyncStore.ts +589 -0
- package/src/backends/backend.ts +152 -0
- package/src/config.ts +101 -0
- package/src/cred.ts +21 -0
- package/src/emulation/async.ts +910 -0
- package/src/emulation/constants.ts +176 -0
- package/src/emulation/dir.ts +139 -0
- package/src/emulation/index.ts +8 -0
- package/src/emulation/path.ts +468 -0
- package/src/emulation/promises.ts +1071 -0
- package/src/emulation/shared.ts +128 -0
- package/src/emulation/streams.ts +33 -0
- package/src/emulation/sync.ts +898 -0
- package/src/file.ts +721 -0
- package/src/filesystem.ts +544 -0
- package/src/index.ts +21 -0
- package/src/inode.ts +229 -0
- package/src/mutex.ts +52 -0
- package/src/stats.ts +385 -0
- package/src/utils.ts +287 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { RequiredKeys } from 'utilium';
|
|
2
|
+
import { ApiError, ErrorCode } from '../ApiError.js';
|
|
3
|
+
import { FileSystem } from '../filesystem.js';
|
|
4
|
+
import { levenshtein } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the type of Backend.options from the options interface
|
|
10
|
+
*/
|
|
11
|
+
type OptionsConfig<T> = {
|
|
12
|
+
[K in keyof T]: {
|
|
13
|
+
/**
|
|
14
|
+
* The basic JavaScript type(s) for this option.
|
|
15
|
+
*/
|
|
16
|
+
type: OptionType | readonly OptionType[];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Description of the option. Used in error messages and documentation.
|
|
20
|
+
*/
|
|
21
|
+
description: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether or not the option is required (optional can be set to null or undefined). Defaults to false.
|
|
25
|
+
*/
|
|
26
|
+
required: K extends RequiredKeys<T> ? true : false;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A custom validation function to check if the option is valid.
|
|
30
|
+
* When async, resolves if valid and rejects if not.
|
|
31
|
+
* When sync, it will throw an error if not valid.
|
|
32
|
+
*/
|
|
33
|
+
validator?(opt: T[K]): void | Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A backend
|
|
39
|
+
*/
|
|
40
|
+
export interface Backend<FS extends FileSystem = FileSystem, TOptions extends object = object> {
|
|
41
|
+
/**
|
|
42
|
+
* Create a new instance of the backend
|
|
43
|
+
*/
|
|
44
|
+
create(options: TOptions): FS;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A name to identify the backend.
|
|
48
|
+
*/
|
|
49
|
+
name: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Describes all of the options available for this backend.
|
|
53
|
+
*/
|
|
54
|
+
options: OptionsConfig<TOptions>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether the backend is available in the current environment.
|
|
58
|
+
* It supports checking synchronously and asynchronously
|
|
59
|
+
* Sync:
|
|
60
|
+
* Returns 'true' if this backend is available in the current
|
|
61
|
+
* environment. For example, a `localStorage`-backed filesystem will return
|
|
62
|
+
* 'false' if the browser does not support that API.
|
|
63
|
+
*
|
|
64
|
+
* Defaults to 'false', as the FileSystem base class isn't usable alone.
|
|
65
|
+
*/
|
|
66
|
+
isAvailable(): boolean | Promise<boolean>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export function isBackend(arg: unknown): arg is Backend {
|
|
73
|
+
return arg != null && typeof arg == 'object' && 'isAvailable' in arg && typeof arg.isAvailable == 'function' && 'create' in arg && typeof arg.create == 'function';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Checks that the given options object is valid for the file system options.
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
export async function checkOptions<T extends Backend>(backend: T, opts: object): Promise<void> {
|
|
81
|
+
if (typeof opts != 'object' || opts === null) {
|
|
82
|
+
throw new ApiError(ErrorCode.EINVAL, 'Invalid options');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check for required options.
|
|
86
|
+
for (const [optName, opt] of Object.entries(backend.options)) {
|
|
87
|
+
const providedValue = opts?.[optName];
|
|
88
|
+
|
|
89
|
+
if (providedValue === undefined || providedValue === null) {
|
|
90
|
+
if (!opt.required) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
/* Required option not provided.
|
|
94
|
+
if any incorrect options provided, which ones are close to the provided one?
|
|
95
|
+
(edit distance 5 === close)*/
|
|
96
|
+
const incorrectOptions = Object.keys(opts)
|
|
97
|
+
.filter(o => !(o in backend.options))
|
|
98
|
+
.map((a: string) => {
|
|
99
|
+
return { str: a, distance: levenshtein(optName, a) };
|
|
100
|
+
})
|
|
101
|
+
.filter(o => o.distance < 5)
|
|
102
|
+
.sort((a, b) => a.distance - b.distance);
|
|
103
|
+
|
|
104
|
+
throw new ApiError(
|
|
105
|
+
ErrorCode.EINVAL,
|
|
106
|
+
`${backend.name}: Required option '${optName}' not provided.${
|
|
107
|
+
incorrectOptions.length > 0 ? ` You provided '${incorrectOptions[0].str}', did you mean '${optName}'.` : ''
|
|
108
|
+
}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
// Option provided, check type.
|
|
112
|
+
const typeMatches = Array.isArray(opt.type) ? opt.type.indexOf(typeof providedValue) != -1 : typeof providedValue == opt.type;
|
|
113
|
+
if (!typeMatches) {
|
|
114
|
+
throw new ApiError(
|
|
115
|
+
ErrorCode.EINVAL,
|
|
116
|
+
`${backend.name}: Value provided for option ${optName} is not the proper type. Expected ${
|
|
117
|
+
Array.isArray(opt.type) ? `one of {${opt.type.join(', ')}}` : opt.type
|
|
118
|
+
}, but received ${typeof providedValue}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (opt.validator) {
|
|
123
|
+
await opt.validator(providedValue);
|
|
124
|
+
}
|
|
125
|
+
// Otherwise: All good!
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function createBackend<B extends Backend>(backend: B, options?: object): Promise<ReturnType<B['create']>> {
|
|
130
|
+
checkOptions(backend, options);
|
|
131
|
+
const fs = <ReturnType<B['create']>>backend.create(options);
|
|
132
|
+
return fs.ready();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Specifies a file system backend type and its options.
|
|
137
|
+
*
|
|
138
|
+
* Individual options can recursively contain BackendConfig objects for
|
|
139
|
+
* option values that require file systems.
|
|
140
|
+
*
|
|
141
|
+
* The option object for each file system corresponds to that file system's option object passed to its `Create()` method.
|
|
142
|
+
*/
|
|
143
|
+
export type BackendConfiguration<FS extends FileSystem = FileSystem, TOptions extends object = object> = TOptions & {
|
|
144
|
+
backend: Backend<FS, TOptions>;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
150
|
+
export function isBackendConfig(arg: unknown): arg is BackendConfiguration {
|
|
151
|
+
return arg != null && typeof arg == 'object' && 'backend' in arg && isBackend(arg.backend);
|
|
152
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ApiError, ErrorCode } from './ApiError.js';
|
|
2
|
+
import type { Backend, BackendConfiguration } from './backends/backend.js';
|
|
3
|
+
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
4
|
+
import * as fs from './emulation/index.js';
|
|
5
|
+
import { setCred, type MountMapping } from './emulation/shared.js';
|
|
6
|
+
import { FileSystem } from './filesystem.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for a specific mount point
|
|
10
|
+
*/
|
|
11
|
+
export type MountConfiguration<FS extends FileSystem = FileSystem, TOptions extends object = object> = FS | BackendConfiguration<FS, TOptions> | Backend<FS, TOptions>;
|
|
12
|
+
|
|
13
|
+
function isMountConfig(arg: unknown): arg is MountConfiguration {
|
|
14
|
+
return isBackendConfig(arg) || isBackend(arg) || arg instanceof FileSystem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Retrieve a file system with the given configuration.
|
|
19
|
+
* @param config A BackendConfig object.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveMountConfig<FS extends FileSystem, TOptions extends object = object>(config: MountConfiguration<FS, TOptions>, _depth = 0): Promise<FS> {
|
|
22
|
+
if (typeof config !== 'object' || config == null) {
|
|
23
|
+
throw new ApiError(ErrorCode.EINVAL, 'Invalid options on mount configuration');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!isMountConfig(config)) {
|
|
27
|
+
throw new ApiError(ErrorCode.EINVAL, 'Invalid mount configuration');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (config instanceof FileSystem) {
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isBackend(config)) {
|
|
35
|
+
config = <BackendConfiguration<FS, TOptions>>{ backend: config };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const [key, value] of Object.entries(config)) {
|
|
39
|
+
if (key == 'backend') {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!isMountConfig(value)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (_depth > 10) {
|
|
48
|
+
throw new ApiError(ErrorCode.EINVAL, 'Invalid configuration, too deep and possibly infinite');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
config[key] = await resolveMountConfig(value, ++_depth);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { backend } = config;
|
|
55
|
+
|
|
56
|
+
if (!(await backend.isAvailable())) {
|
|
57
|
+
throw new ApiError(ErrorCode.EPERM, 'Backend not available: ' + backend);
|
|
58
|
+
}
|
|
59
|
+
checkOptions(backend, config);
|
|
60
|
+
const mount = backend.create(config);
|
|
61
|
+
await mount.ready();
|
|
62
|
+
return mount;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
*A mapping of mount points to their configurations
|
|
67
|
+
*/
|
|
68
|
+
export type MappingConfiguration = Partial<{
|
|
69
|
+
uid: number;
|
|
70
|
+
gid: number;
|
|
71
|
+
}> &
|
|
72
|
+
Record<string, FileSystem | BackendConfiguration | Backend>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Configuration for the file systems
|
|
76
|
+
*/
|
|
77
|
+
export type Configuration = MountConfiguration | MappingConfiguration;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates filesystems with the given configuration, and initializes ZenFS with it.
|
|
81
|
+
* @see Configuration for more info on the configuration object.
|
|
82
|
+
*/
|
|
83
|
+
export async function configure(config: Configuration): Promise<void> {
|
|
84
|
+
const uid = 'uid' in config ? +config.uid || 0 : 0;
|
|
85
|
+
const gid = 'gid' in config ? +config.gid || 0 : 0;
|
|
86
|
+
|
|
87
|
+
if (isMountConfig(config)) {
|
|
88
|
+
// single FS
|
|
89
|
+
config = { '/': config };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const [point, value] of Object.entries(config)) {
|
|
93
|
+
if (point == 'uid' || point == 'gid' || typeof value == 'number') {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
config[point] = await resolveMountConfig(value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fs.mountMapping(<MountMapping>config);
|
|
100
|
+
setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
|
|
101
|
+
}
|
package/src/cred.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials used for various operations.
|
|
3
|
+
* Similar to Linux's cred struct. See https://github.com/torvalds/linux/blob/master/include/linux/cred.h
|
|
4
|
+
*/
|
|
5
|
+
export interface Cred {
|
|
6
|
+
uid: number;
|
|
7
|
+
gid: number;
|
|
8
|
+
suid: number;
|
|
9
|
+
sgid: number;
|
|
10
|
+
euid: number;
|
|
11
|
+
egid: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const rootCred: Cred = {
|
|
15
|
+
uid: 0,
|
|
16
|
+
gid: 0,
|
|
17
|
+
suid: 0,
|
|
18
|
+
sgid: 0,
|
|
19
|
+
euid: 0,
|
|
20
|
+
egid: 0,
|
|
21
|
+
};
|