@aigne/afs 1.11.0-beta → 1.11.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/LICENSE.md +17 -84
- package/README.md +4 -13
- package/dist/_virtual/rolldown_runtime.mjs +7 -0
- package/dist/afs.cjs +1330 -0
- package/dist/afs.d.cts +275 -0
- package/dist/afs.d.cts.map +1 -0
- package/dist/afs.d.mts +275 -0
- package/dist/afs.d.mts.map +1 -0
- package/dist/afs.mjs +1331 -0
- package/dist/afs.mjs.map +1 -0
- package/dist/capabilities/index.d.mts +2 -0
- package/dist/capabilities/types.d.cts +100 -0
- package/dist/capabilities/types.d.cts.map +1 -0
- package/dist/capabilities/types.d.mts +100 -0
- package/dist/capabilities/types.d.mts.map +1 -0
- package/dist/capabilities/world-mapping.cjs +20 -0
- package/dist/capabilities/world-mapping.d.cts +139 -0
- package/dist/capabilities/world-mapping.d.cts.map +1 -0
- package/dist/capabilities/world-mapping.d.mts +139 -0
- package/dist/capabilities/world-mapping.d.mts.map +1 -0
- package/dist/capabilities/world-mapping.mjs +20 -0
- package/dist/capabilities/world-mapping.mjs.map +1 -0
- package/dist/error.cjs +63 -0
- package/dist/error.d.cts +39 -0
- package/dist/error.d.cts.map +1 -0
- package/dist/error.d.mts +39 -0
- package/dist/error.d.mts.map +1 -0
- package/dist/error.mjs +59 -0
- package/dist/error.mjs.map +1 -0
- package/dist/index.cjs +72 -345
- package/dist/index.d.cts +18 -300
- package/dist/index.d.mts +20 -300
- package/dist/index.mjs +16 -342
- package/dist/loader/index.cjs +110 -0
- package/dist/loader/index.d.cts +48 -0
- package/dist/loader/index.d.cts.map +1 -0
- package/dist/loader/index.d.mts +48 -0
- package/dist/loader/index.d.mts.map +1 -0
- package/dist/loader/index.mjs +110 -0
- package/dist/loader/index.mjs.map +1 -0
- package/dist/meta/index.cjs +4 -0
- package/dist/meta/index.mjs +6 -0
- package/dist/meta/kind.cjs +161 -0
- package/dist/meta/kind.d.cts +134 -0
- package/dist/meta/kind.d.cts.map +1 -0
- package/dist/meta/kind.d.mts +134 -0
- package/dist/meta/kind.d.mts.map +1 -0
- package/dist/meta/kind.mjs +157 -0
- package/dist/meta/kind.mjs.map +1 -0
- package/dist/meta/path.cjs +116 -0
- package/dist/meta/path.d.cts +43 -0
- package/dist/meta/path.d.cts.map +1 -0
- package/dist/meta/path.d.mts +43 -0
- package/dist/meta/path.d.mts.map +1 -0
- package/dist/meta/path.mjs +112 -0
- package/dist/meta/path.mjs.map +1 -0
- package/dist/meta/type.d.cts +96 -0
- package/dist/meta/type.d.cts.map +1 -0
- package/dist/meta/type.d.mts +96 -0
- package/dist/meta/type.d.mts.map +1 -0
- package/dist/meta/validation.cjs +77 -0
- package/dist/meta/validation.d.cts +19 -0
- package/dist/meta/validation.d.cts.map +1 -0
- package/dist/meta/validation.d.mts +19 -0
- package/dist/meta/validation.d.mts.map +1 -0
- package/dist/meta/validation.mjs +77 -0
- package/dist/meta/validation.mjs.map +1 -0
- package/dist/meta/well-known-kinds.cjs +228 -0
- package/dist/meta/well-known-kinds.d.cts +52 -0
- package/dist/meta/well-known-kinds.d.cts.map +1 -0
- package/dist/meta/well-known-kinds.d.mts +52 -0
- package/dist/meta/well-known-kinds.d.mts.map +1 -0
- package/dist/meta/well-known-kinds.mjs +219 -0
- package/dist/meta/well-known-kinds.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.cts +141 -0
- package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.mts +141 -0
- package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.mts.map +1 -0
- 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/dist/provider/base.cjs +425 -0
- package/dist/provider/base.d.cts +175 -0
- package/dist/provider/base.d.cts.map +1 -0
- package/dist/provider/base.d.mts +175 -0
- package/dist/provider/base.d.mts.map +1 -0
- package/dist/provider/base.mjs +426 -0
- package/dist/provider/base.mjs.map +1 -0
- package/dist/provider/decorators.cjs +268 -0
- package/dist/provider/decorators.d.cts +244 -0
- package/dist/provider/decorators.d.cts.map +1 -0
- package/dist/provider/decorators.d.mts +244 -0
- package/dist/provider/decorators.d.mts.map +1 -0
- package/dist/provider/decorators.mjs +256 -0
- package/dist/provider/decorators.mjs.map +1 -0
- package/dist/provider/index.cjs +19 -0
- package/dist/provider/index.d.cts +5 -0
- package/dist/provider/index.d.mts +5 -0
- package/dist/provider/index.mjs +5 -0
- package/dist/provider/router.cjs +185 -0
- package/dist/provider/router.d.cts +50 -0
- package/dist/provider/router.d.cts.map +1 -0
- package/dist/provider/router.d.mts +50 -0
- package/dist/provider/router.d.mts.map +1 -0
- package/dist/provider/router.mjs +185 -0
- package/dist/provider/router.mjs.map +1 -0
- package/dist/provider/types.d.cts +113 -0
- package/dist/provider/types.d.cts.map +1 -0
- package/dist/provider/types.d.mts +113 -0
- package/dist/provider/types.d.mts.map +1 -0
- package/dist/registry.cjs +358 -0
- package/dist/registry.d.cts +96 -0
- package/dist/registry.d.cts.map +1 -0
- package/dist/registry.d.mts +96 -0
- package/dist/registry.d.mts.map +1 -0
- package/dist/registry.mjs +360 -0
- package/dist/registry.mjs.map +1 -0
- package/dist/type.cjs +34 -0
- package/dist/type.d.cts +420 -0
- package/dist/type.d.cts.map +1 -0
- package/dist/type.d.mts +420 -0
- package/dist/type.d.mts.map +1 -0
- package/dist/type.mjs +33 -0
- package/dist/type.mjs.map +1 -0
- package/dist/utils/camelize.d.cts.map +1 -1
- package/dist/utils/camelize.d.mts.map +1 -1
- package/dist/utils/schema.cjs +129 -0
- package/dist/utils/schema.d.cts +65 -0
- package/dist/utils/schema.d.cts.map +1 -0
- package/dist/utils/schema.d.mts +65 -0
- package/dist/utils/schema.d.mts.map +1 -0
- package/dist/utils/schema.mjs +124 -0
- package/dist/utils/schema.mjs.map +1 -0
- package/dist/utils/type-utils.d.cts.map +1 -1
- package/dist/utils/type-utils.d.mts.map +1 -1
- package/dist/utils/uri-template.cjs +123 -0
- package/dist/utils/uri-template.d.cts +48 -0
- package/dist/utils/uri-template.d.cts.map +1 -0
- package/dist/utils/uri-template.d.mts +48 -0
- package/dist/utils/uri-template.d.mts.map +1 -0
- package/dist/utils/uri-template.mjs +120 -0
- package/dist/utils/uri-template.mjs.map +1 -0
- package/dist/utils/uri.cjs +49 -0
- package/dist/utils/uri.d.cts +34 -0
- package/dist/utils/uri.d.cts.map +1 -0
- package/dist/utils/uri.d.mts +34 -0
- package/dist/utils/uri.d.mts.map +1 -0
- package/dist/utils/uri.mjs +49 -0
- package/dist/utils/uri.mjs.map +1 -0
- package/dist/utils/zod.cjs +6 -8
- package/dist/utils/zod.d.cts +2 -2
- package/dist/utils/zod.d.cts.map +1 -1
- package/dist/utils/zod.d.mts +2 -2
- package/dist/utils/zod.d.mts.map +1 -1
- package/dist/utils/zod.mjs +6 -8
- package/dist/utils/zod.mjs.map +1 -1
- package/package.json +27 -4
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { AFSExplainResult } from "../meta/type.mjs";
|
|
2
|
+
import { AFSAccessMode, AFSDeleteOptions, AFSDeleteResult, AFSEntry, AFSEntryMetadata, AFSExecOptions, AFSExecResult, AFSExplainOptions, AFSListOptions, AFSListResult, AFSModule, AFSReadOptions, AFSReadResult, AFSRenameOptions, AFSRenameResult, AFSSearchOptions, AFSSearchResult, AFSStatOptions, AFSStatResult, AFSWriteEntryPayload, AFSWriteOptions, AFSWriteResult } from "../type.mjs";
|
|
3
|
+
import { OperationsDeclaration } from "../capabilities/types.mjs";
|
|
4
|
+
import { ProviderRouter } from "./router.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/provider/base.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for AFS providers using virtual routing
|
|
9
|
+
*
|
|
10
|
+
* All providers extend this class and use decorators to define routes:
|
|
11
|
+
* - @List(pattern) - Handle list operations
|
|
12
|
+
* - @Read(pattern) - Handle read operations
|
|
13
|
+
* - @Write(pattern) - Handle write operations
|
|
14
|
+
* - @Delete(pattern) - Handle delete operations
|
|
15
|
+
* - @Exec(pattern) - Handle exec operations
|
|
16
|
+
* - @Search(pattern) - Handle search operations
|
|
17
|
+
* - @Meta(pattern) - Handle .meta path reads
|
|
18
|
+
* - @Actions(pattern) - Handle .actions path listing
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* class MyProvider extends AFSBaseProvider {
|
|
23
|
+
* readonly name = "my-provider";
|
|
24
|
+
*
|
|
25
|
+
* @List("/")
|
|
26
|
+
* async listRoot(ctx: RouteContext) {
|
|
27
|
+
* return { data: [...] };
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* @Read("/:id")
|
|
31
|
+
* async getItem(ctx: RouteContext<{ id: string }>) {
|
|
32
|
+
* return { id: ctx.params.id, path: ctx.path, content: ... };
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare abstract class AFSBaseProvider implements AFSModule {
|
|
38
|
+
/** Provider name, must be unique when mounted */
|
|
39
|
+
abstract readonly name: string;
|
|
40
|
+
/** Optional description */
|
|
41
|
+
readonly description?: string;
|
|
42
|
+
/** Access mode: "readonly" or "readwrite" */
|
|
43
|
+
readonly accessMode: AFSAccessMode;
|
|
44
|
+
/** Internal router for path matching */
|
|
45
|
+
private router;
|
|
46
|
+
constructor();
|
|
47
|
+
/**
|
|
48
|
+
* Collect routes from decorators and register them
|
|
49
|
+
*/
|
|
50
|
+
private collectDecoratorRoutes;
|
|
51
|
+
/**
|
|
52
|
+
* Remove methods for operations that have no routes registered.
|
|
53
|
+
* This allows core to check if a provider supports a capability via typeof check.
|
|
54
|
+
*
|
|
55
|
+
* We set the method to undefined on the instance to shadow the prototype method,
|
|
56
|
+
* since `delete` only removes instance properties, not inherited ones.
|
|
57
|
+
*/
|
|
58
|
+
private removeUnimplementedMethods;
|
|
59
|
+
/**
|
|
60
|
+
* Build an OperationsDeclaration based on which methods are implemented.
|
|
61
|
+
* Uses the fact that removeUnimplementedMethods() sets unsupported methods to undefined.
|
|
62
|
+
* Write/delete/exec/rename are also gated by accessMode.
|
|
63
|
+
*/
|
|
64
|
+
protected getOperationsDeclaration(): OperationsDeclaration;
|
|
65
|
+
/**
|
|
66
|
+
* List entries at a path
|
|
67
|
+
*/
|
|
68
|
+
list(path: string, options?: AFSListOptions): Promise<AFSListResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Expand list results to specified depth using BFS traversal.
|
|
71
|
+
* Used when handler has handleDepth: false.
|
|
72
|
+
*
|
|
73
|
+
* The handler is expected to return single-level results (direct children only).
|
|
74
|
+
* This method recursively calls the handler to expand directories up to maxDepth.
|
|
75
|
+
* Pattern filtering is applied after BFS completes to ensure directories are
|
|
76
|
+
* traversed even if they don't match the pattern.
|
|
77
|
+
*/
|
|
78
|
+
private listWithDepthExpansion;
|
|
79
|
+
/**
|
|
80
|
+
* Check if a path matches a glob pattern
|
|
81
|
+
*/
|
|
82
|
+
private matchesPattern;
|
|
83
|
+
/**
|
|
84
|
+
* Read an entry at a path
|
|
85
|
+
*/
|
|
86
|
+
read(path: string, options?: AFSReadOptions): Promise<AFSReadResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Write an entry at a path
|
|
89
|
+
*/
|
|
90
|
+
write(path: string, content: AFSWriteEntryPayload, options?: AFSWriteOptions): Promise<AFSWriteResult>;
|
|
91
|
+
/**
|
|
92
|
+
* Delete an entry at a path
|
|
93
|
+
*/
|
|
94
|
+
delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Search entries at a path
|
|
97
|
+
*/
|
|
98
|
+
search(path: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Execute an action at a path
|
|
101
|
+
*/
|
|
102
|
+
exec(path: string, args: Record<string, unknown>, options?: AFSExecOptions): Promise<AFSExecResult>;
|
|
103
|
+
/**
|
|
104
|
+
* Get stat information for a path
|
|
105
|
+
*
|
|
106
|
+
* Note: Fallback to read() is handled at AFS core level, not here.
|
|
107
|
+
*/
|
|
108
|
+
stat(path: string, options?: AFSStatOptions): Promise<AFSStatResult>;
|
|
109
|
+
/**
|
|
110
|
+
* Get human-readable explanation for a path
|
|
111
|
+
*
|
|
112
|
+
* Note: Fallback to stat() is handled at AFS core level, not here.
|
|
113
|
+
*/
|
|
114
|
+
explain(path: string, options?: AFSExplainOptions): Promise<AFSExplainResult>;
|
|
115
|
+
/**
|
|
116
|
+
* Rename/move a path
|
|
117
|
+
*/
|
|
118
|
+
rename(oldPath: string, newPath: string, options?: AFSRenameOptions): Promise<AFSRenameResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Normalize a path to ensure consistent format
|
|
121
|
+
* - Always starts with /
|
|
122
|
+
* - No trailing slash (except for root)
|
|
123
|
+
*/
|
|
124
|
+
protected normalizePath(path: string): string;
|
|
125
|
+
/**
|
|
126
|
+
* Join path segments
|
|
127
|
+
*/
|
|
128
|
+
protected joinPath(...segments: string[]): string;
|
|
129
|
+
/**
|
|
130
|
+
* Build an AFSEntry helper
|
|
131
|
+
*
|
|
132
|
+
* @param path - The entry path (will be normalized)
|
|
133
|
+
* @param options - Entry options
|
|
134
|
+
* @param options.id - Entry ID. Defaults to normalized path if not provided
|
|
135
|
+
* @param options.content - Entry content
|
|
136
|
+
* @param options.meta - Entry metadata
|
|
137
|
+
* @param options.createdAt - Creation time. Undefined if not available from data source
|
|
138
|
+
* @param options.updatedAt - Last update time. Undefined if not available from data source
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* // Simple entry with just path
|
|
143
|
+
* this.buildEntry("/items/1")
|
|
144
|
+
*
|
|
145
|
+
* // Entry with content and metadata
|
|
146
|
+
* this.buildEntry("/items/1", {
|
|
147
|
+
* content: { name: "Item 1" },
|
|
148
|
+
* meta: { size: 100 },
|
|
149
|
+
* })
|
|
150
|
+
*
|
|
151
|
+
* // Entry with all fields
|
|
152
|
+
* this.buildEntry("/items/1", {
|
|
153
|
+
* id: "custom-id",
|
|
154
|
+
* content: data,
|
|
155
|
+
* meta: { size: stats.size },
|
|
156
|
+
* createdAt: stats.birthtime,
|
|
157
|
+
* updatedAt: stats.mtime,
|
|
158
|
+
* })
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
protected buildEntry(path: string, options?: {
|
|
162
|
+
id?: string;
|
|
163
|
+
content?: unknown;
|
|
164
|
+
meta?: Partial<AFSEntryMetadata>;
|
|
165
|
+
createdAt?: Date;
|
|
166
|
+
updatedAt?: Date;
|
|
167
|
+
}): AFSEntry;
|
|
168
|
+
/**
|
|
169
|
+
* Get the router (for testing/debugging)
|
|
170
|
+
*/
|
|
171
|
+
protected getRouter(): ProviderRouter;
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
export { AFSBaseProvider };
|
|
175
|
+
//# sourceMappingURL=base.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.mts","names":[],"sources":["../../src/provider/base.ts"],"mappings":";;;;;;;;;AA0EA;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAAsB,eAAA,YAA2B,SAAA;EAmdT;EAAA,kBAjdpB,IAAA;EAidwC;EAAA,SA9cjD,WAAA;EAueE;EAAA,SApeF,UAAA,EAAY,aAAA;EAkkBF;EAAA,QA/jBX,MAAA;;EAikBQ;;;EAAA,QAtjBR,sBAAA;EAtBgD;;;;;;;EAAA,QAqDhD,0BAAA;;;;;;YA4BE,wBAAA,CAAA,GAA4B,qBAAA;EAmB3B;;;EAAL,IAAA,CAAK,IAAA,UAAc,OAAA,GAAU,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAA;;;;;;;;;EAAA,QAyD9C,sBAAA;EAsLZ;;;EAAA,QAlEM,cAAA;EAoEN;;;EA7DI,IAAA,CAAK,IAAA,UAAc,OAAA,GAAU,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAuF/C;;;EA7BP,KAAA,CACJ,IAAA,UACA,OAAA,EAAS,oBAAA,EACT,OAAA,GAAU,eAAA,GACT,OAAA,CAAQ,cAAA;EAyBqD;;;EAA1D,MAAA,CAAO,IAAA,UAAc,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAyBZ;;;EAA9C,MAAA,CAAO,IAAA,UAAc,KAAA,UAAe,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAqBzE;;;EAAA,IAAA,CACJ,IAAA,UACA,IAAA,EAAM,MAAA,mBACN,OAAA,GAAU,cAAA,GACT,OAAA,CAAQ,aAAA;EADC;;;;;EA4BN,IAAA,CAAK,IAAA,UAAc,OAAA,GAAU,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAzB;;;;;EAuB7B,OAAA,CAAQ,IAAA,UAAc,OAAA,GAAU,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;EAA5B;;;EAqBhC,MAAA,CACJ,OAAA,UACA,OAAA,UACA,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,eAAA;EAJL;;;;;EAAA,UAiCI,aAAA,CAAc,IAAA;EA7Bb;;;EAAA,UAgDD,QAAA,CAAA,GAAY,QAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAA,UAyCZ,UAAA,CACR,IAAA,UACA,OAAA;IACE,EAAA;IACA,OAAA;IACA,IAAA,GAAO,OAAA,CAAQ,gBAAA;IACf,SAAA,GAAY,IAAA;IACZ,SAAA,GAAY,IAAA;EAAA,IAEb,QAAA;;;;YAeO,SAAA,CAAA,GAAa,cAAA;AAAA"}
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { AFSNotFoundError, AFSReadonlyError } from "../error.mjs";
|
|
2
|
+
import { getRoutes } from "./decorators.mjs";
|
|
3
|
+
import { ProviderRouter } from "./router.mjs";
|
|
4
|
+
import { minimatch } from "minimatch";
|
|
5
|
+
|
|
6
|
+
//#region src/provider/base.ts
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for AFS providers using virtual routing
|
|
9
|
+
*
|
|
10
|
+
* All providers extend this class and use decorators to define routes:
|
|
11
|
+
* - @List(pattern) - Handle list operations
|
|
12
|
+
* - @Read(pattern) - Handle read operations
|
|
13
|
+
* - @Write(pattern) - Handle write operations
|
|
14
|
+
* - @Delete(pattern) - Handle delete operations
|
|
15
|
+
* - @Exec(pattern) - Handle exec operations
|
|
16
|
+
* - @Search(pattern) - Handle search operations
|
|
17
|
+
* - @Meta(pattern) - Handle .meta path reads
|
|
18
|
+
* - @Actions(pattern) - Handle .actions path listing
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* class MyProvider extends AFSBaseProvider {
|
|
23
|
+
* readonly name = "my-provider";
|
|
24
|
+
*
|
|
25
|
+
* @List("/")
|
|
26
|
+
* async listRoot(ctx: RouteContext) {
|
|
27
|
+
* return { data: [...] };
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* @Read("/:id")
|
|
31
|
+
* async getItem(ctx: RouteContext<{ id: string }>) {
|
|
32
|
+
* return { id: ctx.params.id, path: ctx.path, content: ... };
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
var AFSBaseProvider = class {
|
|
38
|
+
/** Optional description */
|
|
39
|
+
description;
|
|
40
|
+
/** Access mode: "readonly" or "readwrite" */
|
|
41
|
+
accessMode = "readonly";
|
|
42
|
+
/** Internal router for path matching */
|
|
43
|
+
router;
|
|
44
|
+
constructor() {
|
|
45
|
+
this.router = new ProviderRouter();
|
|
46
|
+
this.collectDecoratorRoutes();
|
|
47
|
+
this.removeUnimplementedMethods();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Collect routes from decorators and register them
|
|
51
|
+
*/
|
|
52
|
+
collectDecoratorRoutes() {
|
|
53
|
+
const routes = getRoutes(this.constructor);
|
|
54
|
+
for (const route of routes) {
|
|
55
|
+
const handler = this[route.methodName];
|
|
56
|
+
if (typeof handler !== "function") {
|
|
57
|
+
console.warn(`[AFSBaseProvider] Method ${route.methodName} not found on ${this.constructor.name}`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const boundHandler = handler.bind(this);
|
|
61
|
+
this.router.registerRoute(route.pattern, route.operation, boundHandler, route.description, route.listOptions);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Remove methods for operations that have no routes registered.
|
|
66
|
+
* This allows core to check if a provider supports a capability via typeof check.
|
|
67
|
+
*
|
|
68
|
+
* We set the method to undefined on the instance to shadow the prototype method,
|
|
69
|
+
* since `delete` only removes instance properties, not inherited ones.
|
|
70
|
+
*/
|
|
71
|
+
removeUnimplementedMethods() {
|
|
72
|
+
for (const [operation, methodName] of Object.entries({
|
|
73
|
+
list: "list",
|
|
74
|
+
read: "read",
|
|
75
|
+
write: "write",
|
|
76
|
+
delete: "delete",
|
|
77
|
+
exec: "exec",
|
|
78
|
+
search: "search",
|
|
79
|
+
stat: "stat",
|
|
80
|
+
explain: "explain",
|
|
81
|
+
rename: "rename"
|
|
82
|
+
})) if (this.router.getRoutesForOperation(operation).length === 0) this[methodName] = void 0;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build an OperationsDeclaration based on which methods are implemented.
|
|
86
|
+
* Uses the fact that removeUnimplementedMethods() sets unsupported methods to undefined.
|
|
87
|
+
* Write/delete/exec/rename are also gated by accessMode.
|
|
88
|
+
*/
|
|
89
|
+
getOperationsDeclaration() {
|
|
90
|
+
const isReadwrite = this.accessMode === "readwrite";
|
|
91
|
+
return {
|
|
92
|
+
read: typeof this.read === "function",
|
|
93
|
+
list: typeof this.list === "function",
|
|
94
|
+
write: typeof this.write === "function" && isReadwrite,
|
|
95
|
+
delete: typeof this.delete === "function" && isReadwrite,
|
|
96
|
+
search: typeof this.search === "function",
|
|
97
|
+
exec: typeof this.exec === "function",
|
|
98
|
+
stat: typeof this.stat === "function",
|
|
99
|
+
explain: typeof this.explain === "function"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* List entries at a path
|
|
104
|
+
*/
|
|
105
|
+
async list(path, options) {
|
|
106
|
+
const maxDepth = options?.maxDepth ?? 1;
|
|
107
|
+
if (maxDepth === 0) return { data: [] };
|
|
108
|
+
const normalizedPath = this.normalizePath(path);
|
|
109
|
+
const match = this.router.match(normalizedPath, "list");
|
|
110
|
+
if (!match) throw new AFSNotFoundError(normalizedPath);
|
|
111
|
+
const limit = options?.limit;
|
|
112
|
+
if ((match.route.listOptions?.handleDepth ?? false) || maxDepth <= 1) {
|
|
113
|
+
const ctx = {
|
|
114
|
+
path: normalizedPath,
|
|
115
|
+
params: match.params,
|
|
116
|
+
options
|
|
117
|
+
};
|
|
118
|
+
const handler = match.route.handler;
|
|
119
|
+
const handlerResult = await handler(ctx);
|
|
120
|
+
const result = {
|
|
121
|
+
data: handlerResult.data,
|
|
122
|
+
total: handlerResult.total
|
|
123
|
+
};
|
|
124
|
+
if (limit !== void 0 && result.data.length > limit) return {
|
|
125
|
+
data: result.data.slice(0, limit),
|
|
126
|
+
total: result.total ?? result.data.length
|
|
127
|
+
};
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
return this.listWithDepthExpansion(normalizedPath, options, maxDepth);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Expand list results to specified depth using BFS traversal.
|
|
134
|
+
* Used when handler has handleDepth: false.
|
|
135
|
+
*
|
|
136
|
+
* The handler is expected to return single-level results (direct children only).
|
|
137
|
+
* This method recursively calls the handler to expand directories up to maxDepth.
|
|
138
|
+
* Pattern filtering is applied after BFS completes to ensure directories are
|
|
139
|
+
* traversed even if they don't match the pattern.
|
|
140
|
+
*/
|
|
141
|
+
async listWithDepthExpansion(basePath, options, maxDepth) {
|
|
142
|
+
const limit = options?.limit ?? 1e3;
|
|
143
|
+
const pattern = options?.pattern;
|
|
144
|
+
const allEntries = [];
|
|
145
|
+
const visited = /* @__PURE__ */ new Set();
|
|
146
|
+
const queue = [{
|
|
147
|
+
path: basePath,
|
|
148
|
+
depth: 0
|
|
149
|
+
}];
|
|
150
|
+
const unfilteredEntries = [];
|
|
151
|
+
while (queue.length > 0) {
|
|
152
|
+
const { path, depth } = queue.shift();
|
|
153
|
+
if (visited.has(path)) continue;
|
|
154
|
+
visited.add(path);
|
|
155
|
+
const singleLevelOptions = {
|
|
156
|
+
...options,
|
|
157
|
+
maxDepth: 1,
|
|
158
|
+
pattern: void 0
|
|
159
|
+
};
|
|
160
|
+
const match = this.router.match(path, "list");
|
|
161
|
+
if (!match) continue;
|
|
162
|
+
const ctx = {
|
|
163
|
+
path,
|
|
164
|
+
params: match.params,
|
|
165
|
+
options: singleLevelOptions
|
|
166
|
+
};
|
|
167
|
+
const handler = match.route.handler;
|
|
168
|
+
let result;
|
|
169
|
+
try {
|
|
170
|
+
result = await handler(ctx);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
const code = error.code;
|
|
173
|
+
if (code === "ENOENT" || code === "ENOTDIR" || error instanceof Error && error.message.includes("not found")) continue;
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
const noExpandSet = new Set(result.noExpand ?? []);
|
|
177
|
+
for (const entry of result.data) {
|
|
178
|
+
if (entry.path === path && depth > 0) continue;
|
|
179
|
+
unfilteredEntries.push(entry);
|
|
180
|
+
const childrenCount = entry.meta?.childrenCount;
|
|
181
|
+
const isDirectory = childrenCount !== void 0 && childrenCount !== 0;
|
|
182
|
+
const shouldExpand = !noExpandSet.has(entry.path);
|
|
183
|
+
if (depth < maxDepth - 1 && isDirectory && shouldExpand && entry.path !== path) queue.push({
|
|
184
|
+
path: entry.path,
|
|
185
|
+
depth: depth + 1
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (pattern) for (const entry of unfilteredEntries) {
|
|
190
|
+
if (allEntries.length >= limit) break;
|
|
191
|
+
if (this.matchesPattern(entry.path, pattern)) allEntries.push(entry);
|
|
192
|
+
}
|
|
193
|
+
else for (const entry of unfilteredEntries) {
|
|
194
|
+
if (allEntries.length >= limit) break;
|
|
195
|
+
allEntries.push(entry);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
data: allEntries,
|
|
199
|
+
total: allEntries.length >= limit ? void 0 : allEntries.length
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if a path matches a glob pattern
|
|
204
|
+
*/
|
|
205
|
+
matchesPattern(path, pattern) {
|
|
206
|
+
return minimatch(path, pattern, { matchBase: true });
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Read an entry at a path
|
|
210
|
+
*/
|
|
211
|
+
async read(path, options) {
|
|
212
|
+
const normalizedPath = this.normalizePath(path);
|
|
213
|
+
const actionsMatch = normalizedPath.match(/^(.*)\/\.actions\/([^/]+)$/);
|
|
214
|
+
if (actionsMatch) {
|
|
215
|
+
const actionsListPath = `${actionsMatch[1] || ""}/.actions`;
|
|
216
|
+
const actionName = actionsMatch[2];
|
|
217
|
+
const listMatch = this.router.match(actionsListPath, "list");
|
|
218
|
+
if (listMatch) {
|
|
219
|
+
const ctx = {
|
|
220
|
+
path: actionsListPath,
|
|
221
|
+
params: listMatch.params,
|
|
222
|
+
options: {}
|
|
223
|
+
};
|
|
224
|
+
const handler = listMatch.route.handler;
|
|
225
|
+
const actionEntry = (await handler(ctx)).data.find((entry) => entry.summary === actionName || entry.id === actionName || entry.id.endsWith(`:${actionName}`) || entry.meta?.name === actionName);
|
|
226
|
+
if (actionEntry) return { data: actionEntry };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const match = this.router.match(normalizedPath, "read");
|
|
230
|
+
if (match) {
|
|
231
|
+
const ctx = {
|
|
232
|
+
path: normalizedPath,
|
|
233
|
+
params: match.params,
|
|
234
|
+
options
|
|
235
|
+
};
|
|
236
|
+
const handler = match.route.handler;
|
|
237
|
+
return { data: await handler(ctx) };
|
|
238
|
+
}
|
|
239
|
+
throw new AFSNotFoundError(normalizedPath);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Write an entry at a path
|
|
243
|
+
*/
|
|
244
|
+
async write(path, content, options) {
|
|
245
|
+
if (this.accessMode === "readonly") throw new AFSReadonlyError("Cannot write on a readonly provider");
|
|
246
|
+
const normalizedPath = this.normalizePath(path);
|
|
247
|
+
const match = this.router.match(normalizedPath, "write");
|
|
248
|
+
if (!match) throw new Error(`No write handler for path: ${path}`);
|
|
249
|
+
const ctx = {
|
|
250
|
+
path: normalizedPath,
|
|
251
|
+
params: match.params,
|
|
252
|
+
options
|
|
253
|
+
};
|
|
254
|
+
const handler = match.route.handler;
|
|
255
|
+
return handler(ctx, content);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Delete an entry at a path
|
|
259
|
+
*/
|
|
260
|
+
async delete(path, options) {
|
|
261
|
+
if (this.accessMode === "readonly") throw new AFSReadonlyError("Cannot delete on a readonly provider");
|
|
262
|
+
const normalizedPath = this.normalizePath(path);
|
|
263
|
+
const match = this.router.match(normalizedPath, "delete");
|
|
264
|
+
if (!match) throw new Error(`No delete handler for path: ${path}`);
|
|
265
|
+
const ctx = {
|
|
266
|
+
path: normalizedPath,
|
|
267
|
+
params: match.params,
|
|
268
|
+
options
|
|
269
|
+
};
|
|
270
|
+
const handler = match.route.handler;
|
|
271
|
+
return handler(ctx);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Search entries at a path
|
|
275
|
+
*/
|
|
276
|
+
async search(path, query, options) {
|
|
277
|
+
const normalizedPath = this.normalizePath(path);
|
|
278
|
+
const match = this.router.match(normalizedPath, "search");
|
|
279
|
+
if (!match) return { data: [] };
|
|
280
|
+
const ctx = {
|
|
281
|
+
path: normalizedPath,
|
|
282
|
+
params: match.params,
|
|
283
|
+
options
|
|
284
|
+
};
|
|
285
|
+
const handler = match.route.handler;
|
|
286
|
+
return handler(ctx, query, options);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Execute an action at a path
|
|
290
|
+
*/
|
|
291
|
+
async exec(path, args, options) {
|
|
292
|
+
if (this.accessMode === "readonly") throw new AFSReadonlyError("Cannot exec on a readonly provider");
|
|
293
|
+
const normalizedPath = this.normalizePath(path);
|
|
294
|
+
const match = this.router.match(normalizedPath, "exec");
|
|
295
|
+
if (!match) throw new Error(`No exec handler for path: ${path}`);
|
|
296
|
+
const ctx = {
|
|
297
|
+
path: normalizedPath,
|
|
298
|
+
params: match.params,
|
|
299
|
+
options
|
|
300
|
+
};
|
|
301
|
+
const handler = match.route.handler;
|
|
302
|
+
return handler(ctx, args);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get stat information for a path
|
|
306
|
+
*
|
|
307
|
+
* Note: Fallback to read() is handled at AFS core level, not here.
|
|
308
|
+
*/
|
|
309
|
+
async stat(path, options) {
|
|
310
|
+
const normalizedPath = this.normalizePath(path);
|
|
311
|
+
const match = this.router.match(normalizedPath, "stat");
|
|
312
|
+
if (match) {
|
|
313
|
+
const ctx = {
|
|
314
|
+
path: normalizedPath,
|
|
315
|
+
params: match.params,
|
|
316
|
+
options
|
|
317
|
+
};
|
|
318
|
+
const handler = match.route.handler;
|
|
319
|
+
return handler(ctx);
|
|
320
|
+
}
|
|
321
|
+
throw new AFSNotFoundError(normalizedPath);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get human-readable explanation for a path
|
|
325
|
+
*
|
|
326
|
+
* Note: Fallback to stat() is handled at AFS core level, not here.
|
|
327
|
+
*/
|
|
328
|
+
async explain(path, options) {
|
|
329
|
+
const normalizedPath = this.normalizePath(path);
|
|
330
|
+
const match = this.router.match(normalizedPath, "explain");
|
|
331
|
+
if (!match) throw new Error(`No explain handler for path: ${path}`);
|
|
332
|
+
const ctx = {
|
|
333
|
+
path: normalizedPath,
|
|
334
|
+
params: match.params,
|
|
335
|
+
options
|
|
336
|
+
};
|
|
337
|
+
const handler = match.route.handler;
|
|
338
|
+
return handler(ctx);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Rename/move a path
|
|
342
|
+
*/
|
|
343
|
+
async rename(oldPath, newPath, options) {
|
|
344
|
+
if (this.accessMode === "readonly") throw new AFSReadonlyError("Cannot rename on a readonly provider");
|
|
345
|
+
const normalizedPath = this.normalizePath(oldPath);
|
|
346
|
+
const match = this.router.match(normalizedPath, "rename");
|
|
347
|
+
if (!match) throw new AFSNotFoundError(normalizedPath);
|
|
348
|
+
const ctx = {
|
|
349
|
+
path: normalizedPath,
|
|
350
|
+
params: match.params,
|
|
351
|
+
options
|
|
352
|
+
};
|
|
353
|
+
const handler = match.route.handler;
|
|
354
|
+
return handler(ctx, newPath);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Normalize a path to ensure consistent format
|
|
358
|
+
* - Always starts with /
|
|
359
|
+
* - No trailing slash (except for root)
|
|
360
|
+
*/
|
|
361
|
+
normalizePath(path) {
|
|
362
|
+
if (!path || path === "/") return "/";
|
|
363
|
+
let normalized = path.startsWith("/") ? path : `/${path}`;
|
|
364
|
+
if (normalized.length > 1 && normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
365
|
+
return normalized;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Join path segments
|
|
369
|
+
*/
|
|
370
|
+
joinPath(...segments) {
|
|
371
|
+
return `/${segments.map((s) => s.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/")}`;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Build an AFSEntry helper
|
|
375
|
+
*
|
|
376
|
+
* @param path - The entry path (will be normalized)
|
|
377
|
+
* @param options - Entry options
|
|
378
|
+
* @param options.id - Entry ID. Defaults to normalized path if not provided
|
|
379
|
+
* @param options.content - Entry content
|
|
380
|
+
* @param options.meta - Entry metadata
|
|
381
|
+
* @param options.createdAt - Creation time. Undefined if not available from data source
|
|
382
|
+
* @param options.updatedAt - Last update time. Undefined if not available from data source
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```typescript
|
|
386
|
+
* // Simple entry with just path
|
|
387
|
+
* this.buildEntry("/items/1")
|
|
388
|
+
*
|
|
389
|
+
* // Entry with content and metadata
|
|
390
|
+
* this.buildEntry("/items/1", {
|
|
391
|
+
* content: { name: "Item 1" },
|
|
392
|
+
* meta: { size: 100 },
|
|
393
|
+
* })
|
|
394
|
+
*
|
|
395
|
+
* // Entry with all fields
|
|
396
|
+
* this.buildEntry("/items/1", {
|
|
397
|
+
* id: "custom-id",
|
|
398
|
+
* content: data,
|
|
399
|
+
* meta: { size: stats.size },
|
|
400
|
+
* createdAt: stats.birthtime,
|
|
401
|
+
* updatedAt: stats.mtime,
|
|
402
|
+
* })
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
buildEntry(path, options) {
|
|
406
|
+
const normalizedPath = this.normalizePath(path);
|
|
407
|
+
return {
|
|
408
|
+
id: options?.id ?? normalizedPath,
|
|
409
|
+
path: normalizedPath,
|
|
410
|
+
content: options?.content,
|
|
411
|
+
meta: options?.meta,
|
|
412
|
+
createdAt: options?.createdAt,
|
|
413
|
+
updatedAt: options?.updatedAt
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get the router (for testing/debugging)
|
|
418
|
+
*/
|
|
419
|
+
getRouter() {
|
|
420
|
+
return this.router;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
//#endregion
|
|
425
|
+
export { AFSBaseProvider };
|
|
426
|
+
//# sourceMappingURL=base.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.mjs","names":[],"sources":["../../src/provider/base.ts"],"sourcesContent":["import { minimatch } from \"minimatch\";\nimport type { OperationsDeclaration } from \"../capabilities/types.js\";\nimport { AFSNotFoundError, AFSReadonlyError } from \"../error.js\";\nimport type {\n AFSAccessMode,\n AFSDeleteOptions,\n AFSDeleteResult,\n AFSEntry,\n AFSEntryMetadata,\n AFSExecOptions,\n AFSExecResult,\n AFSExplainOptions,\n AFSExplainResult,\n AFSListOptions,\n AFSListResult,\n AFSModule,\n AFSReadOptions,\n AFSReadResult,\n AFSRenameOptions,\n AFSRenameResult,\n AFSSearchOptions,\n AFSSearchResult,\n AFSStatOptions,\n AFSStatResult,\n AFSWriteEntryPayload,\n AFSWriteOptions,\n AFSWriteResult,\n} from \"../type.js\";\nimport { getRoutes } from \"./decorators.js\";\nimport { ProviderRouter } from \"./router.js\";\nimport type {\n DeleteRouteHandler,\n ExecRouteHandler,\n ExplainRouteHandler,\n ListRouteHandler,\n ReadRouteHandler,\n RenameRouteHandler,\n RouteContext,\n RouteOperation,\n SearchRouteHandler,\n StatRouteHandler,\n WriteRouteHandler,\n} from \"./types.js\";\n\n/**\n * Abstract base class for AFS providers using virtual routing\n *\n * All providers extend this class and use decorators to define routes:\n * - @List(pattern) - Handle list operations\n * - @Read(pattern) - Handle read operations\n * - @Write(pattern) - Handle write operations\n * - @Delete(pattern) - Handle delete operations\n * - @Exec(pattern) - Handle exec operations\n * - @Search(pattern) - Handle search operations\n * - @Meta(pattern) - Handle .meta path reads\n * - @Actions(pattern) - Handle .actions path listing\n *\n * @example\n * ```typescript\n * class MyProvider extends AFSBaseProvider {\n * readonly name = \"my-provider\";\n *\n * @List(\"/\")\n * async listRoot(ctx: RouteContext) {\n * return { data: [...] };\n * }\n *\n * @Read(\"/:id\")\n * async getItem(ctx: RouteContext<{ id: string }>) {\n * return { id: ctx.params.id, path: ctx.path, content: ... };\n * }\n * }\n * ```\n */\nexport abstract class AFSBaseProvider implements AFSModule {\n /** Provider name, must be unique when mounted */\n abstract readonly name: string;\n\n /** Optional description */\n readonly description?: string;\n\n /** Access mode: \"readonly\" or \"readwrite\" */\n readonly accessMode: AFSAccessMode = \"readonly\";\n\n /** Internal router for path matching */\n private router: ProviderRouter;\n\n constructor() {\n this.router = new ProviderRouter();\n this.collectDecoratorRoutes();\n this.removeUnimplementedMethods();\n }\n\n /**\n * Collect routes from decorators and register them\n */\n private collectDecoratorRoutes(): void {\n const routes = getRoutes(this.constructor);\n\n for (const route of routes) {\n const handler = (this as any)[route.methodName];\n if (typeof handler !== \"function\") {\n console.warn(\n `[AFSBaseProvider] Method ${route.methodName} not found on ${this.constructor.name}`,\n );\n continue;\n }\n\n // Bind the handler to this instance\n const boundHandler = handler.bind(this);\n this.router.registerRoute(\n route.pattern,\n route.operation,\n boundHandler,\n route.description,\n route.listOptions,\n );\n }\n }\n\n /**\n * Remove methods for operations that have no routes registered.\n * This allows core to check if a provider supports a capability via typeof check.\n *\n * We set the method to undefined on the instance to shadow the prototype method,\n * since `delete` only removes instance properties, not inherited ones.\n */\n private removeUnimplementedMethods(): void {\n const operationToMethod: Record<string, keyof AFSBaseProvider> = {\n list: \"list\",\n read: \"read\",\n write: \"write\",\n delete: \"delete\",\n exec: \"exec\",\n search: \"search\",\n stat: \"stat\",\n explain: \"explain\",\n rename: \"rename\",\n };\n\n for (const [operation, methodName] of Object.entries(operationToMethod)) {\n const routes = this.router.getRoutesForOperation(operation as RouteOperation);\n\n // Keep the method only if there are routes registered for this operation\n if (routes.length === 0) {\n (this as any)[methodName] = undefined;\n }\n }\n }\n\n /**\n * Build an OperationsDeclaration based on which methods are implemented.\n * Uses the fact that removeUnimplementedMethods() sets unsupported methods to undefined.\n * Write/delete/exec/rename are also gated by accessMode.\n */\n protected getOperationsDeclaration(): OperationsDeclaration {\n const isReadwrite = this.accessMode === \"readwrite\";\n return {\n read: typeof this.read === \"function\",\n list: typeof this.list === \"function\",\n write: typeof this.write === \"function\" && isReadwrite,\n delete: typeof this.delete === \"function\" && isReadwrite,\n search: typeof this.search === \"function\",\n exec: typeof this.exec === \"function\",\n stat: typeof this.stat === \"function\",\n explain: typeof this.explain === \"function\",\n };\n }\n\n // ========== AFSModule Interface Implementation ==========\n\n /**\n * List entries at a path\n */\n async list(path: string, options?: AFSListOptions): Promise<AFSListResult> {\n const maxDepth = options?.maxDepth ?? 1;\n\n // maxDepth=0: return empty array (no children levels to expand)\n if (maxDepth === 0) {\n return { data: [] };\n }\n\n const normalizedPath = this.normalizePath(path);\n const match = this.router.match(normalizedPath, \"list\");\n\n if (!match) {\n throw new AFSNotFoundError(normalizedPath);\n }\n\n const limit = options?.limit;\n const handleDepth = match.route.listOptions?.handleDepth ?? false;\n\n if (handleDepth || maxDepth <= 1) {\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as ListRouteHandler;\n const handlerResult = await handler(ctx);\n\n const result: AFSListResult = {\n data: handlerResult.data,\n total: handlerResult.total,\n };\n\n // Apply limit if specified\n if (limit !== undefined && result.data.length > limit) {\n return {\n data: result.data.slice(0, limit),\n total: result.total ?? result.data.length,\n };\n }\n\n return result;\n }\n\n // Base provider handles depth expansion via BFS\n return this.listWithDepthExpansion(normalizedPath, options, maxDepth);\n }\n\n /**\n * Expand list results to specified depth using BFS traversal.\n * Used when handler has handleDepth: false.\n *\n * The handler is expected to return single-level results (direct children only).\n * This method recursively calls the handler to expand directories up to maxDepth.\n * Pattern filtering is applied after BFS completes to ensure directories are\n * traversed even if they don't match the pattern.\n */\n private async listWithDepthExpansion(\n basePath: string,\n options: AFSListOptions | undefined,\n maxDepth: number,\n ): Promise<AFSListResult> {\n const limit = options?.limit ?? 1000;\n const pattern = options?.pattern;\n\n const allEntries: AFSEntry[] = [];\n const visited = new Set<string>();\n\n // BFS queue: [path, currentDepth]\n // depth 0 = basePath itself, depth 1 = children, etc.\n const queue: Array<{ path: string; depth: number }> = [{ path: basePath, depth: 0 }];\n\n // Track entries before pattern filtering (need more than limit for filtering)\n const unfilteredEntries: AFSEntry[] = [];\n\n while (queue.length > 0) {\n const { path, depth } = queue.shift()!;\n\n // Prevent infinite loops\n if (visited.has(path)) continue;\n visited.add(path);\n\n // Call handler with depth=1 and WITHOUT pattern to get all entries for expansion\n const singleLevelOptions: AFSListOptions = {\n ...options,\n maxDepth: 1, // Always request single level from handler\n pattern: undefined, // Don't filter during BFS - filter at end\n };\n\n const match = this.router.match(path, \"list\");\n if (!match) continue;\n\n const ctx: RouteContext = {\n path,\n params: match.params,\n options: singleLevelOptions,\n };\n\n const handler = match.route.handler as ListRouteHandler;\n\n // Wrap in try-catch to handle files deleted during BFS traversal\n let result: Awaited<ReturnType<ListRouteHandler>>;\n try {\n result = await handler(ctx);\n } catch (error) {\n // Ignore not found errors - file may have been deleted during expansion\n const code = (error as NodeJS.ErrnoException).code;\n const isNotFound =\n code === \"ENOENT\" ||\n code === \"ENOTDIR\" ||\n (error instanceof Error && error.message.includes(\"not found\"));\n if (isNotFound) {\n continue;\n }\n throw error;\n }\n\n // Collect paths that should not be expanded (internal hint from handler)\n const noExpandSet = new Set(result.noExpand ?? []);\n\n // Note: maxChildren is passed to handler via options, so handler applies it to children\n // We don't apply it again here to avoid double-limiting\n\n // Add entries to results and queue directories for expansion\n for (const entry of result.data) {\n // Skip the current path entry for depth > 0 (it was already added as a child by parent)\n // At depth 0, include the root entry\n if (entry.path === path && depth > 0) {\n continue;\n }\n\n unfilteredEntries.push(entry);\n\n // Queue entry for expansion if:\n // 1. Within depth limit (depth < maxDepth means we can go one more level)\n // 2. Entry is a directory (childrenCount > 0 or -1, NOT undefined which means leaf)\n // 3. Entry is not the current path (which is already being processed)\n // 4. Entry is not in noExpand set (used for ignored directories)\n const childrenCount = entry.meta?.childrenCount;\n const isDirectory = childrenCount !== undefined && childrenCount !== 0;\n const shouldExpand = !noExpandSet.has(entry.path);\n\n if (depth < maxDepth - 1 && isDirectory && shouldExpand && entry.path !== path) {\n queue.push({ path: entry.path, depth: depth + 1 });\n }\n }\n }\n\n // Apply pattern filtering after BFS completes\n if (pattern) {\n for (const entry of unfilteredEntries) {\n if (allEntries.length >= limit) break;\n if (this.matchesPattern(entry.path, pattern)) {\n allEntries.push(entry);\n }\n }\n } else {\n // No pattern, apply limit directly\n for (const entry of unfilteredEntries) {\n if (allEntries.length >= limit) break;\n allEntries.push(entry);\n }\n }\n\n return {\n data: allEntries,\n total: allEntries.length >= limit ? undefined : allEntries.length,\n };\n }\n\n /**\n * Check if a path matches a glob pattern\n */\n private matchesPattern(path: string, pattern: string): boolean {\n return minimatch(path, pattern, { matchBase: true });\n }\n\n /**\n * Read an entry at a path\n */\n async read(path: string, options?: AFSReadOptions): Promise<AFSReadResult> {\n const normalizedPath = this.normalizePath(path);\n\n // Handle reading individual action definitions: */.actions/:actionName\n // This allows providers to expose action details via read() without\n // needing to define individual read handlers for each action\n const actionsMatch = normalizedPath.match(/^(.*)\\/\\.actions\\/([^/]+)$/);\n if (actionsMatch) {\n const actionsListPath = `${actionsMatch[1] || \"\"}/.actions`;\n const actionName = actionsMatch[2];\n\n // Try to find a list handler for the .actions path\n const listMatch = this.router.match(actionsListPath, \"list\");\n if (listMatch) {\n const ctx: RouteContext = {\n path: actionsListPath,\n params: listMatch.params,\n options: {},\n };\n const handler = listMatch.route.handler as ListRouteHandler;\n const result = await handler(ctx);\n\n // Find the action by name (check summary, id, or meta.name)\n const actionEntry = result.data.find(\n (entry) =>\n entry.summary === actionName ||\n entry.id === actionName ||\n entry.id.endsWith(`:${actionName}`) ||\n entry.meta?.name === actionName,\n );\n\n if (actionEntry) {\n return { data: actionEntry };\n }\n }\n }\n\n // Standard handler matching\n const match = this.router.match(normalizedPath, \"read\");\n if (match) {\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as ReadRouteHandler;\n const entry = await handler(ctx);\n\n return { data: entry };\n }\n\n throw new AFSNotFoundError(normalizedPath);\n }\n\n /**\n * Write an entry at a path\n */\n async write(\n path: string,\n content: AFSWriteEntryPayload,\n options?: AFSWriteOptions,\n ): Promise<AFSWriteResult> {\n if (this.accessMode === \"readonly\") {\n throw new AFSReadonlyError(\"Cannot write on a readonly provider\");\n }\n\n const normalizedPath = this.normalizePath(path);\n const match = this.router.match(normalizedPath, \"write\");\n\n if (!match) {\n throw new Error(`No write handler for path: ${path}`);\n }\n\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as WriteRouteHandler;\n return handler(ctx, content);\n }\n\n /**\n * Delete an entry at a path\n */\n async delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult> {\n if (this.accessMode === \"readonly\") {\n throw new AFSReadonlyError(\"Cannot delete on a readonly provider\");\n }\n\n const normalizedPath = this.normalizePath(path);\n const match = this.router.match(normalizedPath, \"delete\");\n\n if (!match) {\n throw new Error(`No delete handler for path: ${path}`);\n }\n\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as DeleteRouteHandler;\n return handler(ctx);\n }\n\n /**\n * Search entries at a path\n */\n async search(path: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult> {\n const normalizedPath = this.normalizePath(path);\n const match = this.router.match(normalizedPath, \"search\");\n\n if (!match) {\n return { data: [] };\n }\n\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as SearchRouteHandler;\n return handler(ctx, query, options);\n }\n\n /**\n * Execute an action at a path\n */\n async exec(\n path: string,\n args: Record<string, unknown>,\n options?: AFSExecOptions,\n ): Promise<AFSExecResult> {\n if (this.accessMode === \"readonly\") {\n throw new AFSReadonlyError(\"Cannot exec on a readonly provider\");\n }\n\n const normalizedPath = this.normalizePath(path);\n const match = this.router.match(normalizedPath, \"exec\");\n\n if (!match) {\n throw new Error(`No exec handler for path: ${path}`);\n }\n\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as ExecRouteHandler;\n return handler(ctx, args);\n }\n\n /**\n * Get stat information for a path\n *\n * Note: Fallback to read() is handled at AFS core level, not here.\n */\n async stat(path: string, options?: AFSStatOptions): Promise<AFSStatResult> {\n const normalizedPath = this.normalizePath(path);\n const match = this.router.match(normalizedPath, \"stat\");\n\n if (match) {\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as StatRouteHandler;\n return handler(ctx);\n }\n\n throw new AFSNotFoundError(normalizedPath);\n }\n\n /**\n * Get human-readable explanation for a path\n *\n * Note: Fallback to stat() is handled at AFS core level, not here.\n */\n async explain(path: string, options?: AFSExplainOptions): Promise<AFSExplainResult> {\n const normalizedPath = this.normalizePath(path);\n const match = this.router.match(normalizedPath, \"explain\");\n\n if (!match) {\n throw new Error(`No explain handler for path: ${path}`);\n }\n\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as ExplainRouteHandler;\n return handler(ctx);\n }\n\n /**\n * Rename/move a path\n */\n async rename(\n oldPath: string,\n newPath: string,\n options?: AFSRenameOptions,\n ): Promise<AFSRenameResult> {\n if (this.accessMode === \"readonly\") {\n throw new AFSReadonlyError(\"Cannot rename on a readonly provider\");\n }\n\n const normalizedPath = this.normalizePath(oldPath);\n const match = this.router.match(normalizedPath, \"rename\");\n\n if (!match) {\n throw new AFSNotFoundError(normalizedPath);\n }\n\n const ctx: RouteContext = {\n path: normalizedPath,\n params: match.params,\n options,\n };\n\n const handler = match.route.handler as RenameRouteHandler;\n return handler(ctx, newPath);\n }\n\n // ========== Utility Methods ==========\n\n /**\n * Normalize a path to ensure consistent format\n * - Always starts with /\n * - No trailing slash (except for root)\n */\n protected normalizePath(path: string): string {\n if (!path || path === \"/\") {\n return \"/\";\n }\n\n // Ensure leading slash\n let normalized = path.startsWith(\"/\") ? path : `/${path}`;\n\n // Remove trailing slash\n if (normalized.length > 1 && normalized.endsWith(\"/\")) {\n normalized = normalized.slice(0, -1);\n }\n\n return normalized;\n }\n\n /**\n * Join path segments\n */\n protected joinPath(...segments: string[]): string {\n const joined = segments\n .map((s) => s.replace(/^\\/+|\\/+$/g, \"\"))\n .filter(Boolean)\n .join(\"/\");\n\n return `/${joined}`;\n }\n\n /**\n * Build an AFSEntry helper\n *\n * @param path - The entry path (will be normalized)\n * @param options - Entry options\n * @param options.id - Entry ID. Defaults to normalized path if not provided\n * @param options.content - Entry content\n * @param options.meta - Entry metadata\n * @param options.createdAt - Creation time. Undefined if not available from data source\n * @param options.updatedAt - Last update time. Undefined if not available from data source\n *\n * @example\n * ```typescript\n * // Simple entry with just path\n * this.buildEntry(\"/items/1\")\n *\n * // Entry with content and metadata\n * this.buildEntry(\"/items/1\", {\n * content: { name: \"Item 1\" },\n * meta: { size: 100 },\n * })\n *\n * // Entry with all fields\n * this.buildEntry(\"/items/1\", {\n * id: \"custom-id\",\n * content: data,\n * meta: { size: stats.size },\n * createdAt: stats.birthtime,\n * updatedAt: stats.mtime,\n * })\n * ```\n */\n protected buildEntry(\n path: string,\n options?: {\n id?: string;\n content?: unknown;\n meta?: Partial<AFSEntryMetadata>;\n createdAt?: Date;\n updatedAt?: Date;\n },\n ): AFSEntry {\n const normalizedPath = this.normalizePath(path);\n return {\n id: options?.id ?? normalizedPath,\n path: normalizedPath,\n content: options?.content,\n meta: options?.meta as AFSEntryMetadata,\n createdAt: options?.createdAt,\n updatedAt: options?.updatedAt,\n };\n }\n\n /**\n * Get the router (for testing/debugging)\n */\n protected getRouter(): ProviderRouter {\n return this.router;\n }\n}\n"],"mappings":"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"}
|