@aigne/afs 1.3.0 → 1.4.0-beta.10
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/CHANGELOG.md +106 -0
- package/lib/cjs/afs.d.ts +25 -28
- package/lib/cjs/afs.js +210 -43
- package/lib/cjs/error.d.ts +13 -0
- package/lib/cjs/error.js +25 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/type.d.ts +189 -38
- package/lib/cjs/type.js +23 -0
- package/lib/dts/afs.d.ts +25 -28
- package/lib/dts/error.d.ts +13 -0
- package/lib/dts/index.d.ts +1 -0
- package/lib/dts/type.d.ts +189 -38
- package/lib/esm/afs.d.ts +25 -28
- package/lib/esm/afs.js +210 -43
- package/lib/esm/error.d.ts +13 -0
- package/lib/esm/error.js +20 -0
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/type.d.ts +189 -38
- package/lib/esm/type.js +22 -1
- package/package.json +6 -2
package/lib/esm/afs.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { Emitter } from "strict-event-emitter";
|
|
2
|
-
import type
|
|
2
|
+
import { type AFSContext, type AFSDeleteOptions, type AFSDeleteResult, type AFSExecOptions, type AFSExecResult, type AFSModule, type AFSReadOptions, type AFSReadResult, type AFSRenameOptions, type AFSRenameResult, type AFSRoot, type AFSRootEvents, type AFSRootListOptions, type AFSRootListResult, type AFSRootSearchOptions, type AFSRootSearchResult, type AFSWriteEntryPayload, type AFSWriteOptions, type AFSWriteResult } from "./type.js";
|
|
3
3
|
export interface AFSOptions {
|
|
4
4
|
modules?: AFSModule[];
|
|
5
|
+
context?: AFSContext;
|
|
5
6
|
}
|
|
6
7
|
export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
8
|
+
options: AFSOptions;
|
|
7
9
|
name: string;
|
|
8
10
|
constructor(options?: AFSOptions);
|
|
9
11
|
private modules;
|
|
12
|
+
/**
|
|
13
|
+
* Check if write operations are allowed for the given module.
|
|
14
|
+
* Throws AFSReadonlyError if not allowed.
|
|
15
|
+
*/
|
|
16
|
+
private checkWritePermission;
|
|
10
17
|
mount(module: AFSModule): this;
|
|
11
18
|
listModules(): Promise<{
|
|
12
19
|
name: string;
|
|
@@ -14,32 +21,22 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
|
14
21
|
description?: string;
|
|
15
22
|
module: AFSModule;
|
|
16
23
|
}[]>;
|
|
17
|
-
list(path: string, options?:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
message?: string;
|
|
28
|
-
}>;
|
|
29
|
-
delete(path: string, options?: AFSDeleteOptions): Promise<{
|
|
30
|
-
message?: string;
|
|
31
|
-
}>;
|
|
32
|
-
rename(oldPath: string, newPath: string, options?: AFSRenameOptions): Promise<{
|
|
33
|
-
message?: string;
|
|
34
|
-
}>;
|
|
35
|
-
search(path: string, query: string, options?: AFSSearchOptions): Promise<{
|
|
36
|
-
list: AFSEntry[];
|
|
37
|
-
message?: string;
|
|
38
|
-
}>;
|
|
24
|
+
list(path: string, options?: AFSRootListOptions): Promise<AFSRootListResult>;
|
|
25
|
+
private _list;
|
|
26
|
+
read(path: string, _options?: AFSReadOptions): Promise<AFSReadResult>;
|
|
27
|
+
write(path: string, content: AFSWriteEntryPayload, options?: AFSWriteOptions): Promise<AFSWriteResult>;
|
|
28
|
+
delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult>;
|
|
29
|
+
rename(oldPath: string, newPath: string, options?: AFSRenameOptions): Promise<AFSRenameResult>;
|
|
30
|
+
search(path: string, query: string, options?: AFSRootSearchOptions): Promise<AFSRootSearchResult>;
|
|
31
|
+
private processWithPreset;
|
|
32
|
+
private _select;
|
|
33
|
+
private _search;
|
|
39
34
|
private findModules;
|
|
40
|
-
exec(path: string, args: Record<string, any>, options:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
exec(path: string, args: Record<string, any>, options: AFSExecOptions): Promise<AFSExecResult>;
|
|
36
|
+
private buildSimpleListView;
|
|
37
|
+
private buildTreeView;
|
|
38
|
+
private buildMetadataSuffix;
|
|
39
|
+
private physicalPath?;
|
|
40
|
+
initializePhysicalPath(): Promise<string>;
|
|
41
|
+
cleanupPhysicalPath(): Promise<void>;
|
|
45
42
|
}
|
package/lib/esm/afs.js
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
|
+
import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
|
|
2
|
+
import { v7 } from "@aigne/uuid";
|
|
1
3
|
import { Emitter } from "strict-event-emitter";
|
|
2
4
|
import { joinURL } from "ufo";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { AFSReadonlyError } from "./error.js";
|
|
7
|
+
import { afsEntrySchema, } from "./type.js";
|
|
3
8
|
const DEFAULT_MAX_DEPTH = 1;
|
|
4
9
|
const MODULES_ROOT_DIR = "/modules";
|
|
5
10
|
export class AFS extends Emitter {
|
|
11
|
+
options;
|
|
6
12
|
name = "AFSRoot";
|
|
7
|
-
constructor(options) {
|
|
13
|
+
constructor(options = {}) {
|
|
8
14
|
super();
|
|
15
|
+
this.options = options;
|
|
9
16
|
for (const module of options?.modules ?? []) {
|
|
10
17
|
this.mount(module);
|
|
11
18
|
}
|
|
12
19
|
}
|
|
13
20
|
modules = new Map();
|
|
21
|
+
/**
|
|
22
|
+
* Check if write operations are allowed for the given module.
|
|
23
|
+
* Throws AFSReadonlyError if not allowed.
|
|
24
|
+
*/
|
|
25
|
+
checkWritePermission(module, operation, path) {
|
|
26
|
+
// Module-level readonly (undefined means readonly by default)
|
|
27
|
+
if (module.accessMode !== "readwrite") {
|
|
28
|
+
throw new AFSReadonlyError(`Module '${module.name}' is readonly, cannot perform ${operation} to ${path}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
14
31
|
mount(module) {
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
throw new Error(`Invalid
|
|
32
|
+
// Validate module name (should not contain '/')
|
|
33
|
+
if (module.name.includes("/")) {
|
|
34
|
+
throw new Error(`Invalid module name: ${module.name}. Module name must not contain '/'`);
|
|
18
35
|
}
|
|
19
|
-
path = joinURL(MODULES_ROOT_DIR,
|
|
36
|
+
const path = joinURL(MODULES_ROOT_DIR, module.name);
|
|
20
37
|
if (this.modules.has(path)) {
|
|
21
38
|
throw new Error(`Module already mounted at path: ${path}`);
|
|
22
39
|
}
|
|
@@ -32,74 +49,101 @@ export class AFS extends Emitter {
|
|
|
32
49
|
module,
|
|
33
50
|
}));
|
|
34
51
|
}
|
|
35
|
-
async list(path, options) {
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
52
|
+
async list(path, options = {}) {
|
|
53
|
+
let preset;
|
|
54
|
+
if (options.preset) {
|
|
55
|
+
preset = this.options?.context?.list?.presets?.[options.preset];
|
|
56
|
+
if (!preset)
|
|
57
|
+
throw new Error(`Preset not found: ${options.preset}`);
|
|
58
|
+
}
|
|
59
|
+
return await this.processWithPreset(path, undefined, preset, {
|
|
60
|
+
...options,
|
|
61
|
+
defaultSelect: () => this._list(path, options),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async _list(path, options = {}) {
|
|
39
65
|
const results = [];
|
|
40
|
-
|
|
66
|
+
// Special case: listing root "/" should return /modules directory
|
|
67
|
+
if (path === "/" && this.modules.size > 0) {
|
|
68
|
+
const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
69
|
+
// Always include /modules directory first
|
|
70
|
+
results.push({
|
|
71
|
+
id: "modules",
|
|
72
|
+
path: MODULES_ROOT_DIR,
|
|
73
|
+
summary: "All mounted modules",
|
|
74
|
+
});
|
|
75
|
+
if (maxDepth === 1) {
|
|
76
|
+
// Only show /modules directory
|
|
77
|
+
return { data: results };
|
|
78
|
+
}
|
|
79
|
+
// For maxDepth > 1, also get children of /modules with reduced depth
|
|
80
|
+
const childrenResult = await this._list(MODULES_ROOT_DIR, {
|
|
81
|
+
...options,
|
|
82
|
+
maxDepth: maxDepth - 1,
|
|
83
|
+
});
|
|
84
|
+
results.push(...childrenResult.data);
|
|
85
|
+
return { data: results };
|
|
86
|
+
}
|
|
41
87
|
const matches = this.findModules(path, options);
|
|
42
88
|
for (const matched of matches) {
|
|
43
|
-
const moduleEntry = {
|
|
44
|
-
id: matched.module.name,
|
|
45
|
-
path: matched.remainedModulePath,
|
|
46
|
-
summary: matched.module.description,
|
|
47
|
-
};
|
|
48
89
|
if (matched.maxDepth === 0) {
|
|
90
|
+
// When maxDepth is 0, show the module entry
|
|
91
|
+
const moduleEntry = {
|
|
92
|
+
id: matched.module.name,
|
|
93
|
+
path: matched.modulePath,
|
|
94
|
+
summary: matched.module.description,
|
|
95
|
+
};
|
|
49
96
|
results.push(moduleEntry);
|
|
50
97
|
continue;
|
|
51
98
|
}
|
|
52
99
|
if (!matched.module.list)
|
|
53
100
|
continue;
|
|
54
101
|
try {
|
|
55
|
-
const {
|
|
102
|
+
const { data } = await matched.module.list(matched.subpath, {
|
|
56
103
|
...options,
|
|
57
104
|
maxDepth: matched.maxDepth,
|
|
58
105
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
results.push(moduleEntry);
|
|
67
|
-
}
|
|
68
|
-
if (message)
|
|
69
|
-
messages.push(message);
|
|
106
|
+
const children = data.map((entry) => ({
|
|
107
|
+
...entry,
|
|
108
|
+
path: joinURL(matched.modulePath, entry.path),
|
|
109
|
+
}));
|
|
110
|
+
// Always include all nodes (including the current path itself)
|
|
111
|
+
// This ensures consistent behavior across all listing scenarios
|
|
112
|
+
results.push(...children);
|
|
70
113
|
}
|
|
71
114
|
catch (error) {
|
|
72
|
-
|
|
115
|
+
throw new Error(`Error listing from module at ${matched.modulePath}: ${error.message}`);
|
|
73
116
|
}
|
|
74
117
|
}
|
|
75
|
-
return {
|
|
118
|
+
return { data: results };
|
|
76
119
|
}
|
|
77
|
-
async read(path) {
|
|
120
|
+
async read(path, _options) {
|
|
78
121
|
const modules = this.findModules(path, { exactMatch: true });
|
|
79
122
|
for (const { module, modulePath, subpath } of modules) {
|
|
80
123
|
const res = await module.read?.(subpath);
|
|
81
|
-
if (res?.
|
|
124
|
+
if (res?.data) {
|
|
82
125
|
return {
|
|
83
126
|
...res,
|
|
84
|
-
|
|
85
|
-
...res.
|
|
86
|
-
path: joinURL(modulePath, res.
|
|
127
|
+
data: {
|
|
128
|
+
...res.data,
|
|
129
|
+
path: joinURL(modulePath, res.data.path),
|
|
87
130
|
},
|
|
88
131
|
};
|
|
89
132
|
}
|
|
90
133
|
}
|
|
91
|
-
return {
|
|
134
|
+
return { data: undefined, message: "File not found" };
|
|
92
135
|
}
|
|
93
136
|
async write(path, content, options) {
|
|
94
137
|
const module = this.findModules(path, { exactMatch: true })[0];
|
|
95
138
|
if (!module?.module.write)
|
|
96
139
|
throw new Error(`No module found for path: ${path}`);
|
|
140
|
+
this.checkWritePermission(module.module, "write", path);
|
|
97
141
|
const res = await module.module.write(module.subpath, content, options);
|
|
98
142
|
return {
|
|
99
143
|
...res,
|
|
100
|
-
|
|
101
|
-
...res.
|
|
102
|
-
path: joinURL(module.modulePath, res.
|
|
144
|
+
data: {
|
|
145
|
+
...res.data,
|
|
146
|
+
path: joinURL(module.modulePath, res.data.path),
|
|
103
147
|
},
|
|
104
148
|
};
|
|
105
149
|
}
|
|
@@ -107,6 +151,7 @@ export class AFS extends Emitter {
|
|
|
107
151
|
const module = this.findModules(path, { exactMatch: true })[0];
|
|
108
152
|
if (!module?.module.delete)
|
|
109
153
|
throw new Error(`No module found for path: ${path}`);
|
|
154
|
+
this.checkWritePermission(module.module, "delete", path);
|
|
110
155
|
return await module.module.delete(module.subpath, options);
|
|
111
156
|
}
|
|
112
157
|
async rename(oldPath, newPath, options) {
|
|
@@ -119,17 +164,64 @@ export class AFS extends Emitter {
|
|
|
119
164
|
if (!oldModule.module.rename) {
|
|
120
165
|
throw new Error(`Module does not support rename operation: ${oldModule.modulePath}`);
|
|
121
166
|
}
|
|
167
|
+
this.checkWritePermission(oldModule.module, "rename", oldPath);
|
|
122
168
|
return await oldModule.module.rename(oldModule.subpath, newModule.subpath, options);
|
|
123
169
|
}
|
|
124
|
-
async search(path, query, options) {
|
|
170
|
+
async search(path, query, options = {}) {
|
|
171
|
+
let preset;
|
|
172
|
+
if (options.preset) {
|
|
173
|
+
preset = this.options?.context?.search?.presets?.[options.preset];
|
|
174
|
+
if (!preset)
|
|
175
|
+
throw new Error(`Preset not found: ${options.preset}`);
|
|
176
|
+
}
|
|
177
|
+
return await this.processWithPreset(path, query, preset, {
|
|
178
|
+
...options,
|
|
179
|
+
defaultSelect: () => this._search(path, query, options),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async processWithPreset(path, query, preset, options) {
|
|
183
|
+
const select = options.select || preset?.select;
|
|
184
|
+
const per = options.per || preset?.per;
|
|
185
|
+
const dedupe = options.dedupe || preset?.dedupe;
|
|
186
|
+
const format = options.format || preset?.format;
|
|
187
|
+
const entries = select
|
|
188
|
+
? (await this._select(path, query, select, options)).data
|
|
189
|
+
: (await options.defaultSelect()).data;
|
|
190
|
+
const mapped = per
|
|
191
|
+
? await Promise.all(entries.map((data) => per.invoke({ data }, options).then((res) => res.data)))
|
|
192
|
+
: entries;
|
|
193
|
+
const deduped = dedupe
|
|
194
|
+
? await dedupe.invoke({ data: mapped }, options).then((res) => res.data)
|
|
195
|
+
: mapped;
|
|
196
|
+
let formatted = deduped;
|
|
197
|
+
if (format === "simple-list" || format === "tree") {
|
|
198
|
+
const valid = z.array(afsEntrySchema).safeParse(deduped);
|
|
199
|
+
if (!valid.data)
|
|
200
|
+
throw new Error("Tree format requires entries to be AFSEntry objects");
|
|
201
|
+
if (format === "tree")
|
|
202
|
+
formatted = this.buildTreeView(valid.data);
|
|
203
|
+
else if (format === "simple-list")
|
|
204
|
+
formatted = this.buildSimpleListView(valid.data);
|
|
205
|
+
}
|
|
206
|
+
else if (typeof format === "object" && typeof format.invoke === "function") {
|
|
207
|
+
formatted = await format.invoke({ data: deduped }, options).then((res) => res.data);
|
|
208
|
+
}
|
|
209
|
+
return { data: formatted };
|
|
210
|
+
}
|
|
211
|
+
async _select(path, query, select, options) {
|
|
212
|
+
const { data } = await select.invoke({ path, query }, options);
|
|
213
|
+
const results = (await Promise.all(data.map((p) => this.read(p).then((res) => res.data)))).filter((i) => !!i);
|
|
214
|
+
return { data: results };
|
|
215
|
+
}
|
|
216
|
+
async _search(path, query, options) {
|
|
125
217
|
const results = [];
|
|
126
218
|
const messages = [];
|
|
127
219
|
for (const { module, modulePath, subpath } of this.findModules(path)) {
|
|
128
220
|
if (!module.search)
|
|
129
221
|
continue;
|
|
130
222
|
try {
|
|
131
|
-
const {
|
|
132
|
-
results.push(...
|
|
223
|
+
const { data, message } = await module.search(subpath, query, options);
|
|
224
|
+
results.push(...data.map((entry) => ({
|
|
133
225
|
...entry,
|
|
134
226
|
path: joinURL(modulePath, entry.path),
|
|
135
227
|
})));
|
|
@@ -137,10 +229,10 @@ export class AFS extends Emitter {
|
|
|
137
229
|
messages.push(message);
|
|
138
230
|
}
|
|
139
231
|
catch (error) {
|
|
140
|
-
|
|
232
|
+
throw new Error(`Error searching in module at ${modulePath}: ${error.message}`);
|
|
141
233
|
}
|
|
142
234
|
}
|
|
143
|
-
return {
|
|
235
|
+
return { data: results, message: messages.join("; ") };
|
|
144
236
|
}
|
|
145
237
|
findModules(path, options) {
|
|
146
238
|
const maxDepth = Math.max(options?.maxDepth ?? DEFAULT_MAX_DEPTH, 1);
|
|
@@ -176,4 +268,79 @@ export class AFS extends Emitter {
|
|
|
176
268
|
throw new Error(`No module found for path: ${path}`);
|
|
177
269
|
return await module.module.exec(module.subpath, args, options);
|
|
178
270
|
}
|
|
271
|
+
buildSimpleListView(entries) {
|
|
272
|
+
return entries.map((entry) => `${entry.path}${this.buildMetadataSuffix(entry)}`);
|
|
273
|
+
}
|
|
274
|
+
buildTreeView(entries) {
|
|
275
|
+
const tree = {};
|
|
276
|
+
const entryMap = new Map();
|
|
277
|
+
for (const entry of entries) {
|
|
278
|
+
entryMap.set(entry.path, entry);
|
|
279
|
+
const parts = entry.path.split("/").filter(Boolean);
|
|
280
|
+
let current = tree;
|
|
281
|
+
for (const part of parts) {
|
|
282
|
+
if (!current[part]) {
|
|
283
|
+
current[part] = {};
|
|
284
|
+
}
|
|
285
|
+
current = current[part];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const renderTree = (node, prefix = "", currentPath = "") => {
|
|
289
|
+
let result = "";
|
|
290
|
+
const keys = Object.keys(node);
|
|
291
|
+
keys.forEach((key, index) => {
|
|
292
|
+
const isLast = index === keys.length - 1;
|
|
293
|
+
const fullPath = currentPath ? `${currentPath}/${key}` : `/${key}`;
|
|
294
|
+
const entry = entryMap.get(fullPath);
|
|
295
|
+
result += `${prefix}${isLast ? "└── " : "├── "}${key}${entry ? this.buildMetadataSuffix(entry) : ""}`;
|
|
296
|
+
result += `\n`;
|
|
297
|
+
result += renderTree(node[key], `${prefix}${isLast ? " " : "│ "}`, fullPath);
|
|
298
|
+
});
|
|
299
|
+
return result;
|
|
300
|
+
};
|
|
301
|
+
return renderTree(tree);
|
|
302
|
+
}
|
|
303
|
+
buildMetadataSuffix(entry) {
|
|
304
|
+
// Build metadata suffix
|
|
305
|
+
const metadataParts = [];
|
|
306
|
+
// Children count
|
|
307
|
+
const childrenCount = entry?.metadata?.childrenCount;
|
|
308
|
+
if (typeof childrenCount === "number") {
|
|
309
|
+
metadataParts.push(`${childrenCount} items`);
|
|
310
|
+
}
|
|
311
|
+
// Children truncated
|
|
312
|
+
if (entry?.metadata?.childrenTruncated) {
|
|
313
|
+
metadataParts.push("truncated");
|
|
314
|
+
}
|
|
315
|
+
// Gitignored
|
|
316
|
+
if (entry?.metadata?.gitignored) {
|
|
317
|
+
metadataParts.push("gitignored");
|
|
318
|
+
}
|
|
319
|
+
// Executable
|
|
320
|
+
if (entry?.metadata?.execute) {
|
|
321
|
+
metadataParts.push("executable");
|
|
322
|
+
}
|
|
323
|
+
const metadataSuffix = metadataParts.length > 0 ? ` [${metadataParts.join(", ")}]` : "";
|
|
324
|
+
return metadataSuffix;
|
|
325
|
+
}
|
|
326
|
+
physicalPath;
|
|
327
|
+
async initializePhysicalPath() {
|
|
328
|
+
this.physicalPath ??= (async () => {
|
|
329
|
+
const rootDir = nodejs.path.join(nodejs.os.tmpdir(), v7());
|
|
330
|
+
await nodejs.fs.mkdir(rootDir, { recursive: true });
|
|
331
|
+
for (const [modulePath, module] of this.modules) {
|
|
332
|
+
const physicalModulePath = nodejs.path.join(rootDir, modulePath);
|
|
333
|
+
await nodejs.fs.mkdir(nodejs.path.dirname(physicalModulePath), { recursive: true });
|
|
334
|
+
await module.symlinkToPhysical?.(physicalModulePath);
|
|
335
|
+
}
|
|
336
|
+
return rootDir;
|
|
337
|
+
})();
|
|
338
|
+
return this.physicalPath;
|
|
339
|
+
}
|
|
340
|
+
async cleanupPhysicalPath() {
|
|
341
|
+
if (this.physicalPath) {
|
|
342
|
+
await nodejs.fs.rm(await this.physicalPath, { recursive: true, force: true });
|
|
343
|
+
this.physicalPath = undefined;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
179
346
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all AFS errors.
|
|
3
|
+
*/
|
|
4
|
+
export declare class AFSError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
constructor(message: string, code: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Error thrown when attempting write operations on a readonly AFS or module.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AFSReadonlyError extends AFSError {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
package/lib/esm/error.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all AFS errors.
|
|
3
|
+
*/
|
|
4
|
+
export class AFSError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, code) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "AFSError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Error thrown when attempting write operations on a readonly AFS or module.
|
|
14
|
+
*/
|
|
15
|
+
export class AFSReadonlyError extends AFSError {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message, "AFS_READONLY");
|
|
18
|
+
this.name = "AFSReadonlyError";
|
|
19
|
+
}
|
|
20
|
+
}
|
package/lib/esm/index.d.ts
CHANGED
package/lib/esm/index.js
CHANGED