@aigne/afs 1.1.0 → 1.1.1

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.1](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.1.1-beta...afs-v1.1.1) (2025-10-31)
4
+
5
+ ## [1.1.1-beta](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.1.0...afs-v1.1.1-beta) (2025-10-23)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **afs:** improve module path resolution and depth handling ([#659](https://github.com/AIGNE-io/aigne-framework/issues/659)) ([c609d4f](https://github.com/AIGNE-io/aigne-framework/commit/c609d4fc9614123afcf4b8f86b3382a613ace417))
11
+
3
12
  ## [1.1.0](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.1.0-beta...afs-v1.1.0) (2025-10-19)
4
13
 
5
14
 
package/lib/cjs/afs.d.ts CHANGED
@@ -23,8 +23,6 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
23
23
  list: AFSEntry[];
24
24
  message?: string;
25
25
  }>;
26
- private findModules;
27
- private isSubpath;
28
26
  read(path: string): Promise<{
29
27
  result?: AFSEntry;
30
28
  message?: string;
@@ -37,4 +35,5 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
37
35
  list: AFSEntry[];
38
36
  message?: string;
39
37
  }>;
38
+ private findModules;
40
39
  }
package/lib/cjs/afs.js CHANGED
@@ -5,7 +5,7 @@ const strict_event_emitter_1 = require("strict-event-emitter");
5
5
  const ufo_1 = require("ufo");
6
6
  const index_js_1 = require("./history/index.js");
7
7
  const index_js_2 = require("./storage/index.js");
