@aigne/afs 1.11.0-beta.2 → 1.11.0-beta.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/afs.cjs +234 -65
- package/dist/afs.d.cts +78 -4
- package/dist/afs.d.cts.map +1 -1
- package/dist/afs.d.mts +78 -4
- package/dist/afs.d.mts.map +1 -1
- package/dist/afs.mjs +234 -65
- package/dist/afs.mjs.map +1 -1
- package/dist/index.cjs +9 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +2 -1
- package/dist/path.cjs +255 -0
- package/dist/path.d.cts +93 -0
- package/dist/path.d.cts.map +1 -0
- package/dist/path.d.mts +93 -0
- package/dist/path.d.mts.map +1 -0
- package/dist/path.mjs +249 -0
- package/dist/path.mjs.map +1 -0
- package/package.json +1 -1
package/dist/afs.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_error = require('./error.cjs');
|
|
2
|
+
const require_path = require('./path.cjs');
|
|
2
3
|
const require_type = require('./type.cjs');
|
|
3
4
|
let _aigne_uuid = require("@aigne/uuid");
|
|
4
5
|
let strict_event_emitter = require("strict-event-emitter");
|
|
@@ -8,14 +9,62 @@ let zod = require("zod");
|
|
|
8
9
|
//#region src/afs.ts
|
|
9
10
|
const DEFAULT_MAX_DEPTH = 1;
|
|
10
11
|
const MODULES_ROOT_DIR = "/modules";
|
|
12
|
+
/**
|
|
13
|
+
* Characters forbidden in namespace names (security-sensitive)
|
|
14
|
+
*/
|
|
15
|
+
const NAMESPACE_FORBIDDEN_CHARS = [
|
|
16
|
+
"/",
|
|
17
|
+
"\\",
|
|
18
|
+
":",
|
|
19
|
+
";",
|
|
20
|
+
"|",
|
|
21
|
+
"&",
|
|
22
|
+
"`",
|
|
23
|
+
"$",
|
|
24
|
+
"(",
|
|
25
|
+
")",
|
|
26
|
+
">",
|
|
27
|
+
"<",
|
|
28
|
+
"\n",
|
|
29
|
+
"\r",
|
|
30
|
+
" ",
|
|
31
|
+
"\0"
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Validate a namespace name for mount operations
|
|
35
|
+
* @throws Error if namespace is invalid
|
|
36
|
+
*/
|
|
37
|
+
function validateNamespaceName(namespace) {
|
|
38
|
+
if (namespace.trim() === "") throw new Error("Namespace cannot be empty or whitespace-only");
|
|
39
|
+
for (const char of NAMESPACE_FORBIDDEN_CHARS) if (namespace.includes(char)) throw new Error(`Namespace contains forbidden character: '${char}'`);
|
|
40
|
+
}
|
|
11
41
|
var AFS = class extends strict_event_emitter.Emitter {
|
|
12
42
|
name = "AFSRoot";
|
|
13
43
|
constructor(options = {}) {
|
|
14
44
|
super();
|
|
15
45
|
this.options = options;
|
|
16
|
-
for (const module of options?.modules ?? []) this.mount(module);
|
|
46
|
+
for (const module of options?.modules ?? []) this.mount(module, (0, ufo.joinURL)(MODULES_ROOT_DIR, module.name));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Internal storage: Map<compositeKey, MountEntry>
|
|
50
|
+
* compositeKey = `${namespace ?? ""}:${path}`
|
|
51
|
+
*/
|
|
52
|
+
mounts = /* @__PURE__ */ new Map();
|
|
53
|
+
/**
|
|
54
|
+
* Legacy compatibility: Map<path, AFSModule> for modules mounted via old API
|
|
55
|
+
* This is used internally by findModules for backward compatibility
|
|
56
|
+
*/
|
|
57
|
+
get modules() {
|
|
58
|
+
const map = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const entry of this.mounts.values()) if (entry.namespace === null) map.set(entry.path, entry.module);
|
|
60
|
+
return map;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create composite key for mount storage
|
|
64
|
+
*/
|
|
65
|
+
makeKey(namespace, path) {
|
|
66
|
+
return `${namespace ?? ""}:${path}`;
|
|
17
67
|
}
|
|
18
|
-
modules = /* @__PURE__ */ new Map();
|
|
19
68
|
/**
|
|
20
69
|
* Check if write operations are allowed for the given module.
|
|
21
70
|
* Throws AFSReadonlyError if not allowed.
|
|
@@ -23,57 +72,197 @@ var AFS = class extends strict_event_emitter.Emitter {
|
|
|
23
72
|
checkWritePermission(module, operation, path) {
|
|
24
73
|
if (module.accessMode !== "readwrite") throw new require_error.AFSReadonlyError(`Module '${module.name}' is readonly, cannot perform ${operation} to ${path}`);
|
|
25
74
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Mount a module at a path in a namespace
|
|
77
|
+
*
|
|
78
|
+
* @param module - The module to mount
|
|
79
|
+
* @param path - The path to mount at (optional, defaults to /modules/{module.name} for backward compatibility)
|
|
80
|
+
* @param options - Mount options (namespace, replace)
|
|
81
|
+
*/
|
|
82
|
+
mount(module, path, options) {
|
|
83
|
+
require_path.validateModuleName(module.name);
|
|
84
|
+
const normalizedPath = require_path.validatePath(path ?? (0, ufo.joinURL)(MODULES_ROOT_DIR, module.name));
|
|
85
|
+
const namespace = options?.namespace === void 0 ? null : options.namespace;
|
|
86
|
+
if (namespace !== null) {
|
|
87
|
+
if (namespace === "") throw new Error("Namespace cannot be empty or whitespace-only");
|
|
88
|
+
validateNamespaceName(namespace);
|
|
89
|
+
}
|
|
90
|
+
const key = this.makeKey(namespace, normalizedPath);
|
|
91
|
+
if (this.mounts.has(key)) if (options?.replace) this.mounts.delete(key);
|
|
92
|
+
else throw new Error(`Mount conflict: path '${normalizedPath}' already mounted in namespace '${namespace ?? "default"}'`);
|
|
93
|
+
for (const entry of this.mounts.values()) {
|
|
94
|
+
if (entry.namespace !== namespace) continue;
|
|
95
|
+
const existingPath = entry.path;
|
|
96
|
+
if (normalizedPath === "/" || existingPath === "/") throw new Error(`Mount conflict: path '${normalizedPath}' conflicts with existing mount '${existingPath}' in namespace '${namespace ?? "default"}'`);
|
|
97
|
+
if (existingPath.startsWith(normalizedPath) && (existingPath === normalizedPath || existingPath.length === normalizedPath.length || existingPath[normalizedPath.length] === "/")) throw new Error(`Mount conflict: path '${normalizedPath}' conflicts with existing mount '${existingPath}' in namespace '${namespace ?? "default"}'`);
|
|
98
|
+
if (normalizedPath.startsWith(existingPath) && (normalizedPath === existingPath || normalizedPath.length === existingPath.length || normalizedPath[existingPath.length] === "/")) throw new Error(`Mount conflict: path '${normalizedPath}' conflicts with existing mount '${existingPath}' in namespace '${namespace ?? "default"}'`);
|
|
99
|
+
}
|
|
100
|
+
this.mounts.set(key, {
|
|
101
|
+
namespace,
|
|
102
|
+
path: normalizedPath,
|
|
103
|
+
module
|
|
104
|
+
});
|
|
31
105
|
module.onMount?.(this);
|
|
32
106
|
return this;
|
|
33
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Get all mounts, optionally filtered by namespace
|
|
110
|
+
*
|
|
111
|
+
* @param namespace - Filter by namespace (undefined = all, null = default only)
|
|
112
|
+
*/
|
|
113
|
+
getMounts(namespace) {
|
|
114
|
+
const result = [];
|
|
115
|
+
for (const entry of this.mounts.values()) if (namespace === void 0 || entry.namespace === namespace) result.push({
|
|
116
|
+
namespace: entry.namespace,
|
|
117
|
+
path: entry.path,
|
|
118
|
+
module: entry.module
|
|
119
|
+
});
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get all unique namespaces that have mounts
|
|
124
|
+
*/
|
|
125
|
+
getNamespaces() {
|
|
126
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
127
|
+
for (const entry of this.mounts.values()) namespaces.add(entry.namespace);
|
|
128
|
+
return Array.from(namespaces);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Unmount a module at a path in a namespace
|
|
132
|
+
*
|
|
133
|
+
* @param path - The path to unmount
|
|
134
|
+
* @param namespace - The namespace (undefined/null for default namespace)
|
|
135
|
+
* @returns true if unmounted, false if not found
|
|
136
|
+
*/
|
|
137
|
+
unmount(path, namespace) {
|
|
138
|
+
const normalizedPath = require_path.validatePath(path);
|
|
139
|
+
const ns = namespace === void 0 ? null : namespace;
|
|
140
|
+
const key = this.makeKey(ns, normalizedPath);
|
|
141
|
+
if (this.mounts.has(key)) {
|
|
142
|
+
this.mounts.delete(key);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if a path is mounted in a namespace
|
|
149
|
+
*
|
|
150
|
+
* @param path - The path to check
|
|
151
|
+
* @param namespace - The namespace (undefined/null for default namespace)
|
|
152
|
+
*/
|
|
153
|
+
isMounted(path, namespace) {
|
|
154
|
+
const normalizedPath = require_path.validatePath(path);
|
|
155
|
+
const ns = namespace === void 0 ? null : namespace;
|
|
156
|
+
const key = this.makeKey(ns, normalizedPath);
|
|
157
|
+
return this.mounts.has(key);
|
|
158
|
+
}
|
|
34
159
|
async listModules() {
|
|
35
|
-
return Array.from(this.
|
|
36
|
-
path,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
module
|
|
160
|
+
return Array.from(this.mounts.values()).map((entry) => ({
|
|
161
|
+
path: entry.path,
|
|
162
|
+
namespace: entry.namespace,
|
|
163
|
+
name: entry.module.name,
|
|
164
|
+
description: entry.module.description,
|
|
165
|
+
module: entry.module
|
|
40
166
|
}));
|
|
41
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Parse a path and extract namespace if it's a canonical path
|
|
170
|
+
* Returns the namespace and the path within the namespace
|
|
171
|
+
*/
|
|
172
|
+
parsePathWithNamespace(inputPath) {
|
|
173
|
+
if (require_path.isCanonicalPath(inputPath)) {
|
|
174
|
+
const parsed = require_path.parseCanonicalPath(inputPath);
|
|
175
|
+
return {
|
|
176
|
+
namespace: parsed.namespace,
|
|
177
|
+
path: parsed.path
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
namespace: null,
|
|
182
|
+
path: require_path.validatePath(inputPath)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Find modules that can handle a path in a specific namespace
|
|
187
|
+
*/
|
|
188
|
+
findModulesInNamespace(path, namespace, options) {
|
|
189
|
+
const maxDepth = Math.max(options?.maxDepth ?? DEFAULT_MAX_DEPTH, 1);
|
|
190
|
+
const matched = [];
|
|
191
|
+
for (const entry of this.mounts.values()) {
|
|
192
|
+
if (entry.namespace !== namespace) continue;
|
|
193
|
+
const modulePath = entry.path;
|
|
194
|
+
const module = entry.module;
|
|
195
|
+
const pathSegments = path.split("/").filter(Boolean);
|
|
196
|
+
const modulePathSegments = modulePath.split("/").filter(Boolean);
|
|
197
|
+
let newMaxDepth;
|
|
198
|
+
let subpath;
|
|
199
|
+
let remainedModulePath;
|
|
200
|
+
if (!options?.exactMatch && modulePath.startsWith(path)) {
|
|
201
|
+
newMaxDepth = Math.max(0, maxDepth - (modulePathSegments.length - pathSegments.length));
|
|
202
|
+
subpath = "/";
|
|
203
|
+
remainedModulePath = (0, ufo.joinURL)("/", ...modulePathSegments.slice(pathSegments.length).slice(0, maxDepth));
|
|
204
|
+
} else if (path.startsWith(modulePath)) {
|
|
205
|
+
newMaxDepth = maxDepth;
|
|
206
|
+
subpath = (0, ufo.joinURL)("/", ...pathSegments.slice(modulePathSegments.length));
|
|
207
|
+
remainedModulePath = "/";
|
|
208
|
+
} else continue;
|
|
209
|
+
if (newMaxDepth < 0) continue;
|
|
210
|
+
matched.push({
|
|
211
|
+
module,
|
|
212
|
+
modulePath,
|
|
213
|
+
maxDepth: newMaxDepth,
|
|
214
|
+
subpath,
|
|
215
|
+
remainedModulePath
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return matched;
|
|
219
|
+
}
|
|
42
220
|
async list(path, options = {}) {
|
|
221
|
+
const { namespace, path: normalizedPath } = this.parsePathWithNamespace(path);
|
|
43
222
|
let preset;
|
|
44
223
|
if (options.preset) {
|
|
45
224
|
preset = this.options?.context?.list?.presets?.[options.preset];
|
|
46
225
|
if (!preset) throw new Error(`Preset not found: ${options.preset}`);
|
|
47
226
|
}
|
|
48
|
-
return await this.processWithPreset(
|
|
227
|
+
return await this.processWithPreset(normalizedPath, void 0, preset, {
|
|
49
228
|
...options,
|
|
50
|
-
defaultSelect: () => this._list(
|
|
229
|
+
defaultSelect: () => this._list(normalizedPath, namespace, options)
|
|
51
230
|
});
|
|
52
231
|
}
|
|
53
|
-
async _list(path, options = {}) {
|
|
232
|
+
async _list(path, namespace, options = {}) {
|
|
54
233
|
const results = [];
|
|
55
|
-
|
|
234
|
+
const hasModulesMounts = namespace === null && [...this.mounts.values()].some((m) => m.namespace === null && m.path.startsWith(`${MODULES_ROOT_DIR}/`));
|
|
235
|
+
if (path === "/" && hasModulesMounts) {
|
|
56
236
|
const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
57
237
|
results.push({
|
|
58
238
|
id: "modules",
|
|
59
239
|
path: MODULES_ROOT_DIR,
|
|
60
|
-
summary: "All mounted modules"
|
|
240
|
+
summary: "All mounted modules",
|
|
241
|
+
metadata: {
|
|
242
|
+
type: "directory",
|
|
243
|
+
description: "All mounted modules"
|
|
244
|
+
}
|
|
61
245
|
});
|
|
62
246
|
if (maxDepth === 1) return { data: results };
|
|
63
|
-
const childrenResult = await this._list(MODULES_ROOT_DIR, {
|
|
247
|
+
const childrenResult = await this._list(MODULES_ROOT_DIR, namespace, {
|
|
64
248
|
...options,
|
|
65
249
|
maxDepth: maxDepth - 1
|
|
66
250
|
});
|
|
67
251
|
results.push(...childrenResult.data);
|
|
68
252
|
return { data: results };
|
|
69
253
|
}
|
|
70
|
-
const matches = this.
|
|
254
|
+
const matches = this.findModulesInNamespace(path, namespace, options);
|
|
255
|
+
if (matches.length === 0) return { data: results };
|
|
71
256
|
for (const matched of matches) {
|
|
72
257
|
if (matched.maxDepth === 0) {
|
|
73
258
|
const moduleEntry = {
|
|
74
259
|
id: matched.module.name,
|
|
75
260
|
path: matched.modulePath,
|
|
76
|
-
summary: matched.module.description
|
|
261
|
+
summary: matched.module.description,
|
|
262
|
+
metadata: {
|
|
263
|
+
type: "module",
|
|
264
|
+
description: matched.module.description
|
|
265
|
+
}
|
|
77
266
|
};
|
|
78
267
|
results.push(moduleEntry);
|
|
79
268
|
continue;
|
|
@@ -96,7 +285,8 @@ var AFS = class extends strict_event_emitter.Emitter {
|
|
|
96
285
|
return { data: results };
|
|
97
286
|
}
|
|
98
287
|
async read(path, _options) {
|
|
99
|
-
const
|
|
288
|
+
const { namespace, path: normalizedPath } = this.parsePathWithNamespace(path);
|
|
289
|
+
const modules = this.findModulesInNamespace(normalizedPath, namespace, { exactMatch: true });
|
|
100
290
|
for (const { module, modulePath, subpath } of modules) {
|
|
101
291
|
const res = await module.read?.(subpath);
|
|
102
292
|
if (res?.data) return {
|
|
@@ -113,8 +303,9 @@ var AFS = class extends strict_event_emitter.Emitter {
|
|
|
113
303
|
};
|
|
114
304
|
}
|
|
115
305
|
async write(path, content, options) {
|
|
116
|
-
const
|
|
117
|
-
|
|
306
|
+
const { namespace, path: normalizedPath } = this.parsePathWithNamespace(path);
|
|
307
|
+
const module = this.findModulesInNamespace(normalizedPath, namespace, { exactMatch: true })[0];
|
|
308
|
+
if (!module?.module.write) throw new Error(`No module found for path: ${normalizedPath} in namespace '${namespace ?? "default"}'`);
|
|
118
309
|
this.checkWritePermission(module.module, "write", path);
|
|
119
310
|
const res = await module.module.write(module.subpath, content, options);
|
|
120
311
|
return {
|
|
@@ -126,28 +317,33 @@ var AFS = class extends strict_event_emitter.Emitter {
|
|
|
126
317
|
};
|
|
127
318
|
}
|
|
128
319
|
async delete(path, options) {
|
|
129
|
-
const
|
|
130
|
-
|
|
320
|
+
const { namespace, path: normalizedPath } = this.parsePathWithNamespace(path);
|
|
321
|
+
const module = this.findModulesInNamespace(normalizedPath, namespace, { exactMatch: true })[0];
|
|
322
|
+
if (!module?.module.delete) throw new Error(`No module found for path: ${normalizedPath} in namespace '${namespace ?? "default"}'`);
|
|
131
323
|
this.checkWritePermission(module.module, "delete", path);
|
|
132
324
|
return await module.module.delete(module.subpath, options);
|
|
133
325
|
}
|
|
134
326
|
async rename(oldPath, newPath, options) {
|
|
135
|
-
const
|
|
136
|
-
const
|
|
327
|
+
const { namespace: oldNamespace, path: normalizedOldPath } = this.parsePathWithNamespace(oldPath);
|
|
328
|
+
const { namespace: newNamespace, path: normalizedNewPath } = this.parsePathWithNamespace(newPath);
|
|
329
|
+
if (oldNamespace !== newNamespace) throw new Error(`Cannot rename across different namespaces.`);
|
|
330
|
+
const oldModule = this.findModulesInNamespace(normalizedOldPath, oldNamespace, { exactMatch: true })[0];
|
|
331
|
+
const newModule = this.findModulesInNamespace(normalizedNewPath, newNamespace, { exactMatch: true })[0];
|
|
137
332
|
if (!oldModule || !newModule || oldModule.modulePath !== newModule.modulePath) throw new Error(`Cannot rename across different modules. Both paths must be in the same module.`);
|
|
138
333
|
if (!oldModule.module.rename) throw new Error(`Module does not support rename operation: ${oldModule.modulePath}`);
|
|
139
334
|
this.checkWritePermission(oldModule.module, "rename", oldPath);
|
|
140
335
|
return await oldModule.module.rename(oldModule.subpath, newModule.subpath, options);
|
|
141
336
|
}
|
|
142
337
|
async search(path, query, options = {}) {
|
|
338
|
+
const { namespace, path: normalizedPath } = this.parsePathWithNamespace(path);
|
|
143
339
|
let preset;
|
|
144
340
|
if (options.preset) {
|
|
145
341
|
preset = this.options?.context?.search?.presets?.[options.preset];
|
|
146
342
|
if (!preset) throw new Error(`Preset not found: ${options.preset}`);
|
|
147
343
|
}
|
|
148
|
-
return await this.processWithPreset(
|
|
344
|
+
return await this.processWithPreset(normalizedPath, query, preset, {
|
|
149
345
|
...options,
|
|
150
|
-
defaultSelect: () => this._search(
|
|
346
|
+
defaultSelect: () => this._search(normalizedPath, namespace, query, options)
|
|
151
347
|
});
|
|
152
348
|
}
|
|
153
349
|
async processWithPreset(path, query, preset, options) {
|
|
@@ -174,10 +370,10 @@ var AFS = class extends strict_event_emitter.Emitter {
|
|
|
174
370
|
}, options);
|
|
175
371
|
return { data: (await Promise.all(data.map((p) => this.read(p).then((res) => res.data)))).filter((i) => !!i) };
|
|
176
372
|
}
|
|
177
|
-
async _search(path, query, options) {
|
|
373
|
+
async _search(path, namespace, query, options) {
|
|
178
374
|
const results = [];
|
|
179
375
|
const messages = [];
|
|
180
|
-
for (const { module, modulePath, subpath } of this.
|
|
376
|
+
for (const { module, modulePath, subpath } of this.findModulesInNamespace(path, namespace)) {
|
|
181
377
|
if (!module.search) continue;
|
|
182
378
|
try {
|
|
183
379
|
const { data, message } = await module.search(subpath, query, options);
|
|
@@ -195,38 +391,10 @@ var AFS = class extends strict_event_emitter.Emitter {
|
|
|
195
391
|
message: messages.join("; ")
|
|
196
392
|
};
|
|
197
393
|
}
|
|
198
|
-
findModules(path, options) {
|
|
199
|
-
const maxDepth = Math.max(options?.maxDepth ?? DEFAULT_MAX_DEPTH, 1);
|
|
200
|
-
const matched = [];
|
|
201
|
-
for (const [modulePath, module] of this.modules) {
|
|
202
|
-
const pathSegments = path.split("/").filter(Boolean);
|
|
203
|
-
const modulePathSegments = modulePath.split("/").filter(Boolean);
|
|
204
|
-
let newMaxDepth;
|
|
205
|
-
let subpath;
|
|
206
|
-
let remainedModulePath;
|
|
207
|
-
if (!options?.exactMatch && modulePath.startsWith(path)) {
|
|
208
|
-
newMaxDepth = Math.max(0, maxDepth - (modulePathSegments.length - pathSegments.length));
|
|
209
|
-
subpath = "/";
|
|
210
|
-
remainedModulePath = (0, ufo.joinURL)("/", ...modulePathSegments.slice(pathSegments.length).slice(0, maxDepth));
|
|
211
|
-
} else if (path.startsWith(modulePath)) {
|
|
212
|
-
newMaxDepth = maxDepth;
|
|
213
|
-
subpath = (0, ufo.joinURL)("/", ...pathSegments.slice(modulePathSegments.length));
|
|
214
|
-
remainedModulePath = "/";
|
|
215
|
-
} else continue;
|
|
216
|
-
if (newMaxDepth < 0) continue;
|
|
217
|
-
matched.push({
|
|
218
|
-
module,
|
|
219
|
-
modulePath,
|
|
220
|
-
maxDepth: newMaxDepth,
|
|
221
|
-
subpath,
|
|
222
|
-
remainedModulePath
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
return matched;
|
|
226
|
-
}
|
|
227
394
|
async exec(path, args, options) {
|
|
228
|
-
const
|
|
229
|
-
|
|
395
|
+
const { namespace, path: normalizedPath } = this.parsePathWithNamespace(path);
|
|
396
|
+
const module = this.findModulesInNamespace(normalizedPath, namespace)[0];
|
|
397
|
+
if (!module?.module.exec) throw new Error(`No module found for path: ${normalizedPath} in namespace '${namespace ?? "default"}'`);
|
|
230
398
|
return await module.module.exec(module.subpath, args, options);
|
|
231
399
|
}
|
|
232
400
|
buildSimpleListView(entries) {
|
|
@@ -276,10 +444,11 @@ var AFS = class extends strict_event_emitter.Emitter {
|
|
|
276
444
|
const fs = await import("node:fs/promises");
|
|
277
445
|
const rootDir = path.join(os.tmpdir(), (0, _aigne_uuid.v7)());
|
|
278
446
|
await fs.mkdir(rootDir, { recursive: true });
|
|
279
|
-
for (const
|
|
280
|
-
const
|
|
447
|
+
for (const entry of this.mounts.values()) {
|
|
448
|
+
const namespacePart = entry.namespace ?? "_default";
|
|
449
|
+
const physicalModulePath = path.join(rootDir, namespacePart, entry.path);
|
|
281
450
|
await fs.mkdir(path.dirname(physicalModulePath), { recursive: true });
|
|
282
|
-
await module.symlinkToPhysical?.(physicalModulePath);
|
|
451
|
+
await entry.module.symlinkToPhysical?.(physicalModulePath);
|
|
283
452
|
}
|
|
284
453
|
return rootDir;
|
|
285
454
|
})();
|
package/dist/afs.d.cts
CHANGED
|
@@ -2,6 +2,26 @@ import { AFSContext, AFSDeleteOptions, AFSDeleteResult, AFSExecOptions, AFSExecR
|
|
|
2
2
|
import { Emitter } from "strict-event-emitter";
|
|
3
3
|
|
|
4
4
|
//#region src/afs.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Information about a mounted module
|
|
7
|
+
*/
|
|
8
|
+
interface MountInfo {
|
|
9
|
+
/** The namespace (null for default namespace) */
|
|
10
|
+
namespace: string | null;
|
|
11
|
+
/** The mount path within the namespace */
|
|
12
|
+
path: string;
|
|
13
|
+
/** The mounted module */
|
|
14
|
+
module: AFSModule;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Options for mounting a module
|
|
18
|
+
*/
|
|
19
|
+
interface MountOptions {
|
|
20
|
+
/** Namespace to mount into (null/undefined for default namespace) */
|
|
21
|
+
namespace?: string | null;
|
|
22
|
+
/** Replace existing mount at the same path */
|
|
23
|
+
replace?: boolean;
|
|
24
|
+
}
|
|
5
25
|
interface AFSOptions {
|
|
6
26
|
modules?: AFSModule[];
|
|
7
27
|
context?: AFSContext;
|
|
@@ -10,19 +30,74 @@ declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
|
10
30
|
options: AFSOptions;
|
|
11
31
|
name: string;
|
|
12
32
|
constructor(options?: AFSOptions);
|
|
13
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Internal storage: Map<compositeKey, MountEntry>
|
|
35
|
+
* compositeKey = `${namespace ?? ""}:${path}`
|
|
36
|
+
*/
|
|
37
|
+
private mounts;
|
|
38
|
+
/**
|
|
39
|
+
* Legacy compatibility: Map<path, AFSModule> for modules mounted via old API
|
|
40
|
+
* This is used internally by findModules for backward compatibility
|
|
41
|
+
*/
|
|
42
|
+
private get modules();
|
|
43
|
+
/**
|
|
44
|
+
* Create composite key for mount storage
|
|
45
|
+
*/
|
|
46
|
+
private makeKey;
|
|
14
47
|
/**
|
|
15
48
|
* Check if write operations are allowed for the given module.
|
|
16
49
|
* Throws AFSReadonlyError if not allowed.
|
|
17
50
|
*/
|
|
18
51
|
private checkWritePermission;
|
|
19
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Mount a module at a path in a namespace
|
|
54
|
+
*
|
|
55
|
+
* @param module - The module to mount
|
|
56
|
+
* @param path - The path to mount at (optional, defaults to /modules/{module.name} for backward compatibility)
|
|
57
|
+
* @param options - Mount options (namespace, replace)
|
|
58
|
+
*/
|
|
59
|
+
mount(module: AFSModule, path?: string, options?: MountOptions): this;
|
|
60
|
+
/**
|
|
61
|
+
* Get all mounts, optionally filtered by namespace
|
|
62
|
+
*
|
|
63
|
+
* @param namespace - Filter by namespace (undefined = all, null = default only)
|
|
64
|
+
*/
|
|
65
|
+
getMounts(namespace?: string | null): MountInfo[];
|
|
66
|
+
/**
|
|
67
|
+
* Get all unique namespaces that have mounts
|
|
68
|
+
*/
|
|
69
|
+
getNamespaces(): (string | null)[];
|
|
70
|
+
/**
|
|
71
|
+
* Unmount a module at a path in a namespace
|
|
72
|
+
*
|
|
73
|
+
* @param path - The path to unmount
|
|
74
|
+
* @param namespace - The namespace (undefined/null for default namespace)
|
|
75
|
+
* @returns true if unmounted, false if not found
|
|
76
|
+
*/
|
|
77
|
+
unmount(path: string, namespace?: string | null): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Check if a path is mounted in a namespace
|
|
80
|
+
*
|
|
81
|
+
* @param path - The path to check
|
|
82
|
+
* @param namespace - The namespace (undefined/null for default namespace)
|
|
83
|
+
*/
|
|
84
|
+
isMounted(path: string, namespace?: string | null): boolean;
|
|
20
85
|
listModules(): Promise<{
|
|
21
86
|
name: string;
|
|
22
87
|
path: string;
|
|
88
|
+
namespace: string | null;
|
|
23
89
|
description?: string;
|
|
24
90
|
module: AFSModule;
|
|
25
91
|
}[]>;
|
|
92
|
+
/**
|
|
93
|
+
* Parse a path and extract namespace if it's a canonical path
|
|
94
|
+
* Returns the namespace and the path within the namespace
|
|
95
|
+
*/
|
|
96
|
+
private parsePathWithNamespace;
|
|
97
|
+
/**
|
|
98
|
+
* Find modules that can handle a path in a specific namespace
|
|
99
|
+
*/
|
|
100
|
+
private findModulesInNamespace;
|
|
26
101
|
list(path: string, options?: AFSRootListOptions): Promise<AFSRootListResult>;
|
|
27
102
|
private _list;
|
|
28
103
|
read(path: string, _options?: AFSReadOptions): Promise<AFSReadResult>;
|
|
@@ -33,7 +108,6 @@ declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
|
33
108
|
private processWithPreset;
|
|
34
109
|
private _select;
|
|
35
110
|
private _search;
|
|
36
|
-
private findModules;
|
|
37
111
|
exec(path: string, args: Record<string, any>, options: AFSExecOptions): Promise<AFSExecResult>;
|
|
38
112
|
private buildSimpleListView;
|
|
39
113
|
private buildTreeView;
|
|
@@ -43,5 +117,5 @@ declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
|
43
117
|
cleanupPhysicalPath(): Promise<void>;
|
|
44
118
|
}
|
|
45
119
|
//#endregion
|
|
46
|
-
export { AFS, AFSOptions };
|
|
120
|
+
export { AFS, AFSOptions, MountInfo, MountOptions };
|
|
47
121
|
//# sourceMappingURL=afs.d.cts.map
|
package/dist/afs.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"afs.d.cts","names":[],"sources":["../src/afs.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"afs.d.cts","names":[],"sources":["../src/afs.ts"],"mappings":";;;;;AA+EA;AAYA;UAZiB,SAAA;EAAA;EAAA,SAAA;EAAA;EAAA,IAAA;EAAA;EAAA,MAAA,EAMP,SAAA;AAAA;AAAA;AAMV;AAgBA;AAtBU,UAMO,YAAA;EAAA;EAAA,SAAA;EAAA;EAAA,OAAA;AAAA;AAAA,UAgBA,UAAA;EAAA,OAAA,GACL,SAAA;EAAA,OAAA,GACA,UAAA;AAAA;AAAA,cAGC,GAAA,SAAY,OAAA,CAAQ,aAAA,aAA0B,OAAA;EAAA,OAAA,EAG7B,UAAA;EAAA,IAAA;EAAA,YAAA,OAAA,GAAA,UAAA;EAAA;;;;EAAA,QAAA,MAAA;EAAA;;;;EAAA,YAAA,QAAA;EAAA;;;EAAA,QAAA,OAAA;EAAA;;;;EAAA,QAAA,oBAAA;EAAA;;;;;;;EAAA,MAAA,MAAA,EAwDd,SAAA,EAAA,IAAA,WAAA,OAAA,GAAoC,YAAA;EAAA;;;;;EAAA,UAAA,SAAA,mBAyFZ,SAAA;EAAA;;;EAAA,cAAA;EAAA;;;;;;;EAAA,QAAA,IAAA,UAAA,SAAA;EAAA;;;;;;EAAA,UAAA,IAAA,UAAA,SAAA;EAAA,YAAA,GA2DjB,OAAA;IAAA,IAAA;IAAA,IAAA;IAAA,SAAA;IAAA,WAAA;IAAA,MAAA,EAMT,SAAA;EAAA;EAAA;;;;EAAA,QAAA,sBAAA;EAAA;;;EAAA,QAAA,sBAAA;EAAA,KAAA,IAAA,UAAA,OAAA,GAqFsB,kBAAA,GAA0B,OAAA,CAAQ,iBAAA;EAAA,QAAA,KAAA;EAAA,KAAA,IAAA,UAAA,QAAA,GA6GhC,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAA,MAAA,IAAA,UAAA,OAAA,EAyBlD,oBAAA,EAAA,OAAA,GACC,eAAA,GACT,OAAA,CAAQ,cAAA;EAAA,OAAA,IAAA,UAAA,OAAA,GAuB0B,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAAA,OAAA,OAAA,UAAA,OAAA,UAAA,OAAA,GAkBpD,gBAAA,GACT,OAAA,CAAQ,eAAA;EAAA,OAAA,IAAA,UAAA,KAAA,UAAA,OAAA,GAsCA,oBAAA,GACR,OAAA,CAAQ,mBAAA;EAAA,QAAA,iBAAA;EAAA,QAAA,OAAA;EAAA,QAAA,OAAA;EAAA,KAAA,IAAA,UAAA,IAAA,EAsGH,MAAA,eAAA,OAAA,EACG,cAAA,GACR,OAAA,CAAQ,aAAA;EAAA,QAAA,mBAAA;EAAA,QAAA,aAAA;EAAA,QAAA,mBAAA;EAAA,QAAA,YAAA;EAAA,uBAAA,GAoFqB,OAAA;EAAA,oBAAA,GAuBH,OAAA;AAAA"}
|
package/dist/afs.d.mts
CHANGED
|
@@ -2,6 +2,26 @@ import { AFSContext, AFSDeleteOptions, AFSDeleteResult, AFSExecOptions, AFSExecR
|
|
|
2
2
|
import { Emitter } from "strict-event-emitter";
|
|
3
3
|
|
|
4
4
|
//#region src/afs.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Information about a mounted module
|
|
7
|
+
*/
|
|
8
|
+
interface MountInfo {
|
|
9
|
+
/** The namespace (null for default namespace) */
|
|
10
|
+
namespace: string | null;
|
|
11
|
+
/** The mount path within the namespace */
|
|
12
|
+
path: string;
|
|
13
|
+
/** The mounted module */
|
|
14
|
+
module: AFSModule;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Options for mounting a module
|
|
18
|
+
*/
|
|
19
|
+
interface MountOptions {
|
|
20
|
+
/** Namespace to mount into (null/undefined for default namespace) */
|
|
21
|
+
namespace?: string | null;
|
|
22
|
+
/** Replace existing mount at the same path */
|
|
23
|
+
replace?: boolean;
|
|
24
|
+
}
|
|
5
25
|
interface AFSOptions {
|
|
6
26
|
modules?: AFSModule[];
|
|
7
27
|
context?: AFSContext;
|
|
@@ -10,19 +30,74 @@ declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
|
10
30
|
options: AFSOptions;
|
|
11
31
|
name: string;
|
|
12
32
|
constructor(options?: AFSOptions);
|
|
13
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Internal storage: Map<compositeKey, MountEntry>
|
|
35
|
+
* compositeKey = `${namespace ?? ""}:${path}`
|
|
36
|
+
*/
|
|
37
|
+
private mounts;
|
|
38
|
+
/**
|
|
39
|
+
* Legacy compatibility: Map<path, AFSModule> for modules mounted via old API
|
|
40
|
+
* This is used internally by findModules for backward compatibility
|
|
41
|
+
*/
|
|
42
|
+
private get modules();
|
|
43
|
+
/**
|
|
44
|
+
* Create composite key for mount storage
|
|
45
|
+
*/
|
|
46
|
+
private makeKey;
|
|
14
47
|
/**
|
|
15
48
|
* Check if write operations are allowed for the given module.
|
|
16
49
|
* Throws AFSReadonlyError if not allowed.
|
|
17
50
|
*/
|
|
18
51
|
private checkWritePermission;
|
|
19
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Mount a module at a path in a namespace
|
|
54
|
+
*
|
|
55
|
+
* @param module - The module to mount
|
|
56
|
+
* @param path - The path to mount at (optional, defaults to /modules/{module.name} for backward compatibility)
|
|
57
|
+
* @param options - Mount options (namespace, replace)
|
|
58
|
+
*/
|
|
59
|
+
mount(module: AFSModule, path?: string, options?: MountOptions): this;
|
|
60
|
+
/**
|
|
61
|
+
* Get all mounts, optionally filtered by namespace
|
|
62
|
+
*
|
|
63
|
+
* @param namespace - Filter by namespace (undefined = all, null = default only)
|
|
64
|
+
*/
|
|
65
|
+
getMounts(namespace?: string | null): MountInfo[];
|
|
66
|
+
/**
|
|
67
|
+
* Get all unique namespaces that have mounts
|
|
68
|
+
*/
|
|
69
|
+
getNamespaces(): (string | null)[];
|
|
70
|
+
/**
|
|
71
|
+
* Unmount a module at a path in a namespace
|
|
72
|
+
*
|
|
73
|
+
* @param path - The path to unmount
|
|
74
|
+
* @param namespace - The namespace (undefined/null for default namespace)
|
|
75
|
+
* @returns true if unmounted, false if not found
|
|
76
|
+
*/
|
|
77
|
+
unmount(path: string, namespace?: string | null): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Check if a path is mounted in a namespace
|
|
80
|
+
*
|
|
81
|
+
* @param path - The path to check
|
|
82
|
+
* @param namespace - The namespace (undefined/null for default namespace)
|
|
83
|
+
*/
|
|
84
|
+
isMounted(path: string, namespace?: string | null): boolean;
|
|
20
85
|
listModules(): Promise<{
|
|
21
86
|
name: string;
|
|
22
87
|
path: string;
|
|
88
|
+
namespace: string | null;
|
|
23
89
|
description?: string;
|
|
24
90
|
module: AFSModule;
|
|
25
91
|
}[]>;
|
|
92
|
+
/**
|
|
93
|
+
* Parse a path and extract namespace if it's a canonical path
|
|
94
|
+
* Returns the namespace and the path within the namespace
|
|
95
|
+
*/
|
|
96
|
+
private parsePathWithNamespace;
|
|
97
|
+
/**
|
|
98
|
+
* Find modules that can handle a path in a specific namespace
|
|
99
|
+
*/
|
|
100
|
+
private findModulesInNamespace;
|
|
26
101
|
list(path: string, options?: AFSRootListOptions): Promise<AFSRootListResult>;
|
|
27
102
|
private _list;
|
|
28
103
|
read(path: string, _options?: AFSReadOptions): Promise<AFSReadResult>;
|
|
@@ -33,7 +108,6 @@ declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
|
33
108
|
private processWithPreset;
|
|
34
109
|
private _select;
|
|
35
110
|
private _search;
|
|
36
|
-
private findModules;
|
|
37
111
|
exec(path: string, args: Record<string, any>, options: AFSExecOptions): Promise<AFSExecResult>;
|
|
38
112
|
private buildSimpleListView;
|
|
39
113
|
private buildTreeView;
|
|
@@ -43,5 +117,5 @@ declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
|
|
|
43
117
|
cleanupPhysicalPath(): Promise<void>;
|
|
44
118
|
}
|
|
45
119
|
//#endregion
|
|
46
|
-
export { AFS, AFSOptions };
|
|
120
|
+
export { AFS, AFSOptions, MountInfo, MountOptions };
|
|
47
121
|
//# sourceMappingURL=afs.d.mts.map
|
package/dist/afs.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"afs.d.mts","names":[],"sources":["../src/afs.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"afs.d.mts","names":[],"sources":["../src/afs.ts"],"mappings":";;;;;AA+EA;AAYA;UAZiB,SAAA;EAAA;EAAA,SAAA;EAAA;EAAA,IAAA;EAAA;EAAA,MAAA,EAMP,SAAA;AAAA;AAAA;AAMV;AAgBA;AAtBU,UAMO,YAAA;EAAA;EAAA,SAAA;EAAA;EAAA,OAAA;AAAA;AAAA,UAgBA,UAAA;EAAA,OAAA,GACL,SAAA;EAAA,OAAA,GACA,UAAA;AAAA;AAAA,cAGC,GAAA,SAAY,OAAA,CAAQ,aAAA,aAA0B,OAAA;EAAA,OAAA,EAG7B,UAAA;EAAA,IAAA;EAAA,YAAA,OAAA,GAAA,UAAA;EAAA;;;;EAAA,QAAA,MAAA;EAAA;;;;EAAA,YAAA,QAAA;EAAA;;;EAAA,QAAA,OAAA;EAAA;;;;EAAA,QAAA,oBAAA;EAAA;;;;;;;EAAA,MAAA,MAAA,EAwDd,SAAA,EAAA,IAAA,WAAA,OAAA,GAAoC,YAAA;EAAA;;;;;EAAA,UAAA,SAAA,mBAyFZ,SAAA;EAAA;;;EAAA,cAAA;EAAA;;;;;;;EAAA,QAAA,IAAA,UAAA,SAAA;EAAA;;;;;;EAAA,UAAA,IAAA,UAAA,SAAA;EAAA,YAAA,GA2DjB,OAAA;IAAA,IAAA;IAAA,IAAA;IAAA,SAAA;IAAA,WAAA;IAAA,MAAA,EAMT,SAAA;EAAA;EAAA;;;;EAAA,QAAA,sBAAA;EAAA;;;EAAA,QAAA,sBAAA;EAAA,KAAA,IAAA,UAAA,OAAA,GAqFsB,kBAAA,GAA0B,OAAA,CAAQ,iBAAA;EAAA,QAAA,KAAA;EAAA,KAAA,IAAA,UAAA,QAAA,GA6GhC,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAA,MAAA,IAAA,UAAA,OAAA,EAyBlD,oBAAA,EAAA,OAAA,GACC,eAAA,GACT,OAAA,CAAQ,cAAA;EAAA,OAAA,IAAA,UAAA,OAAA,GAuB0B,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAAA,OAAA,OAAA,UAAA,OAAA,UAAA,OAAA,GAkBpD,gBAAA,GACT,OAAA,CAAQ,eAAA;EAAA,OAAA,IAAA,UAAA,KAAA,UAAA,OAAA,GAsCA,oBAAA,GACR,OAAA,CAAQ,mBAAA;EAAA,QAAA,iBAAA;EAAA,QAAA,OAAA;EAAA,QAAA,OAAA;EAAA,KAAA,IAAA,UAAA,IAAA,EAsGH,MAAA,eAAA,OAAA,EACG,cAAA,GACR,OAAA,CAAQ,aAAA;EAAA,QAAA,mBAAA;EAAA,QAAA,aAAA;EAAA,QAAA,mBAAA;EAAA,QAAA,YAAA;EAAA,uBAAA,GAoFqB,OAAA;EAAA,oBAAA,GAuBH,OAAA;AAAA"}
|