@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,425 @@
|
|
|
1
|
+
const require_error = require('../error.cjs');
|
|
2
|
+
const require_decorators = require('./decorators.cjs');
|
|
3
|
+
const require_router = require('./router.cjs');
|
|
4
|
+
let minimatch = require("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 require_router.ProviderRouter();
|
|
46
|
+
this.collectDecoratorRoutes();
|
|
47
|
+
this.removeUnimplementedMethods();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Collect routes from decorators and register them
|
|
51
|
+
*/
|
|
52
|
+
collectDecoratorRoutes() {
|
|
53
|
+
const routes = require_decorators.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 require_error.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 (0, minimatch.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 require_error.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 require_error.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 require_error.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 require_error.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 require_error.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 require_error.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 require_error.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
|
+
exports.AFSBaseProvider = AFSBaseProvider;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { AFSExplainResult } from "../meta/type.cjs";
|
|
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.cjs";
|
|
3
|
+
import { OperationsDeclaration } from "../capabilities/types.cjs";
|
|
4
|
+
import { ProviderRouter } from "./router.cjs";
|
|
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.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.cts","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"}
|