8
- const DEFAULT_MAX_DEPTH = 5;
8
+ const DEFAULT_MAX_DEPTH = 1;
9
9
  class AFS extends strict_event_emitter_1.Emitter {
10
10
  moduleId = "AFSRoot";
11
11
  path = "/";
@@ -43,66 +43,52 @@ class AFS extends strict_event_emitter_1.Emitter {
43
43
  throw new Error(`Invalid maxDepth: ${maxDepth}`);
44
44
  const results = [];
45
45
  const messages = [];
46
- const modules = this.findModules(path);
47
- for (const { module, subpath, mountPath } of modules) {
48
- if (!module.list)
46
+ const matches = this.findModules(path, options);
47
+ for (const matched of matches) {
48
+ const moduleEntry = {
49
+ id: matched.module.moduleId,
50
+ path: matched.remainedModulePath,
51
+ summary: matched.module.description,
52
+ };
53
+ if (matched.maxDepth === 0) {
54
+ results.push(moduleEntry);
55
+ continue;
56
+ }
57
+ if (!matched.module.list)
49
58
  continue;
50
59
  try {
51
- const newMaxDepth = maxDepth - mountPath.split("/").filter(Boolean).length;
52
- if (newMaxDepth < 0)
53
- continue;
54
- const { list, message } = await module.list(subpath, { ...options, maxDepth: newMaxDepth });
55
- results.push(...list.map((entry) => ({
56
- ...entry,
57
- path: (0, ufo_1.joinURL)(mountPath, entry.path),
58
- })));
60
+ const { list, message } = await matched.module.list(matched.subpath, {
61
+ ...options,
62
+ maxDepth: matched.maxDepth,
63
+ });
64
+ if (list.length) {
65
+ results.push(...list.map((entry) => ({
66
+ ...entry,
67
+ path: (0, ufo_1.joinURL)(matched.module.path, entry.path),
68
+ })));
69
+ }
70
+ else {
71
+ results.push(moduleEntry);
72
+ }
59
73
  if (message)
60
74
  messages.push(message);
61
75
  }
62
76
  catch (error) {
63
- console.error(`Error listing from module at ${mountPath}`, error);
77
+ console.error(`Error listing from module at ${matched.module.path}`, error);
64
78
  }
65
79
  }
66
- return { list: results, message: messages.join("; ") };
67
- }
68
- findModules(fullPath) {
69
- const modules = [];
70
- for (const [mountPath, module] of this.modules) {
71
- const match = this.isSubpath(fullPath, mountPath);
72
- if (!match)
73
- continue;
74
- modules.push({ ...match, module, mountPath });
75
- }
76
- return modules.sort((a, b) => b.matchedDepth - a.matchedDepth);
77
- }
78
- isSubpath(fullPath, mountPath) {
79
- const fullPathSegments = fullPath.split("/").filter(Boolean);
80
- const mountPathSegments = mountPath.split("/").filter(Boolean);
81
- const fp = fullPathSegments.join("/");
82
- const mp = mountPathSegments.join("/");
83
- if (fp.startsWith(mp)) {
84
- return {
85
- matchedDepth: mountPathSegments.length,
86
- subpath: (0, ufo_1.joinURL)("/", ...fullPathSegments.slice(mountPathSegments.length)),
87
- };
88
- }
89
- else if (mp.startsWith(fp)) {
90
- return {
91
- matchedDepth: fullPathSegments.length,
92
- subpath: "/",
93
- };
94
- }
80
+ return { list: results, message: messages.join("; ").trim() || undefined };
95
81
  }
96
82
  async read(path) {
97
- const modules = this.findModules(path);
98
- for (const { module, mountPath, subpath } of modules) {
83
+ const modules = this.findModules(path, { exactMatch: true });
84
+ for (const { module, subpath } of modules) {
99
85
  const res = await module.read?.(subpath);
100
86
  if (res?.result) {
101
87
  return {
102
88
  ...res,
103
89
  result: {
104
90
  ...res.result,
105
- path: (0, ufo_1.joinURL)(mountPath, res.result.path),
91
+ path: (0, ufo_1.joinURL)(module.path, res.result.path),
106
92
  },
107
93
  };
108
94
  }
@@ -110,7 +96,7 @@ class AFS extends strict_event_emitter_1.Emitter {
110
96
  return { result: undefined, message: "File not found" };
111
97
  }
112
98
  async write(path, content) {
113
- const module = this.findModules(path)[0];
99
+ const module = this.findModules(path, { exactMatch: true })[0];
114
100
  if (!module?.module.write)
115
101
  throw new Error(`No module found for path: ${path}`);
116
102
  const res = await module.module.write(module.subpath, content);
@@ -118,32 +104,58 @@ class AFS extends strict_event_emitter_1.Emitter {
118
104
  ...res,
119
105
  result: {
120
106
  ...res.result,
121
- path: (0, ufo_1.joinURL)(module.mountPath, res.result.path),
107
+ path: (0, ufo_1.joinURL)(module.module.path, res.result.path),
122
108
  },
123
109
  };
124
110
  }
125
111
  async search(path, query, options) {
126
112
  const results = [];
127
113
  const messages = [];
128
- for (const { module, mountPath, subpath } of this.findModules(path)) {
129
- if (mountPath.startsWith(path)) {
130
- if (!module.search)
131
- continue;
132
- try {
133
- const { list, message } = await module.search(subpath, query, options);
134
- results.push(...list.map((entry) => ({
135
- ...entry,
136
- path: (0, ufo_1.joinURL)(mountPath, entry.path),
137
- })));
138
- if (message)
139
- messages.push(message);
140
- }
141
- catch (error) {
142
- console.error(`Error searching in module at ${mountPath}`, error);
143
- }
114
+ for (const { module, subpath } of this.findModules(path)) {
115
+ if (!module.search)
116
+ continue;
117
+ try {
118
+ const { list, message } = await module.search(subpath, query, options);
119
+ results.push(...list.map((entry) => ({
120
+ ...entry,
121
+ path: (0, ufo_1.joinURL)(module.path, entry.path),
122
+ })));
123
+ if (message)
124
+ messages.push(message);
125
+ }
126
+ catch (error) {
127
+ console.error(`Error searching in module at ${module.path}`, error);
144
128
  }
145
129
  }
146
130
  return { list: results, message: messages.join("; ") };
147
131
  }
132
+ findModules(path, options) {
133
+ const maxDepth = Math.max(options?.maxDepth ?? DEFAULT_MAX_DEPTH, 1);
134
+ const matched = [];
135
+ for (const [modulePath, module] of this.modules) {
136
+ const pathSegments = path.split("/").filter(Boolean);
137
+ const modulePathSegments = modulePath.split("/").filter(Boolean);
138
+ let newMaxDepth;
139
+ let subpath;
140
+ let remainedModulePath;
141
+ if (!options?.exactMatch && modulePath.startsWith(path)) {
142
+ newMaxDepth = Math.max(0, maxDepth - (modulePathSegments.length - pathSegments.length));
143
+ subpath = "/";
144
+ remainedModulePath = (0, ufo_1.joinURL)("/", ...modulePathSegments.slice(pathSegments.length).slice(0, maxDepth));
145
+ }
146
+ else if (path.startsWith(modulePath)) {
147
+ newMaxDepth = maxDepth;
148
+ subpath = (0, ufo_1.joinURL)("/", ...pathSegments.slice(modulePathSegments.length));
149
+ remainedModulePath = "/";
150
+ }
151
+ else {
152
+ continue;
153
+ }
154
+ if (newMaxDepth < 0)
155
+ continue;
156
+ matched.push({ module, maxDepth: newMaxDepth, subpath, remainedModulePath });
157
+ }
158
+ return matched;
159
+ }
148
160
  }
149
161
  exports.AFS = AFS;
package/lib/dts/afs.d.ts CHANGED
@@ -23,8 +23,6 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
23
23
  list: AFSEntry[];
24
24
  message?: string;
25
25
  }>;
26
- private findModules;
27
- private isSubpath;
28
26
  read(path: string): Promise<{
29
27
  result?: AFSEntry;
30
28
  message?: string;
@@ -37,4 +35,5 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
37
35
  list: AFSEntry[];
38
36
  message?: string;
39
37
  }>;
38
+ private findModules;
40
39
  }
package/lib/esm/afs.d.ts CHANGED
@@ -23,8 +23,6 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
23
23
  list: AFSEntry[];
24
24
  message?: string;
25
25
  }>;
26
- private findModules;
27
- private isSubpath;
28
26
  read(path: string): Promise<{
29
27
  result?: AFSEntry;
30
28
  message?: string;
@@ -37,4 +35,5 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
37
35
  list: AFSEntry[];
38
36
  message?: string;
39
37
  }>;
38
+ private findModules;
40
39
  }
package/lib/esm/afs.js CHANGED
@@ -2,7 +2,7 @@ import { Emitter } from "strict-event-emitter";
2
2
  import { joinURL } from "ufo";
3
3
  import { AFSHistory } from "./history/index.js";
4
4
  import { SharedAFSStorage } from "./storage/index.js";
5
- const DEFAULT_MAX_DEPTH = 5;
5
+ const DEFAULT_MAX_DEPTH = 1;
6
6
  export class AFS extends Emitter {
7
7
  moduleId = "AFSRoot";
8
8
  path = "/";
@@ -40,66 +40,52 @@ export class AFS extends Emitter {
40
40
  throw new Error(`Invalid maxDepth: ${maxDepth}`);
41
41
  const results = [];
42
42
  const messages = [];
43
- const modules = this.findModules(path);
44
- for (const { module, subpath, mountPath } of modules) {
45
- if (!module.list)
43
+ const matches = this.findModules(path, options);
44
+ for (const matched of matches) {
45
+ const moduleEntry = {
46
+ id: matched.module.moduleId,
47
+ path: matched.remainedModulePath,
48
+ summary: matched.module.description,
49
+ };
50
+ if (matched.maxDepth === 0) {
51
+ results.push(moduleEntry);
52
+ continue;
53
+ }
54
+ if (!matched.module.list)
46
55
  continue;
47
56
  try {
48
- const newMaxDepth = maxDepth - mountPath.split("/").filter(Boolean).length;
49
- if (newMaxDepth < 0)
50
- continue;
51
- const { list, message } = await module.list(subpath, { ...options, maxDepth: newMaxDepth });
52
- results.push(...list.map((entry) => ({
53
- ...entry,
54
- path: joinURL(mountPath, entry.path),
55
- })));
57
+ const { list, message } = await matched.module.list(matched.subpath, {
58
+ ...options,
59
+ maxDepth: matched.maxDepth,
60
+ });
61
+ if (list.length) {
62
+ results.push(...list.map((entry) => ({
63
+ ...entry,
64
+ path: joinURL(matched.module.path, entry.path),
65
+ })));
66
+ }
67
+ else {
68
+ results.push(moduleEntry);
69
+ }
56
70
  if (message)
57
71
  messages.push(message);
58
72
  }
59
73
  catch (error) {
60
- console.error(`Error listing from module at ${mountPath}`, error);
74
+ console.error(`Error listing from module at ${matched.module.path}`, error);
61
75
  }
62
76
  }
63
- return { list: results, message: messages.join("; ") };
64
- }
65
- findModules(fullPath) {
66
- const modules = [];
67
- for (const [mountPath, module] of this.modules) {
68
- const match = this.isSubpath(fullPath, mountPath);
69
- if (!match)
70
- continue;
71
- modules.push({ ...match, module, mountPath });
72
- }
73
- return modules.sort((a, b) => b.matchedDepth - a.matchedDepth);
74
- }
75
- isSubpath(fullPath, mountPath) {
76
- const fullPathSegments = fullPath.split("/").filter(Boolean);
77
- const mountPathSegments = mountPath.split("/").filter(Boolean);
78
- const fp = fullPathSegments.join("/");
79
- const mp = mountPathSegments.join("/");
80
- if (fp.startsWith(mp)) {
81
- return {
82
- matchedDepth: mountPathSegments.length,
83
- subpath: joinURL("/", ...fullPathSegments.slice(mountPathSegments.length)),
84
- };
85
- }
86
- else if (mp.startsWith(fp)) {
87
- return {
88
- matchedDepth: fullPathSegments.length,
89
- subpath: "/",
90
- };
91
- }
77
+ return { list: results, message: messages.join("; ").trim() || undefined };
92
78
  }
93
79
  async read(path) {
94
- const modules = this.findModules(path);
95
- for (const { module, mountPath, subpath } of modules) {
80
+ const modules = this.findModules(path, { exactMatch: true });
81
+ for (const { module, subpath } of modules) {
96
82
  const res = await module.read?.(subpath);
97
83
  if (res?.result) {
98
84
  return {
99
85
  ...res,
100
86
  result: {
101
87
  ...res.result,
102
- path: joinURL(mountPath, res.result.path),
88
+ path: joinURL(module.path, res.result.path),
103
89
  },
104
90
  };
105
91
  }
@@ -107,7 +93,7 @@ export class AFS extends Emitter {
107
93
  return { result: undefined, message: "File not found" };
108
94
  }
109
95
  async write(path, content) {
110
- const module = this.findModules(path)[0];
96
+ const module = this.findModules(path, { exactMatch: true })[0];
111
97
  if (!module?.module.write)
112
98
  throw new Error(`No module found for path: ${path}`);
113
99
  const res = await module.module.write(module.subpath, content);
@@ -115,31 +101,57 @@ export class AFS extends Emitter {
115
101
  ...res,
116
102
  result: {
117
103
  ...res.result,
118
- path: joinURL(module.mountPath, res.result.path),
104
+ path: joinURL(module.module.path, res.result.path),
119
105
  },
120
106
  };
121
107
  }
122
108
  async search(path, query, options) {
123
109
  const results = [];
124
110
  const messages = [];
125
- for (const { module, mountPath, subpath } of this.findModules(path)) {
126
- if (mountPath.startsWith(path)) {
127
- if (!module.search)
128
- continue;
129
- try {
130
- const { list, message } = await module.search(subpath, query, options);
131
- results.push(...list.map((entry) => ({
132
- ...entry,
133
- path: joinURL(mountPath, entry.path),
134
- })));
135
- if (message)
136
- messages.push(message);
137
- }
138
- catch (error) {
139
- console.error(`Error searching in module at ${mountPath}`, error);
140
- }
111
+ for (const { module, subpath } of this.findModules(path)) {
112
+ if (!module.search)
113
+ continue;
114
+ try {
115
+ const { list, message } = await module.search(subpath, query, options);
116
+ results.push(...list.map((entry) => ({
117
+ ...entry,
118
+ path: joinURL(module.path, entry.path),
119
+ })));
120
+ if (message)
121
+ messages.push(message);
122
+ }
123
+ catch (error) {
124
+ console.error(`Error searching in module at ${module.path}`, error);
141
125
  }
142
126
  }
143
127
  return { list: results, message: messages.join("; ") };
144
128
  }
129
+ findModules(path, options) {
130
+ const maxDepth = Math.max(options?.maxDepth ?? DEFAULT_MAX_DEPTH, 1);
131
+ const matched = [];
132
+ for (const [modulePath, module] of this.modules) {
133
+ const pathSegments = path.split("/").filter(Boolean);
134
+ const modulePathSegments = modulePath.split("/").filter(Boolean);
135
+ let newMaxDepth;
136
+ let subpath;
137
+ let remainedModulePath;
138
+ if (!options?.exactMatch && modulePath.startsWith(path)) {
139
+ newMaxDepth = Math.max(0, maxDepth - (modulePathSegments.length - pathSegments.length));
140
+ subpath = "/";
141
+ remainedModulePath = joinURL("/", ...modulePathSegments.slice(pathSegments.length).slice(0, maxDepth));
142
+ }
143
+ else if (path.startsWith(modulePath)) {
144
+ newMaxDepth = maxDepth;
145
+ subpath = joinURL("/", ...pathSegments.slice(modulePathSegments.length));
146
+ remainedModulePath = "/";
147
+ }
148
+ else {
149
+ continue;
150
+ }
151
+ if (newMaxDepth < 0)
152
+ continue;
153
+ matched.push({ module, maxDepth: newMaxDepth, subpath, remainedModulePath });
154
+ }
155
+ return matched;
156
+ }
145
157
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/afs",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "AIGNE File System (AFS) is a virtual file system that supports various storage backends and provides a unified API for file operations.",
5
5
  "publishConfig": {
6
6
  "access": "public"