@aigne/afs 1.0.0 → 1.1.0

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.0](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.1.0-beta...afs-v1.1.0) (2025-10-19)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * @aigne/sqlite bumped to 0.4.3
11
+
12
+ ## [1.1.0-beta](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.0.0...afs-v1.1.0-beta) (2025-10-11)
13
+
14
+
15
+ ### Features
16
+
17
+ * **afs:** add module system fs for afs ([#594](https://github.com/AIGNE-io/aigne-framework/issues/594)) ([83c7b65](https://github.com/AIGNE-io/aigne-framework/commit/83c7b6555d21c606a5005eb05f6686882fb8ffa3))
18
+
3
19
  ## 1.0.0 (2025-10-07)
4
20
 
5
21
 
package/lib/cjs/afs.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { AFSStorage } from "./storage/type.js";
4
4
  import type { AFSEntry, AFSListOptions, AFSModule, AFSRoot, AFSRootEvents, AFSSearchOptions, AFSWriteEntryPayload } from "./type.js";
5
5
  export interface AFSOptions {
6
6
  storage?: SharedAFSStorage | SharedAFSStorageOptions;
7
+ modules?: AFSModule[];
7
8
  }
8
9
  export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
9
10
  moduleId: string;
@@ -13,14 +14,27 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
13
14
  storage(module: AFSModule): AFSStorage;
14
15
  private modules;
15
16
  use(module: AFSModule): this;
17
+ listModules(): Promise<{
18
+ moduleId: string;
19
+ path: string;
20
+ description?: string;
21
+ }[]>;
16
22
  list(path: string, options?: AFSListOptions): Promise<{
17
23
  list: AFSEntry[];
24
+ message?: string;
18
25
  }>;
19
26
  private findModules;
20
27
  private isSubpath;
21
- read(path: string): Promise<AFSEntry | undefined>;
22
- write(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
28
+ read(path: string): Promise<{
29
+ result?: AFSEntry;
30
+ message?: string;
31
+ }>;
32
+ write(path: string, content: AFSWriteEntryPayload): Promise<{
33
+ result: AFSEntry;
34
+ message?: string;
35
+ }>;
23
36
  search(path: string, query: string, options?: AFSSearchOptions): Promise<{
24
37
  list: AFSEntry[];
38
+ message?: string;
25
39
  }>;
26
40
  }
package/lib/cjs/afs.js CHANGED
@@ -16,6 +16,9 @@ class AFS extends strict_event_emitter_1.Emitter {
16
16
  ? options.storage
17
17
  : new index_js_2.SharedAFSStorage(options?.storage);
18
18
  this.use(new index_js_1.AFSHistory());
19
+ for (const module of options?.modules ?? []) {
20
+ this.use(module);
21
+ }
19
22
  }
20
23
  _storage;
21
24
  storage(module) {
@@ -27,11 +30,19 @@ class AFS extends strict_event_emitter_1.Emitter {
27
30
  module.onMount?.(this);
28
31
  return this;
29
32
  }
33
+ async listModules() {
34
+ return Array.from(this.modules.entries()).map(([path, module]) => ({
35
+ moduleId: module.moduleId,
36
+ path,
37
+ description: module.description,
38
+ }));
39
+ }
30
40
  async list(path, options) {
31
41
  const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
32
42
  if (!(maxDepth >= 0))
33
43
  throw new Error(`Invalid maxDepth: ${maxDepth}`);
34
44
  const results = [];
45
+ const messages = [];
35
46
  const modules = this.findModules(path);
36
47
  for (const { module, subpath, mountPath } of modules) {
37
48
  if (!module.list)
@@ -40,17 +51,19 @@ class AFS extends strict_event_emitter_1.Emitter {
40
51
  const newMaxDepth = maxDepth - mountPath.split("/").filter(Boolean).length;
41
52
  if (newMaxDepth < 0)
42
53
  continue;
43
- const { list } = await module.list(subpath, { ...options, maxDepth: newMaxDepth });
54
+ const { list, message } = await module.list(subpath, { ...options, maxDepth: newMaxDepth });
44
55
  results.push(...list.map((entry) => ({
45
56
  ...entry,
46
57
  path: (0, ufo_1.joinURL)(mountPath, entry.path),
47
58
  })));
59
+ if (message)
60
+ messages.push(message);
48
61
  }
49
62
  catch (error) {
50
63
  console.error(`Error listing from module at ${mountPath}`, error);
51
64
  }
52
65
  }
53
- return { list: results };
66
+ return { list: results, message: messages.join("; ") };
54
67
  }
55
68
  findModules(fullPath) {
56
69
  const modules = [];
@@ -76,51 +89,61 @@ class AFS extends strict_event_emitter_1.Emitter {
76
89
  else if (mp.startsWith(fp)) {
77
90
  return {
78
91
  matchedDepth: fullPathSegments.length,
79
- subpath: (0, ufo_1.joinURL)("/", ...mountPathSegments.slice(fullPathSegments.length)),
92
+ subpath: "/",
80
93
  };
81
94
  }
82
95
  }
83
96
  async read(path) {
84
97
  const modules = this.findModules(path);
85
98
  for (const { module, mountPath, subpath } of modules) {
86
- const entry = await module.read?.(subpath);
87
- if (entry) {
99
+ const res = await module.read?.(subpath);
100
+ if (res?.result) {
88
101
  return {
89
- ...entry,
90
- path: (0, ufo_1.joinURL)(mountPath, entry.path),
102
+ ...res,
103
+ result: {
104
+ ...res.result,
105
+ path: (0, ufo_1.joinURL)(mountPath, res.result.path),
106
+ },
91
107
  };
92
108
  }
93
109
  }
110
+ return { result: undefined, message: "File not found" };
94
111
  }
95
112
  async write(path, content) {
96
113
  const module = this.findModules(path)[0];
97
114
  if (!module?.module.write)
98
115
  throw new Error(`No module found for path: ${path}`);
99
- const entry = await module.module.write(module.subpath, content);
116
+ const res = await module.module.write(module.subpath, content);
100
117
  return {
101
- ...entry,
102
- path: (0, ufo_1.joinURL)(module.mountPath, entry.path),
118
+ ...res,
119
+ result: {
120
+ ...res.result,
121
+ path: (0, ufo_1.joinURL)(module.mountPath, res.result.path),
122
+ },
103
123
  };
104
124
  }
105
125
  async search(path, query, options) {
106
126
  const results = [];
127
+ const messages = [];
107
128
  for (const { module, mountPath, subpath } of this.findModules(path)) {
108
129
  if (mountPath.startsWith(path)) {
109
130
  if (!module.search)
110
131
  continue;
111
132
  try {
112
- const { list } = await module.search(subpath, query, options);
133
+ const { list, message } = await module.search(subpath, query, options);
113
134
  results.push(...list.map((entry) => ({
114
135
  ...entry,
115
136
  path: (0, ufo_1.joinURL)(mountPath, entry.path),
116
137
  })));
138
+ if (message)
139
+ messages.push(message);
117
140
  }
118
141
  catch (error) {
119
142
  console.error(`Error searching in module at ${mountPath}`, error);
120
143
  }
121
144
  }
122
145
  }
123
- return { list: results };
146
+ return { list: results, message: messages.join("; ") };
124
147
  }
125
148
  }
126
149
  exports.AFS = AFS;
@@ -9,6 +9,12 @@ export declare class AFSHistory implements AFSModule {
9
9
  list(path: string, options?: AFSListOptions): Promise<{
10
10
  list: AFSEntry[];
11
11
  }>;
12
- read(path: string): Promise<AFSEntry | undefined>;
13
- write(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
12
+ read(path: string): Promise<{
13
+ result: AFSEntry | undefined;
14
+ message?: string;
15
+ }>;
16
+ write(path: string, content: AFSWriteEntryPayload): Promise<{
17
+ result: AFSEntry;
18
+ message?: string;
19
+ }>;
14
20
  }
@@ -33,13 +33,15 @@ class AFSHistory {
33
33
  async list(path, options) {
34
34
  if (path !== "/")
35
35
  return { list: [] };
36
- return this.afs.storage(this).list(options);
36
+ return await this.afs.storage(this).list(options);
37
37
  }
38
38
  async read(path) {
39
- return this.afs.storage(this).read(path);
39
+ const result = await this.afs.storage(this).read(path);
40
+ return { result };
40
41
  }
41
42
  async write(path, content) {
42
- return this.afs.storage(this).create({ ...content, path });
43
+ const result = await this.afs.storage(this).create({ ...content, path });
44
+ return { result };
43
45
  }
44
46
  }
45
47
  exports.AFSHistory = AFSHistory;
package/lib/cjs/type.d.ts CHANGED
@@ -18,14 +18,23 @@ export interface AFSWriteEntryPayload extends Omit<AFSEntry, "id" | "path"> {
18
18
  export interface AFSModule {
19
19
  readonly moduleId: string;
20
20
  readonly path: string;
21
+ readonly description?: string;
21
22
  onMount?(root: AFSRoot): void;
22
23
  list?(path: string, options?: AFSListOptions): Promise<{
23
24
  list: AFSEntry[];
25
+ message?: string;
26
+ }>;
27
+ read?(path: string): Promise<{
28
+ result?: AFSEntry;
29
+ message?: string;
30
+ }>;
31
+ write?(path: string, content: AFSWriteEntryPayload): Promise<{
32
+ result: AFSEntry;
33
+ message?: string;
24
34
  }>;
25
- read?(path: string): Promise<AFSEntry | undefined>;
26
- write?(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
27
35
  search?(path: string, query: string, options?: AFSSearchOptions): Promise<{
28
36
  list: AFSEntry[];
37
+ message?: string;
29
38
  }>;
30
39
  }
31
40
  export type AFSRootEvents = {
package/lib/dts/afs.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { AFSStorage } from "./storage/type.js";
4
4
  import type { AFSEntry, AFSListOptions, AFSModule, AFSRoot, AFSRootEvents, AFSSearchOptions, AFSWriteEntryPayload } from "./type.js";
5
5
  export interface AFSOptions {
6
6
  storage?: SharedAFSStorage | SharedAFSStorageOptions;
7
+ modules?: AFSModule[];
7
8
  }
8
9
  export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
9
10
  moduleId: string;
@@ -13,14 +14,27 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
13
14
  storage(module: AFSModule): AFSStorage;
14
15
  private modules;
15
16
  use(module: AFSModule): this;
17
+ listModules(): Promise<{
18
+ moduleId: string;
19
+ path: string;
20
+ description?: string;
21
+ }[]>;
16
22
  list(path: string, options?: AFSListOptions): Promise<{
17
23
  list: AFSEntry[];
24
+ message?: string;
18
25
  }>;
19
26
  private findModules;
20
27
  private isSubpath;
21
- read(path: string): Promise<AFSEntry | undefined>;
22
- write(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
28
+ read(path: string): Promise<{
29
+ result?: AFSEntry;
30
+ message?: string;
31
+ }>;
32
+ write(path: string, content: AFSWriteEntryPayload): Promise<{
33
+ result: AFSEntry;
34
+ message?: string;
35
+ }>;
23
36
  search(path: string, query: string, options?: AFSSearchOptions): Promise<{
24
37
  list: AFSEntry[];
38
+ message?: string;
25
39
  }>;
26
40
  }
@@ -9,6 +9,12 @@ export declare class AFSHistory implements AFSModule {
9
9
  list(path: string, options?: AFSListOptions): Promise<{
10
10
  list: AFSEntry[];
11
11
  }>;
12
- read(path: string): Promise<AFSEntry | undefined>;
13
- write(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
12
+ read(path: string): Promise<{
13
+ result: AFSEntry | undefined;
14
+ message?: string;
15
+ }>;
16
+ write(path: string, content: AFSWriteEntryPayload): Promise<{
17
+ result: AFSEntry;
18
+ message?: string;
19
+ }>;
14
20
  }
package/lib/dts/type.d.ts CHANGED
@@ -18,14 +18,23 @@ export interface AFSWriteEntryPayload extends Omit<AFSEntry, "id" | "path"> {
18
18
  export interface AFSModule {
19
19
  readonly moduleId: string;
20
20
  readonly path: string;
21
+ readonly description?: string;
21
22
  onMount?(root: AFSRoot): void;
22
23
  list?(path: string, options?: AFSListOptions): Promise<{
23
24
  list: AFSEntry[];
25
+ message?: string;
26
+ }>;
27
+ read?(path: string): Promise<{
28
+ result?: AFSEntry;
29
+ message?: string;
30
+ }>;
31
+ write?(path: string, content: AFSWriteEntryPayload): Promise<{
32
+ result: AFSEntry;
33
+ message?: string;
24
34
  }>;
25
- read?(path: string): Promise<AFSEntry | undefined>;
26
- write?(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
27
35
  search?(path: string, query: string, options?: AFSSearchOptions): Promise<{
28
36
  list: AFSEntry[];
37
+ message?: string;
29
38
  }>;
30
39
  }
31
40
  export type AFSRootEvents = {
package/lib/esm/afs.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { AFSStorage } from "./storage/type.js";
4
4
  import type { AFSEntry, AFSListOptions, AFSModule, AFSRoot, AFSRootEvents, AFSSearchOptions, AFSWriteEntryPayload } from "./type.js";
5
5
  export interface AFSOptions {
6
6
  storage?: SharedAFSStorage | SharedAFSStorageOptions;
7
+ modules?: AFSModule[];
7
8
  }
8
9
  export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
9
10
  moduleId: string;
@@ -13,14 +14,27 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
13
14
  storage(module: AFSModule): AFSStorage;
14
15
  private modules;
15
16
  use(module: AFSModule): this;
17
+ listModules(): Promise<{
18
+ moduleId: string;
19
+ path: string;
20
+ description?: string;
21
+ }[]>;
16
22
  list(path: string, options?: AFSListOptions): Promise<{
17
23
  list: AFSEntry[];
24
+ message?: string;
18
25
  }>;
19
26
  private findModules;
20
27
  private isSubpath;
21
- read(path: string): Promise<AFSEntry | undefined>;
22
- write(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
28
+ read(path: string): Promise<{
29
+ result?: AFSEntry;
30
+ message?: string;
31
+ }>;
32
+ write(path: string, content: AFSWriteEntryPayload): Promise<{
33
+ result: AFSEntry;
34
+ message?: string;
35
+ }>;
23
36
  search(path: string, query: string, options?: AFSSearchOptions): Promise<{
24
37
  list: AFSEntry[];
38
+ message?: string;
25
39
  }>;
26
40
  }
package/lib/esm/afs.js CHANGED
@@ -13,6 +13,9 @@ export class AFS extends Emitter {
13
13
  ? options.storage
14
14
  : new SharedAFSStorage(options?.storage);
15
15
  this.use(new AFSHistory());
16
+ for (const module of options?.modules ?? []) {
17
+ this.use(module);
18
+ }
16
19
  }
17
20
  _storage;
18
21
  storage(module) {
@@ -24,11 +27,19 @@ export class AFS extends Emitter {
24
27
  module.onMount?.(this);
25
28
  return this;
26
29
  }
30
+ async listModules() {
31
+ return Array.from(this.modules.entries()).map(([path, module]) => ({
32
+ moduleId: module.moduleId,
33
+ path,
34
+ description: module.description,
35
+ }));
36
+ }
27
37
  async list(path, options) {
28
38
  const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
29
39
  if (!(maxDepth >= 0))
30
40
  throw new Error(`Invalid maxDepth: ${maxDepth}`);
31
41
  const results = [];
42
+ const messages = [];
32
43
  const modules = this.findModules(path);
33
44
  for (const { module, subpath, mountPath } of modules) {
34
45
  if (!module.list)
@@ -37,17 +48,19 @@ export class AFS extends Emitter {
37
48
  const newMaxDepth = maxDepth - mountPath.split("/").filter(Boolean).length;
38
49
  if (newMaxDepth < 0)
39
50
  continue;
40
- const { list } = await module.list(subpath, { ...options, maxDepth: newMaxDepth });
51
+ const { list, message } = await module.list(subpath, { ...options, maxDepth: newMaxDepth });
41
52
  results.push(...list.map((entry) => ({
42
53
  ...entry,
43
54
  path: joinURL(mountPath, entry.path),
44
55
  })));
56
+ if (message)
57
+ messages.push(message);
45
58
  }
46
59
  catch (error) {
47
60
  console.error(`Error listing from module at ${mountPath}`, error);
48
61
  }
49
62
  }
50
- return { list: results };
63
+ return { list: results, message: messages.join("; ") };
51
64
  }
52
65
  findModules(fullPath) {
53
66
  const modules = [];
@@ -73,50 +86,60 @@ export class AFS extends Emitter {
73
86
  else if (mp.startsWith(fp)) {
74
87
  return {
75
88
  matchedDepth: fullPathSegments.length,
76
- subpath: joinURL("/", ...mountPathSegments.slice(fullPathSegments.length)),
89
+ subpath: "/",
77
90
  };
78
91
  }
79
92
  }
80
93
  async read(path) {
81
94
  const modules = this.findModules(path);
82
95
  for (const { module, mountPath, subpath } of modules) {
83
- const entry = await module.read?.(subpath);
84
- if (entry) {
96
+ const res = await module.read?.(subpath);
97
+ if (res?.result) {
85
98
  return {
86
- ...entry,
87
- path: joinURL(mountPath, entry.path),
99
+ ...res,
100
+ result: {
101
+ ...res.result,
102
+ path: joinURL(mountPath, res.result.path),
103
+ },
88
104
  };
89
105
  }
90
106
  }
107
+ return { result: undefined, message: "File not found" };
91
108
  }
92
109
  async write(path, content) {
93
110
  const module = this.findModules(path)[0];
94
111
  if (!module?.module.write)
95
112
  throw new Error(`No module found for path: ${path}`);
96
- const entry = await module.module.write(module.subpath, content);
113
+ const res = await module.module.write(module.subpath, content);
97
114
  return {
98
- ...entry,
99
- path: joinURL(module.mountPath, entry.path),
115
+ ...res,
116
+ result: {
117
+ ...res.result,
118
+ path: joinURL(module.mountPath, res.result.path),
119
+ },
100
120
  };
101
121
  }
102
122
  async search(path, query, options) {
103
123
  const results = [];
124
+ const messages = [];
104
125
  for (const { module, mountPath, subpath } of this.findModules(path)) {
105
126
  if (mountPath.startsWith(path)) {
106
127
  if (!module.search)
107
128
  continue;
108
129
  try {
109
- const { list } = await module.search(subpath, query, options);
130
+ const { list, message } = await module.search(subpath, query, options);
110
131
  results.push(...list.map((entry) => ({
111
132
  ...entry,
112
133
  path: joinURL(mountPath, entry.path),
113
134
  })));
135
+ if (message)
136
+ messages.push(message);
114
137
  }
115
138
  catch (error) {
116
139
  console.error(`Error searching in module at ${mountPath}`, error);
117
140
  }
118
141
  }
119
142
  }
120
- return { list: results };
143
+ return { list: results, message: messages.join("; ") };
121
144
  }
122
145
  }
@@ -9,6 +9,12 @@ export declare class AFSHistory implements AFSModule {
9
9
  list(path: string, options?: AFSListOptions): Promise<{
10
10
  list: AFSEntry[];
11
11
  }>;
12
- read(path: string): Promise<AFSEntry | undefined>;
13
- write(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
12
+ read(path: string): Promise<{
13
+ result: AFSEntry | undefined;
14
+ message?: string;
15
+ }>;
16
+ write(path: string, content: AFSWriteEntryPayload): Promise<{
17
+ result: AFSEntry;
18
+ message?: string;
19
+ }>;
14
20
  }
@@ -30,12 +30,14 @@ export class AFSHistory {
30
30
  async list(path, options) {
31
31
  if (path !== "/")
32
32
  return { list: [] };
33
- return this.afs.storage(this).list(options);
33
+ return await this.afs.storage(this).list(options);
34
34
  }
35
35
  async read(path) {
36
- return this.afs.storage(this).read(path);
36
+ const result = await this.afs.storage(this).read(path);
37
+ return { result };
37
38
  }
38
39
  async write(path, content) {
39
- return this.afs.storage(this).create({ ...content, path });
40
+ const result = await this.afs.storage(this).create({ ...content, path });
41
+ return { result };
40
42
  }
41
43
  }
package/lib/esm/type.d.ts CHANGED
@@ -18,14 +18,23 @@ export interface AFSWriteEntryPayload extends Omit<AFSEntry, "id" | "path"> {
18
18
  export interface AFSModule {
19
19
  readonly moduleId: string;
20
20
  readonly path: string;
21
+ readonly description?: string;
21
22
  onMount?(root: AFSRoot): void;
22
23
  list?(path: string, options?: AFSListOptions): Promise<{
23
24
  list: AFSEntry[];
25
+ message?: string;
26
+ }>;
27
+ read?(path: string): Promise<{
28
+ result?: AFSEntry;
29
+ message?: string;
30
+ }>;
31
+ write?(path: string, content: AFSWriteEntryPayload): Promise<{
32
+ result: AFSEntry;
33
+ message?: string;
24
34
  }>;
25
- read?(path: string): Promise<AFSEntry | undefined>;
26
- write?(path: string, content: AFSWriteEntryPayload): Promise<AFSEntry>;
27
35
  search?(path: string, query: string, options?: AFSSearchOptions): Promise<{
28
36
  list: AFSEntry[];
37
+ message?: string;
29
38
  }>;
30
39
  }
31
40
  export type AFSRootEvents = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/afs",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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"
@@ -50,7 +50,7 @@
50
50
  "@aigne/uuid": "^13.0.1",
51
51
  "strict-event-emitter": "^0.5.1",
52
52
  "ufo": "^1.6.1",
53
- "@aigne/sqlite": "^0.4.3-beta"
53
+ "@aigne/sqlite": "^0.4.3"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/bun": "^1.2.22",