@aigne/afs-toml 1.11.0-beta.6
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 +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/index.cjs +499 -0
- package/dist/index.d.cts +170 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +170 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +500 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential. Unauthorized copying, modification, distribution, or use of
|
|
7
|
+
this Software, via any medium, is strictly prohibited.
|
|
8
|
+
|
|
9
|
+
The Software is provided for internal use only within ArcBlock, Inc. and its
|
|
10
|
+
authorized affiliates.
|
|
11
|
+
|
|
12
|
+
## No License Granted
|
|
13
|
+
|
|
14
|
+
No license, express or implied, is granted to any party for any purpose.
|
|
15
|
+
All rights are reserved by ArcBlock, Inc.
|
|
16
|
+
|
|
17
|
+
## Public Artifact Distribution
|
|
18
|
+
|
|
19
|
+
Portions of this Software may be released publicly under separate open-source
|
|
20
|
+
licenses (such as MIT License) through designated public repositories. Such
|
|
21
|
+
public releases are governed by their respective licenses and do not affect
|
|
22
|
+
the proprietary nature of this repository.
|
|
23
|
+
|
|
24
|
+
## Contact
|
|
25
|
+
|
|
26
|
+
For licensing inquiries, contact: legal@arcblock.io
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
//#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
|
|
3
|
+
function __decorate(decorators, target, key, desc) {
|
|
4
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
5
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
6
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
7
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
exports.__decorate = __decorate;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
|
|
2
|
+
function __decorate(decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
export { __decorate };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
const require_decorate = require('./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs');
|
|
2
|
+
let node_fs_promises = require("node:fs/promises");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let _aigne_afs = require("@aigne/afs");
|
|
5
|
+
let _aigne_afs_provider = require("@aigne/afs/provider");
|
|
6
|
+
let _aigne_afs_utils_zod = require("@aigne/afs/utils/zod");
|
|
7
|
+
let smol_toml = require("smol-toml");
|
|
8
|
+
let ufo = require("ufo");
|
|
9
|
+
let zod = require("zod");
|
|
10
|
+
|
|
11
|
+
//#region src/index.ts
|
|
12
|
+
const LIST_MAX_LIMIT = 1e3;
|
|
13
|
+
/** Hidden key for storing AFS metadata (mirrors FS provider's .afs directory) */
|
|
14
|
+
const AFS_KEY = ".afs";
|
|
15
|
+
/** Subkey for storing metadata (mirrors FS provider's meta.yaml file) */
|
|
16
|
+
const META_KEY = "meta";
|
|
17
|
+
/** Subkey for storing child node metadata (mirrors FS provider's .nodes directory) */
|
|
18
|
+
const NODES_KEY = ".nodes";
|
|
19
|
+
const afsTOMLOptionsSchema = (0, _aigne_afs_utils_zod.camelize)(zod.z.object({
|
|
20
|
+
name: (0, _aigne_afs_utils_zod.optionalize)(zod.z.string()),
|
|
21
|
+
tomlPath: zod.z.string().describe("The path to the TOML file to mount"),
|
|
22
|
+
description: (0, _aigne_afs_utils_zod.optionalize)(zod.z.string().describe("A description of the TOML module")),
|
|
23
|
+
accessMode: (0, _aigne_afs_utils_zod.optionalize)(zod.z.enum(["readonly", "readwrite"]).describe("Access mode for this module"))
|
|
24
|
+
}));
|
|
25
|
+
/**
|
|
26
|
+
* AFS module for mounting TOML files as virtual file systems.
|
|
27
|
+
*
|
|
28
|
+
* TOML tables are treated as directories, and properties as files.
|
|
29
|
+
* Supports nested structures and path-based access to data values.
|
|
30
|
+
*/
|
|
31
|
+
var AFSTOML = class AFSTOML extends _aigne_afs_provider.AFSBaseProvider {
|
|
32
|
+
static schema() {
|
|
33
|
+
return afsTOMLOptionsSchema;
|
|
34
|
+
}
|
|
35
|
+
static async load({ filepath, parsed }) {
|
|
36
|
+
return new AFSTOML({
|
|
37
|
+
...await AFSTOML.schema().parseAsync(parsed),
|
|
38
|
+
cwd: (0, node_path.dirname)(filepath)
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
name;
|
|
42
|
+
description;
|
|
43
|
+
accessMode;
|
|
44
|
+
tomlData = null;
|
|
45
|
+
fileStats = {};
|
|
46
|
+
resolvedTomlPath;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
super();
|
|
49
|
+
this.options = options;
|
|
50
|
+
(0, _aigne_afs_utils_zod.zodParse)(afsTOMLOptionsSchema, options);
|
|
51
|
+
let tomlPath;
|
|
52
|
+
tomlPath = options.tomlPath.replaceAll("${CWD}", process.cwd());
|
|
53
|
+
if (tomlPath.startsWith("~/")) tomlPath = (0, node_path.join)(process.env.HOME || "", tomlPath.slice(2));
|
|
54
|
+
if (!(0, node_path.isAbsolute)(tomlPath)) tomlPath = (0, node_path.join)(options.cwd || process.cwd(), tomlPath);
|
|
55
|
+
let name = (0, node_path.basename)(tomlPath);
|
|
56
|
+
if (name.endsWith(".toml")) name = name.slice(0, -5);
|
|
57
|
+
this.name = options.name || name || "toml";
|
|
58
|
+
this.description = options.description;
|
|
59
|
+
this.accessMode = options.accessMode ?? "readwrite";
|
|
60
|
+
this.resolvedTomlPath = tomlPath;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read metadata for a TOML node via /.meta or /path/.meta
|
|
64
|
+
* Returns stored metadata merged with computed type information
|
|
65
|
+
* Note: Meta is read-only. To write metadata, use write() with payload.metadata.
|
|
66
|
+
*/
|
|
67
|
+
async readMetaHandler(ctx) {
|
|
68
|
+
await this.ensureLoaded();
|
|
69
|
+
const nodePath = (0, ufo.joinURL)("/", ctx.params.path ?? "");
|
|
70
|
+
const segments = this.getPathSegments(nodePath);
|
|
71
|
+
const value = this.getValueAtPath(this.tomlData, segments);
|
|
72
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(nodePath);
|
|
73
|
+
const isDir = this.isDirectoryValue(value);
|
|
74
|
+
const children = isDir ? this.getChildren(value) : [];
|
|
75
|
+
let type;
|
|
76
|
+
if (Array.isArray(value)) type = "array";
|
|
77
|
+
else if (value === null) type = "null";
|
|
78
|
+
else if (value instanceof Date) type = "datetime";
|
|
79
|
+
else if (typeof value === "object") type = "table";
|
|
80
|
+
else type = typeof value;
|
|
81
|
+
const storedMeta = this.loadMeta(nodePath) || {};
|
|
82
|
+
const computedMeta = {
|
|
83
|
+
type,
|
|
84
|
+
path: nodePath
|
|
85
|
+
};
|
|
86
|
+
if (isDir) {
|
|
87
|
+
computedMeta.childrenCount = children.length;
|
|
88
|
+
if (Array.isArray(value)) computedMeta.length = value.length;
|
|
89
|
+
else computedMeta.keys = Object.keys(value).filter((k) => !this.isMetaKey(k));
|
|
90
|
+
} else computedMeta.value = value;
|
|
91
|
+
if (this.fileStats.birthtime) computedMeta.created = this.fileStats.birthtime;
|
|
92
|
+
if (this.fileStats.mtime) computedMeta.modified = this.fileStats.mtime;
|
|
93
|
+
return this.buildEntry((0, ufo.joinURL)(nodePath, ".meta"), {
|
|
94
|
+
metadata: storedMeta,
|
|
95
|
+
content: computedMeta,
|
|
96
|
+
createdAt: this.fileStats.birthtime,
|
|
97
|
+
updatedAt: this.fileStats.mtime
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async listHandler(ctx) {
|
|
101
|
+
await this.ensureLoaded();
|
|
102
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
103
|
+
const options = ctx.options;
|
|
104
|
+
const limit = Math.min(options?.limit || LIST_MAX_LIMIT, LIST_MAX_LIMIT);
|
|
105
|
+
const maxChildren = typeof options?.maxChildren === "number" ? options.maxChildren : Number.MAX_SAFE_INTEGER;
|
|
106
|
+
const maxDepth = options?.maxDepth ?? 1;
|
|
107
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
108
|
+
const value = this.getValueAtPath(this.tomlData, segments);
|
|
109
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
110
|
+
const entries = [];
|
|
111
|
+
const queue = [{
|
|
112
|
+
path: normalizedPath,
|
|
113
|
+
value,
|
|
114
|
+
depth: 0
|
|
115
|
+
}];
|
|
116
|
+
while (queue.length > 0) {
|
|
117
|
+
const item = queue.shift();
|
|
118
|
+
if (!item) break;
|
|
119
|
+
const { path: itemPath, value: itemValue, depth } = item;
|
|
120
|
+
const entry = this.valueToAFSEntry(itemPath, itemValue);
|
|
121
|
+
entries.push(entry);
|
|
122
|
+
if (entries.length >= limit) break;
|
|
123
|
+
if (this.isDirectoryValue(itemValue) && depth < maxDepth) {
|
|
124
|
+
const children = this.getChildren(itemValue);
|
|
125
|
+
const childrenToProcess = children.length > maxChildren ? children.slice(0, maxChildren) : children;
|
|
126
|
+
for (const child of childrenToProcess) {
|
|
127
|
+
const childPath = itemPath === "/" ? `/${child.key}` : `${itemPath}/${child.key}`;
|
|
128
|
+
queue.push({
|
|
129
|
+
path: childPath,
|
|
130
|
+
value: child.value,
|
|
131
|
+
depth: depth + 1
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { data: entries };
|
|
137
|
+
}
|
|
138
|
+
async readHandler(ctx) {
|
|
139
|
+
await this.ensureLoaded();
|
|
140
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
141
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
142
|
+
const value = this.getValueAtPath(this.tomlData, segments);
|
|
143
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
144
|
+
return this.valueToAFSEntry(normalizedPath, value);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Write handler - supports writing content and/or metadata
|
|
148
|
+
*
|
|
149
|
+
* | payload | behavior |
|
|
150
|
+
* |---------|----------|
|
|
151
|
+
* | { content } | write content only |
|
|
152
|
+
* | { metadata } | write metadata only (to .afs storage) |
|
|
153
|
+
* | { content, metadata } | write both |
|
|
154
|
+
*/
|
|
155
|
+
async writeHandler(ctx, payload) {
|
|
156
|
+
await this.ensureLoaded();
|
|
157
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
158
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
159
|
+
if (payload.content !== void 0) this.setValueAtPath(this.tomlData, segments, payload.content);
|
|
160
|
+
if (payload.metadata !== void 0 && typeof payload.metadata === "object") {
|
|
161
|
+
const finalMeta = {
|
|
162
|
+
...this.loadMeta(normalizedPath) || {},
|
|
163
|
+
...payload.metadata
|
|
164
|
+
};
|
|
165
|
+
this.saveMeta(normalizedPath, finalMeta);
|
|
166
|
+
}
|
|
167
|
+
await this.saveToFile();
|
|
168
|
+
const newValue = this.getValueAtPath(this.tomlData, segments);
|
|
169
|
+
const isDir = this.isDirectoryValue(newValue);
|
|
170
|
+
const children = isDir ? this.getChildren(newValue) : [];
|
|
171
|
+
const storedMeta = this.loadMeta(normalizedPath) || {};
|
|
172
|
+
return { data: {
|
|
173
|
+
id: normalizedPath,
|
|
174
|
+
path: normalizedPath,
|
|
175
|
+
content: payload.content !== void 0 ? payload.content : newValue,
|
|
176
|
+
summary: payload.summary,
|
|
177
|
+
createdAt: this.fileStats.birthtime,
|
|
178
|
+
updatedAt: this.fileStats.mtime,
|
|
179
|
+
metadata: {
|
|
180
|
+
...storedMeta,
|
|
181
|
+
childrenCount: isDir ? children.length : void 0
|
|
182
|
+
},
|
|
183
|
+
userId: payload.userId,
|
|
184
|
+
sessionId: payload.sessionId,
|
|
185
|
+
linkTo: payload.linkTo
|
|
186
|
+
} };
|
|
187
|
+
}
|
|
188
|
+
async deleteHandler(ctx) {
|
|
189
|
+
await this.ensureLoaded();
|
|
190
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
191
|
+
const options = ctx.options;
|
|
192
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
193
|
+
const value = this.getValueAtPath(this.tomlData, segments);
|
|
194
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
195
|
+
if (this.isDirectoryValue(value) && this.getChildren(value).length > 0 && !options?.recursive) throw new Error(`Cannot delete directory '${normalizedPath}' without recursive option. Set recursive: true to delete directories.`);
|
|
196
|
+
this.deleteValueAtPath(this.tomlData, segments);
|
|
197
|
+
await this.saveToFile();
|
|
198
|
+
return { message: `Successfully deleted: ${normalizedPath}` };
|
|
199
|
+
}
|
|
200
|
+
async renameHandler(ctx, newPath) {
|
|
201
|
+
await this.ensureLoaded();
|
|
202
|
+
const normalizedOldPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
203
|
+
const normalizedNewPath = this.normalizePath(newPath);
|
|
204
|
+
const options = ctx.options;
|
|
205
|
+
const oldSegments = this.getPathSegments(normalizedOldPath);
|
|
206
|
+
const newSegments = this.getPathSegments(normalizedNewPath);
|
|
207
|
+
const oldValue = this.getValueAtPath(this.tomlData, oldSegments);
|
|
208
|
+
if (oldValue === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedOldPath);
|
|
209
|
+
if (this.getValueAtPath(this.tomlData, newSegments) !== void 0 && !options?.overwrite) throw new Error(`Destination '${normalizedNewPath}' already exists. Set overwrite: true to replace it.`);
|
|
210
|
+
this.setValueAtPath(this.tomlData, newSegments, oldValue);
|
|
211
|
+
this.deleteValueAtPath(this.tomlData, oldSegments);
|
|
212
|
+
await this.saveToFile();
|
|
213
|
+
return { message: `Successfully renamed '${normalizedOldPath}' to '${normalizedNewPath}'` };
|
|
214
|
+
}
|
|
215
|
+
async searchHandler(ctx, query, options) {
|
|
216
|
+
await this.ensureLoaded();
|
|
217
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
218
|
+
const limit = Math.min(options?.limit || LIST_MAX_LIMIT, LIST_MAX_LIMIT);
|
|
219
|
+
const caseSensitive = options?.caseSensitive ?? false;
|
|
220
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
221
|
+
const rootValue = this.getValueAtPath(this.tomlData, segments);
|
|
222
|
+
if (rootValue === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
223
|
+
const entries = [];
|
|
224
|
+
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
225
|
+
const searchInValue = (valuePath, value) => {
|
|
226
|
+
if (entries.length >= limit) return;
|
|
227
|
+
let matched = false;
|
|
228
|
+
if (!this.isDirectoryValue(value)) {
|
|
229
|
+
const valueStr = typeof value === "string" ? value : JSON.stringify(value);
|
|
230
|
+
if ((caseSensitive ? valueStr : valueStr.toLowerCase()).includes(searchQuery)) matched = true;
|
|
231
|
+
}
|
|
232
|
+
if (matched) entries.push(this.valueToAFSEntry(valuePath, value));
|
|
233
|
+
if (this.isDirectoryValue(value)) {
|
|
234
|
+
const children = this.getChildren(value);
|
|
235
|
+
for (const child of children) {
|
|
236
|
+
if (entries.length >= limit) break;
|
|
237
|
+
searchInValue(valuePath === "/" ? `/${child.key}` : `${valuePath}/${child.key}`, child.value);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
searchInValue(normalizedPath, rootValue);
|
|
242
|
+
return {
|
|
243
|
+
data: entries,
|
|
244
|
+
message: entries.length >= limit ? `Results truncated to limit ${limit}` : void 0
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async statHandler(ctx) {
|
|
248
|
+
await this.ensureLoaded();
|
|
249
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
250
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
251
|
+
const value = this.getValueAtPath(this.tomlData, segments);
|
|
252
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
253
|
+
const isDir = this.isDirectoryValue(value);
|
|
254
|
+
const children = isDir ? this.getChildren(value) : [];
|
|
255
|
+
const meta = this.loadMeta(normalizedPath);
|
|
256
|
+
return { data: {
|
|
257
|
+
path: normalizedPath,
|
|
258
|
+
childrenCount: isDir ? children.length : void 0,
|
|
259
|
+
created: this.fileStats.birthtime,
|
|
260
|
+
modified: this.fileStats.mtime,
|
|
261
|
+
meta: meta ?? void 0
|
|
262
|
+
} };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Check if a key is a hidden meta key that should be filtered from listings
|
|
266
|
+
*/
|
|
267
|
+
isMetaKey(key) {
|
|
268
|
+
return key === AFS_KEY;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Load metadata for a node.
|
|
272
|
+
*
|
|
273
|
+
* Storage location depends on node type (mirrors FS provider's .afs structure):
|
|
274
|
+
* - Objects: `.afs.meta` key within the object itself
|
|
275
|
+
* - Primitives: parent's `.afs[".nodes"][key].meta`
|
|
276
|
+
*/
|
|
277
|
+
loadMeta(nodePath) {
|
|
278
|
+
const segments = this.getPathSegments(nodePath);
|
|
279
|
+
const value = this.getValueAtPath(this.tomlData, segments);
|
|
280
|
+
if (value === void 0) return null;
|
|
281
|
+
if (this.isDirectoryValue(value) && !Array.isArray(value)) {
|
|
282
|
+
const afs$1 = value[AFS_KEY];
|
|
283
|
+
if (afs$1 && typeof afs$1 === "object" && !Array.isArray(afs$1)) {
|
|
284
|
+
const meta$1 = afs$1[META_KEY];
|
|
285
|
+
if (meta$1 && typeof meta$1 === "object" && !Array.isArray(meta$1)) return meta$1;
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
if (segments.length === 0) return null;
|
|
290
|
+
const parentSegments = segments.slice(0, -1);
|
|
291
|
+
const nodeKey = segments[segments.length - 1];
|
|
292
|
+
const parentValue = this.getValueAtPath(this.tomlData, parentSegments);
|
|
293
|
+
if (!parentValue || Array.isArray(parentValue) || typeof parentValue !== "object") return null;
|
|
294
|
+
const afs = parentValue[AFS_KEY];
|
|
295
|
+
if (!afs || typeof afs !== "object" || Array.isArray(afs)) return null;
|
|
296
|
+
const nodes = afs[NODES_KEY];
|
|
297
|
+
if (!nodes || typeof nodes !== "object" || Array.isArray(nodes)) return null;
|
|
298
|
+
const nodeEntry = nodes[nodeKey];
|
|
299
|
+
if (!nodeEntry || typeof nodeEntry !== "object" || Array.isArray(nodeEntry)) return null;
|
|
300
|
+
const meta = nodeEntry[META_KEY];
|
|
301
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return null;
|
|
302
|
+
return meta;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Save metadata for a node.
|
|
306
|
+
*
|
|
307
|
+
* Storage location depends on node type (mirrors FS provider's .afs structure):
|
|
308
|
+
* - Objects: `.afs.meta` key within the object itself
|
|
309
|
+
* - Primitives: parent's `.afs[".nodes"][key].meta`
|
|
310
|
+
*/
|
|
311
|
+
saveMeta(nodePath, meta) {
|
|
312
|
+
const segments = this.getPathSegments(nodePath);
|
|
313
|
+
const value = this.getValueAtPath(this.tomlData, segments);
|
|
314
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(nodePath);
|
|
315
|
+
if (this.isDirectoryValue(value) && !Array.isArray(value)) {
|
|
316
|
+
const obj = value;
|
|
317
|
+
if (!obj[AFS_KEY]) obj[AFS_KEY] = {};
|
|
318
|
+
obj[AFS_KEY][META_KEY] = meta;
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (segments.length === 0) throw new Error("Cannot save meta for root when root is not an object");
|
|
322
|
+
const parentSegments = segments.slice(0, -1);
|
|
323
|
+
const nodeKey = segments[segments.length - 1];
|
|
324
|
+
const parentValue = this.getValueAtPath(this.tomlData, parentSegments);
|
|
325
|
+
if (!parentValue || typeof parentValue !== "object") throw new Error(`Parent path is not an object`);
|
|
326
|
+
if (Array.isArray(parentValue)) throw new Error(`Cannot save meta for array elements`);
|
|
327
|
+
const parentObj = parentValue;
|
|
328
|
+
if (!parentObj[AFS_KEY]) parentObj[AFS_KEY] = {};
|
|
329
|
+
const afs = parentObj[AFS_KEY];
|
|
330
|
+
if (!afs[NODES_KEY]) afs[NODES_KEY] = {};
|
|
331
|
+
const nodes = afs[NODES_KEY];
|
|
332
|
+
if (!nodes[nodeKey]) nodes[nodeKey] = {};
|
|
333
|
+
nodes[nodeKey][META_KEY] = meta;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Load TOML data from file. Called lazily on first access.
|
|
337
|
+
*/
|
|
338
|
+
async ensureLoaded() {
|
|
339
|
+
if (this.tomlData !== null) return;
|
|
340
|
+
try {
|
|
341
|
+
const stats = await (0, node_fs_promises.stat)(this.resolvedTomlPath);
|
|
342
|
+
this.fileStats = {
|
|
343
|
+
birthtime: stats.birthtime,
|
|
344
|
+
mtime: stats.mtime
|
|
345
|
+
};
|
|
346
|
+
this.tomlData = (0, smol_toml.parse)(await (0, node_fs_promises.readFile)(this.resolvedTomlPath, "utf8"));
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (error.code === "ENOENT") this.tomlData = {};
|
|
349
|
+
else throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Save TOML data back to file. Only called in readwrite mode.
|
|
354
|
+
*/
|
|
355
|
+
async saveToFile() {
|
|
356
|
+
const content = (0, smol_toml.stringify)(this.tomlData);
|
|
357
|
+
await (0, node_fs_promises.writeFile)(this.resolvedTomlPath, content, "utf8");
|
|
358
|
+
const stats = await (0, node_fs_promises.stat)(this.resolvedTomlPath);
|
|
359
|
+
this.fileStats = {
|
|
360
|
+
birthtime: this.fileStats.birthtime || stats.birthtime,
|
|
361
|
+
mtime: stats.mtime
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Get path segments from normalized path
|
|
366
|
+
*/
|
|
367
|
+
getPathSegments(path) {
|
|
368
|
+
const normalized = this.normalizePath(path);
|
|
369
|
+
if (normalized === "/") return [];
|
|
370
|
+
return normalized.slice(1).split("/");
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Navigate to a value in the TOML structure using path segments
|
|
374
|
+
*/
|
|
375
|
+
getValueAtPath(data, segments) {
|
|
376
|
+
let current = data;
|
|
377
|
+
for (const segment of segments) {
|
|
378
|
+
if (current == null) return void 0;
|
|
379
|
+
if (Array.isArray(current)) {
|
|
380
|
+
const index = Number.parseInt(segment, 10);
|
|
381
|
+
if (Number.isNaN(index) || index < 0 || index >= current.length) return;
|
|
382
|
+
current = current[index];
|
|
383
|
+
} else if (typeof current === "object") current = current[segment];
|
|
384
|
+
else return;
|
|
385
|
+
}
|
|
386
|
+
return current;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Set a value in the TOML structure at the given path
|
|
390
|
+
*/
|
|
391
|
+
setValueAtPath(data, segments, value) {
|
|
392
|
+
if (segments.length === 0) throw new Error("Cannot set value at root path");
|
|
393
|
+
let current = data;
|
|
394
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
395
|
+
const segment = segments[i];
|
|
396
|
+
const nextSegment = segments[i + 1];
|
|
397
|
+
if (Array.isArray(current)) {
|
|
398
|
+
const index = Number.parseInt(segment, 10);
|
|
399
|
+
if (Number.isNaN(index) || index < 0) throw new Error(`Invalid array index: ${segment}`);
|
|
400
|
+
while (current.length <= index) current.push(null);
|
|
401
|
+
if (current[index] == null) current[index] = !Number.isNaN(Number.parseInt(nextSegment, 10)) ? [] : {};
|
|
402
|
+
current = current[index];
|
|
403
|
+
} else if (typeof current === "object" && current !== null) {
|
|
404
|
+
const obj = current;
|
|
405
|
+
if (obj[segment] == null) obj[segment] = !Number.isNaN(Number.parseInt(nextSegment, 10)) ? [] : {};
|
|
406
|
+
current = obj[segment];
|
|
407
|
+
} else throw new Error(`Cannot set property on non-object at ${segments.slice(0, i + 1).join("/")}`);
|
|
408
|
+
}
|
|
409
|
+
const lastSegment = segments[segments.length - 1];
|
|
410
|
+
if (Array.isArray(current)) {
|
|
411
|
+
const index = Number.parseInt(lastSegment, 10);
|
|
412
|
+
if (Number.isNaN(index) || index < 0) throw new Error(`Invalid array index: ${lastSegment}`);
|
|
413
|
+
current[index] = value;
|
|
414
|
+
} else if (typeof current === "object" && current !== null) current[lastSegment] = value;
|
|
415
|
+
else throw new Error("Cannot set property on non-object");
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Delete a value from the TOML structure at the given path
|
|
419
|
+
*/
|
|
420
|
+
deleteValueAtPath(data, segments) {
|
|
421
|
+
if (segments.length === 0) throw new Error("Cannot delete root path");
|
|
422
|
+
let current = data;
|
|
423
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
424
|
+
const segment = segments[i];
|
|
425
|
+
if (Array.isArray(current)) {
|
|
426
|
+
const index = Number.parseInt(segment, 10);
|
|
427
|
+
if (Number.isNaN(index) || index < 0 || index >= current.length) return false;
|
|
428
|
+
current = current[index];
|
|
429
|
+
} else if (typeof current === "object" && current !== null) {
|
|
430
|
+
const obj = current;
|
|
431
|
+
if (!(segment in obj)) return false;
|
|
432
|
+
current = obj[segment];
|
|
433
|
+
} else return false;
|
|
434
|
+
}
|
|
435
|
+
const lastSegment = segments[segments.length - 1];
|
|
436
|
+
if (Array.isArray(current)) {
|
|
437
|
+
const index = Number.parseInt(lastSegment, 10);
|
|
438
|
+
if (Number.isNaN(index) || index < 0 || index >= current.length) return false;
|
|
439
|
+
current.splice(index, 1);
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
if (typeof current === "object" && current !== null) {
|
|
443
|
+
const obj = current;
|
|
444
|
+
if (!(lastSegment in obj)) return false;
|
|
445
|
+
delete obj[lastSegment];
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Check if a value is a "directory" (object or array with children)
|
|
452
|
+
*/
|
|
453
|
+
isDirectoryValue(value) {
|
|
454
|
+
if (Array.isArray(value)) return true;
|
|
455
|
+
if (typeof value === "object" && value !== null) {
|
|
456
|
+
if (value instanceof Date) return false;
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Get children of a directory value (filters out .afs meta key)
|
|
463
|
+
*/
|
|
464
|
+
getChildren(value) {
|
|
465
|
+
if (Array.isArray(value)) return value.map((item, index) => ({
|
|
466
|
+
key: String(index),
|
|
467
|
+
value: item
|
|
468
|
+
}));
|
|
469
|
+
if (typeof value === "object" && value !== null && !(value instanceof Date)) return Object.entries(value).filter(([key]) => !this.isMetaKey(key)).map(([key, val]) => ({
|
|
470
|
+
key,
|
|
471
|
+
value: val
|
|
472
|
+
}));
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Convert a TOML value to an AFSEntry
|
|
477
|
+
*/
|
|
478
|
+
valueToAFSEntry(path, value) {
|
|
479
|
+
const isDir = this.isDirectoryValue(value);
|
|
480
|
+
const children = isDir ? this.getChildren(value) : [];
|
|
481
|
+
return this.buildEntry(path, {
|
|
482
|
+
content: isDir ? void 0 : value,
|
|
483
|
+
metadata: { childrenCount: isDir ? children.length : void 0 },
|
|
484
|
+
createdAt: this.fileStats.birthtime,
|
|
485
|
+
updatedAt: this.fileStats.mtime
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/:path*")], AFSTOML.prototype, "readMetaHandler", null);
|
|
490
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.List)("/:path*", { handleDepth: true })], AFSTOML.prototype, "listHandler", null);
|
|
491
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/:path*")], AFSTOML.prototype, "readHandler", null);
|
|
492
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Write)("/:path*")], AFSTOML.prototype, "writeHandler", null);
|
|
493
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Delete)("/:path*")], AFSTOML.prototype, "deleteHandler", null);
|
|
494
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Rename)("/:path*")], AFSTOML.prototype, "renameHandler", null);
|
|
495
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Search)("/:path*")], AFSTOML.prototype, "searchHandler", null);
|
|
496
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Stat)("/:path*")], AFSTOML.prototype, "statHandler", null);
|
|
497
|
+
|
|
498
|
+
//#endregion
|
|
499
|
+
exports.AFSTOML = AFSTOML;
|