@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.
Files changed (164) hide show
  1. package/LICENSE.md +17 -84
  2. package/README.md +4 -13
  3. package/dist/_virtual/rolldown_runtime.mjs +7 -0
  4. package/dist/afs.cjs +1330 -0
  5. package/dist/afs.d.cts +275 -0
  6. package/dist/afs.d.cts.map +1 -0
  7. package/dist/afs.d.mts +275 -0
  8. package/dist/afs.d.mts.map +1 -0
  9. package/dist/afs.mjs +1331 -0
  10. package/dist/afs.mjs.map +1 -0
  11. package/dist/capabilities/index.d.mts +2 -0
  12. package/dist/capabilities/types.d.cts +100 -0
  13. package/dist/capabilities/types.d.cts.map +1 -0
  14. package/dist/capabilities/types.d.mts +100 -0
  15. package/dist/capabilities/types.d.mts.map +1 -0
  16. package/dist/capabilities/world-mapping.cjs +20 -0
  17. package/dist/capabilities/world-mapping.d.cts +139 -0
  18. package/dist/capabilities/world-mapping.d.cts.map +1 -0
  19. package/dist/capabilities/world-mapping.d.mts +139 -0
  20. package/dist/capabilities/world-mapping.d.mts.map +1 -0
  21. package/dist/capabilities/world-mapping.mjs +20 -0
  22. package/dist/capabilities/world-mapping.mjs.map +1 -0
  23. package/dist/error.cjs +63 -0
  24. package/dist/error.d.cts +39 -0
  25. package/dist/error.d.cts.map +1 -0
  26. package/dist/error.d.mts +39 -0
  27. package/dist/error.d.mts.map +1 -0
  28. package/dist/error.mjs +59 -0
  29. package/dist/error.mjs.map +1 -0
  30. package/dist/index.cjs +72 -345
  31. package/dist/index.d.cts +18 -300
  32. package/dist/index.d.mts +20 -300
  33. package/dist/index.mjs +16 -342
  34. package/dist/loader/index.cjs +110 -0
  35. package/dist/loader/index.d.cts +48 -0
  36. package/dist/loader/index.d.cts.map +1 -0
  37. package/dist/loader/index.d.mts +48 -0
  38. package/dist/loader/index.d.mts.map +1 -0
  39. package/dist/loader/index.mjs +110 -0
  40. package/dist/loader/index.mjs.map +1 -0
  41. package/dist/meta/index.cjs +4 -0
  42. package/dist/meta/index.mjs +6 -0
  43. package/dist/meta/kind.cjs +161 -0
  44. package/dist/meta/kind.d.cts +134 -0
  45. package/dist/meta/kind.d.cts.map +1 -0
  46. package/dist/meta/kind.d.mts +134 -0
  47. package/dist/meta/kind.d.mts.map +1 -0
  48. package/dist/meta/kind.mjs +157 -0
  49. package/dist/meta/kind.mjs.map +1 -0
  50. package/dist/meta/path.cjs +116 -0
  51. package/dist/meta/path.d.cts +43 -0
  52. package/dist/meta/path.d.cts.map +1 -0
  53. package/dist/meta/path.d.mts +43 -0
  54. package/dist/meta/path.d.mts.map +1 -0
  55. package/dist/meta/path.mjs +112 -0
  56. package/dist/meta/path.mjs.map +1 -0
  57. package/dist/meta/type.d.cts +96 -0
  58. package/dist/meta/type.d.cts.map +1 -0
  59. package/dist/meta/type.d.mts +96 -0
  60. package/dist/meta/type.d.mts.map +1 -0
  61. package/dist/meta/validation.cjs +77 -0
  62. package/dist/meta/validation.d.cts +19 -0
  63. package/dist/meta/validation.d.cts.map +1 -0
  64. package/dist/meta/validation.d.mts +19 -0
  65. package/dist/meta/validation.d.mts.map +1 -0
  66. package/dist/meta/validation.mjs +77 -0
  67. package/dist/meta/validation.mjs.map +1 -0
  68. package/dist/meta/well-known-kinds.cjs +228 -0
  69. package/dist/meta/well-known-kinds.d.cts +52 -0
  70. package/dist/meta/well-known-kinds.d.cts.map +1 -0
  71. package/dist/meta/well-known-kinds.d.mts +52 -0
  72. package/dist/meta/well-known-kinds.d.mts.map +1 -0
  73. package/dist/meta/well-known-kinds.mjs +219 -0
  74. package/dist/meta/well-known-kinds.mjs.map +1 -0
  75. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.cts +141 -0
  76. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.cts.map +1 -0
  77. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.mts +141 -0
  78. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.mts.map +1 -0
  79. package/dist/path.cjs +255 -0
  80. package/dist/path.d.cts +93 -0
  81. package/dist/path.d.cts.map +1 -0
  82. package/dist/path.d.mts +93 -0
  83. package/dist/path.d.mts.map +1 -0
  84. package/dist/path.mjs +249 -0
  85. package/dist/path.mjs.map +1 -0
  86. package/dist/provider/base.cjs +425 -0
  87. package/dist/provider/base.d.cts +175 -0
  88. package/dist/provider/base.d.cts.map +1 -0
  89. package/dist/provider/base.d.mts +175 -0
  90. package/dist/provider/base.d.mts.map +1 -0
  91. package/dist/provider/base.mjs +426 -0
  92. package/dist/provider/base.mjs.map +1 -0
  93. package/dist/provider/decorators.cjs +268 -0
  94. package/dist/provider/decorators.d.cts +244 -0
  95. package/dist/provider/decorators.d.cts.map +1 -0
  96. package/dist/provider/decorators.d.mts +244 -0
  97. package/dist/provider/decorators.d.mts.map +1 -0
  98. package/dist/provider/decorators.mjs +256 -0
  99. package/dist/provider/decorators.mjs.map +1 -0
  100. package/dist/provider/index.cjs +19 -0
  101. package/dist/provider/index.d.cts +5 -0
  102. package/dist/provider/index.d.mts +5 -0
  103. package/dist/provider/index.mjs +5 -0
  104. package/dist/provider/router.cjs +185 -0
  105. package/dist/provider/router.d.cts +50 -0
  106. package/dist/provider/router.d.cts.map +1 -0
  107. package/dist/provider/router.d.mts +50 -0
  108. package/dist/provider/router.d.mts.map +1 -0
  109. package/dist/provider/router.mjs +185 -0
  110. package/dist/provider/router.mjs.map +1 -0
  111. package/dist/provider/types.d.cts +113 -0
  112. package/dist/provider/types.d.cts.map +1 -0
  113. package/dist/provider/types.d.mts +113 -0
  114. package/dist/provider/types.d.mts.map +1 -0
  115. package/dist/registry.cjs +358 -0
  116. package/dist/registry.d.cts +96 -0
  117. package/dist/registry.d.cts.map +1 -0
  118. package/dist/registry.d.mts +96 -0
  119. package/dist/registry.d.mts.map +1 -0
  120. package/dist/registry.mjs +360 -0
  121. package/dist/registry.mjs.map +1 -0
  122. package/dist/type.cjs +34 -0
  123. package/dist/type.d.cts +420 -0
  124. package/dist/type.d.cts.map +1 -0
  125. package/dist/type.d.mts +420 -0
  126. package/dist/type.d.mts.map +1 -0
  127. package/dist/type.mjs +33 -0
  128. package/dist/type.mjs.map +1 -0
  129. package/dist/utils/camelize.d.cts.map +1 -1
  130. package/dist/utils/camelize.d.mts.map +1 -1
  131. package/dist/utils/schema.cjs +129 -0
  132. package/dist/utils/schema.d.cts +65 -0
  133. package/dist/utils/schema.d.cts.map +1 -0
  134. package/dist/utils/schema.d.mts +65 -0
  135. package/dist/utils/schema.d.mts.map +1 -0
  136. package/dist/utils/schema.mjs +124 -0
  137. package/dist/utils/schema.mjs.map +1 -0
  138. package/dist/utils/type-utils.d.cts.map +1 -1
  139. package/dist/utils/type-utils.d.mts.map +1 -1
  140. package/dist/utils/uri-template.cjs +123 -0
  141. package/dist/utils/uri-template.d.cts +48 -0
  142. package/dist/utils/uri-template.d.cts.map +1 -0
  143. package/dist/utils/uri-template.d.mts +48 -0
  144. package/dist/utils/uri-template.d.mts.map +1 -0
  145. package/dist/utils/uri-template.mjs +120 -0
  146. package/dist/utils/uri-template.mjs.map +1 -0
  147. package/dist/utils/uri.cjs +49 -0
  148. package/dist/utils/uri.d.cts +34 -0
  149. package/dist/utils/uri.d.cts.map +1 -0
  150. package/dist/utils/uri.d.mts +34 -0
  151. package/dist/utils/uri.d.mts.map +1 -0
  152. package/dist/utils/uri.mjs +49 -0
  153. package/dist/utils/uri.mjs.map +1 -0
  154. package/dist/utils/zod.cjs +6 -8
  155. package/dist/utils/zod.d.cts +2 -2
  156. package/dist/utils/zod.d.cts.map +1 -1
  157. package/dist/utils/zod.d.mts +2 -2
  158. package/dist/utils/zod.d.mts.map +1 -1
  159. package/dist/utils/zod.mjs +6 -8
  160. package/dist/utils/zod.mjs.map +1 -1
  161. package/package.json +27 -4
  162. package/dist/index.d.cts.map +0 -1
  163. package/dist/index.d.mts.map +0 -1
  164. 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"}