@remodlai/lexiqfs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fuse.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import Fuse from 'fuse-native';
2
+
3
+ type MountFuseParams = {
4
+ mountPoint: string;
5
+ libsqlUrl: string;
6
+ authToken?: string;
7
+ namespaces: Record<string, string>;
8
+ };
9
+ declare function mountFuse(params: MountFuseParams): Promise<Fuse>;
10
+
11
+ export { type MountFuseParams, mountFuse };
package/dist/fuse.js ADDED
@@ -0,0 +1,459 @@
1
+ // src/fuse/daemon.ts
2
+ import Fuse from "fuse-native";
3
+ import { configure, fs } from "@zenfs/core";
4
+
5
+ // src/backends/libsql/backend.ts
6
+ import { createClient } from "@libsql/client";
7
+ import { Async, FileSystem, InMemory } from "@zenfs/core";
8
+ function errnoError(code, path, syscall) {
9
+ const err = Object.assign(new Error(`${code}: ${syscall || "unknown"} '${path || ""}'`), { code, path, syscall });
10
+ return err;
11
+ }
12
+ var DEFAULT_FILE_MODE = 33188;
13
+ var DEFAULT_DIR_MODE = 16877;
14
+ var LibSQLFS = class extends Async(FileSystem) {
15
+ _sync = InMemory.create({});
16
+ client;
17
+ organizationId;
18
+ agentId;
19
+ label;
20
+ constructor(client, options) {
21
+ super(19539, "libsqlfs");
22
+ this.client = client;
23
+ this.organizationId = options.organizationId;
24
+ this.agentId = options.agentId ?? null;
25
+ this.label = options.label;
26
+ }
27
+ /**
28
+ * Initialize schema and seed root directory.
29
+ * Called before ready(), which triggers Async() crossCopy.
30
+ */
31
+ async initialize() {
32
+ await this.client.execute(`
33
+ CREATE TABLE IF NOT EXISTS files (
34
+ path TEXT PRIMARY KEY,
35
+ content BLOB,
36
+ mode INTEGER NOT NULL DEFAULT ${DEFAULT_FILE_MODE},
37
+ uid INTEGER NOT NULL DEFAULT 1000,
38
+ gid INTEGER NOT NULL DEFAULT 1000,
39
+ size INTEGER NOT NULL DEFAULT 0,
40
+ atime TEXT NOT NULL,
41
+ mtime TEXT NOT NULL,
42
+ ctime TEXT NOT NULL,
43
+ birthtime TEXT NOT NULL,
44
+ organization_id TEXT,
45
+ agent_id TEXT
46
+ )
47
+ `);
48
+ await this.client.execute(
49
+ `CREATE INDEX IF NOT EXISTS idx_files_path_prefix ON files(path)`
50
+ );
51
+ const root = await this.client.execute(`SELECT 1 FROM files WHERE path = '/'`);
52
+ if (!root.rows.length) {
53
+ const now = (/* @__PURE__ */ new Date()).toISOString();
54
+ await this.client.execute({
55
+ sql: `INSERT INTO files (path, content, mode, uid, gid, size, atime, mtime, ctime, birthtime, organization_id, agent_id)
56
+ VALUES ('/', NULL, ?, 1000, 1000, 0, ?, ?, ?, ?, ?, ?)`,
57
+ args: [DEFAULT_DIR_MODE, now, now, now, now, this.organizationId, this.agentId]
58
+ });
59
+ }
60
+ }
61
+ /**
62
+ * Override ready() to run initialize(), then let Async() crossCopy.
63
+ */
64
+ async ready() {
65
+ await this.initialize();
66
+ await super.ready();
67
+ }
68
+ // -- async methods that Async(FileSystem) requires --
69
+ async rename(oldPath, newPath) {
70
+ await this.client.execute({
71
+ sql: `UPDATE files SET path = ? WHERE path = ?`,
72
+ args: [newPath, oldPath]
73
+ });
74
+ }
75
+ async stat(path) {
76
+ const result = await this.client.execute({
77
+ sql: `SELECT mode, size, atime, mtime, ctime, birthtime, uid, gid FROM files WHERE path = ?`,
78
+ args: [path]
79
+ });
80
+ if (!result.rows.length) {
81
+ throw errnoError("ENOENT", path, "stat");
82
+ }
83
+ const r = result.rows[0];
84
+ return {
85
+ mode: r.mode,
86
+ size: r.size,
87
+ atimeMs: new Date(r.atime).getTime(),
88
+ mtimeMs: new Date(r.mtime).getTime(),
89
+ ctimeMs: new Date(r.ctime).getTime(),
90
+ birthtimeMs: new Date(r.birthtime).getTime(),
91
+ uid: r.uid,
92
+ gid: r.gid,
93
+ ino: 0,
94
+ nlink: 1
95
+ };
96
+ }
97
+ async touch(path, metadata) {
98
+ const now = (/* @__PURE__ */ new Date()).toISOString();
99
+ const atime = metadata.atimeMs ? new Date(metadata.atimeMs).toISOString() : now;
100
+ const mtime = metadata.mtimeMs ? new Date(metadata.mtimeMs).toISOString() : now;
101
+ const ctime = metadata.ctimeMs ? new Date(metadata.ctimeMs).toISOString() : now;
102
+ await this.client.execute({
103
+ sql: `UPDATE files SET atime = ?, mtime = ?, ctime = ?, organization_id = ?, agent_id = ? WHERE path = ?`,
104
+ args: [atime, mtime, ctime, this.organizationId, this.agentId, path]
105
+ });
106
+ }
107
+ async createFile(path, options) {
108
+ const now = (/* @__PURE__ */ new Date()).toISOString();
109
+ const mode = options.mode ?? DEFAULT_FILE_MODE;
110
+ const uid = options.uid ?? 1e3;
111
+ const gid = options.gid ?? 1e3;
112
+ await this.client.execute({
113
+ sql: `INSERT INTO files (path, content, mode, uid, gid, size, atime, mtime, ctime, birthtime, organization_id, agent_id)
114
+ VALUES (?, NULL, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)
115
+ ON CONFLICT (path) DO UPDATE SET mode = excluded.mode, mtime = excluded.mtime, ctime = excluded.ctime`,
116
+ args: [path, mode, uid, gid, now, now, now, now, this.organizationId, this.agentId]
117
+ });
118
+ return {
119
+ mode,
120
+ size: 0,
121
+ uid,
122
+ gid,
123
+ atimeMs: Date.now(),
124
+ mtimeMs: Date.now(),
125
+ ctimeMs: Date.now(),
126
+ birthtimeMs: Date.now(),
127
+ ino: 0,
128
+ nlink: 1
129
+ };
130
+ }
131
+ async unlink(path) {
132
+ await this.client.execute({ sql: `DELETE FROM files WHERE path = ?`, args: [path] });
133
+ }
134
+ async rmdir(path) {
135
+ await this.client.execute({ sql: `DELETE FROM files WHERE path = ?`, args: [path] });
136
+ }
137
+ async mkdir(path, options) {
138
+ const now = (/* @__PURE__ */ new Date()).toISOString();
139
+ const mode = options.mode ?? DEFAULT_DIR_MODE;
140
+ const uid = options.uid ?? 1e3;
141
+ const gid = options.gid ?? 1e3;
142
+ await this.client.execute({
143
+ sql: `INSERT INTO files (path, content, mode, uid, gid, size, atime, mtime, ctime, birthtime, organization_id, agent_id)
144
+ VALUES (?, NULL, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)
145
+ ON CONFLICT (path) DO NOTHING`,
146
+ args: [path, mode, uid, gid, now, now, now, now, this.organizationId, this.agentId]
147
+ });
148
+ return {
149
+ mode,
150
+ size: 0,
151
+ uid,
152
+ gid,
153
+ atimeMs: Date.now(),
154
+ mtimeMs: Date.now(),
155
+ ctimeMs: Date.now(),
156
+ birthtimeMs: Date.now(),
157
+ ino: 0,
158
+ nlink: 1
159
+ };
160
+ }
161
+ async readdir(path) {
162
+ const prefix = path === "/" ? "/" : path.endsWith("/") ? path : path + "/";
163
+ let sql;
164
+ let args;
165
+ if (prefix === "/") {
166
+ sql = `SELECT path FROM files WHERE path != '/' AND path LIKE '/%' AND path NOT LIKE '/%/%'`;
167
+ args = [];
168
+ } else {
169
+ sql = `SELECT path FROM files WHERE path LIKE ? AND path NOT LIKE ?`;
170
+ args = [prefix + "%", prefix + "%/%"];
171
+ }
172
+ const result = await this.client.execute({ sql, args });
173
+ return result.rows.map((r) => r.path.slice(prefix.length)).filter((name) => name.length > 0);
174
+ }
175
+ async link(_target, _link) {
176
+ throw errnoError("ENOSYS", _target, "link");
177
+ }
178
+ async sync() {
179
+ }
180
+ async read(path, buffer, start, end) {
181
+ const result = await this.client.execute({
182
+ sql: `SELECT content FROM files WHERE path = ?`,
183
+ args: [path]
184
+ });
185
+ const raw = result.rows[0]?.content;
186
+ if (raw === null || raw === void 0) return;
187
+ let content;
188
+ if (raw instanceof Uint8Array) {
189
+ content = raw;
190
+ } else if (raw instanceof ArrayBuffer) {
191
+ content = new Uint8Array(raw);
192
+ } else if (ArrayBuffer.isView(raw)) {
193
+ content = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
194
+ } else if (typeof raw === "string") {
195
+ const binary = atob(raw);
196
+ content = new Uint8Array(binary.length);
197
+ for (let i = 0; i < binary.length; i++) content[i] = binary.charCodeAt(i);
198
+ } else {
199
+ return;
200
+ }
201
+ const slice = content.subarray(start, end);
202
+ buffer.set(slice);
203
+ }
204
+ async write(path, buffer, offset) {
205
+ const result = await this.client.execute({
206
+ sql: `SELECT content FROM files WHERE path = ?`,
207
+ args: [path]
208
+ });
209
+ let existing = new Uint8Array(0);
210
+ const raw = result.rows[0]?.content;
211
+ if (raw instanceof Uint8Array) {
212
+ existing = new Uint8Array(raw);
213
+ } else if (raw instanceof ArrayBuffer) {
214
+ existing = new Uint8Array(raw);
215
+ } else if (ArrayBuffer.isView(raw)) {
216
+ existing = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
217
+ }
218
+ const newSize = Math.max(existing.length, offset + buffer.length);
219
+ const merged = new Uint8Array(newSize);
220
+ merged.set(existing);
221
+ merged.set(buffer, offset);
222
+ const now = (/* @__PURE__ */ new Date()).toISOString();
223
+ await this.client.execute({
224
+ sql: `INSERT INTO files (path, content, mode, uid, gid, size, atime, mtime, ctime, birthtime, organization_id, agent_id)
225
+ VALUES (?, ?, ${DEFAULT_FILE_MODE}, 1000, 1000, ?, ?, ?, ?, ?, ?, ?)
226
+ ON CONFLICT (path) DO UPDATE SET
227
+ content = excluded.content, size = excluded.size,
228
+ mtime = excluded.mtime, ctime = excluded.ctime,
229
+ organization_id = excluded.organization_id, agent_id = excluded.agent_id`,
230
+ args: [path, merged, merged.length, now, now, now, now, this.organizationId, this.agentId]
231
+ });
232
+ }
233
+ };
234
+ var LibSQLBackend = {
235
+ name: "LibSQL",
236
+ options: {
237
+ url: { type: "string", required: false },
238
+ syncUrl: { type: "string", required: false },
239
+ authToken: { type: "string", required: false },
240
+ organizationId: { type: "string", required: true },
241
+ agentId: { type: ["string", "undefined"], required: false },
242
+ label: { type: "string", required: false },
243
+ maxSize: { type: "number", required: false }
244
+ },
245
+ async create(options) {
246
+ const url = options.url || options.syncUrl || ":memory:";
247
+ const client = createClient({ url, authToken: options.authToken });
248
+ const fs2 = new LibSQLFS(client, options);
249
+ return fs2;
250
+ },
251
+ async isAvailable() {
252
+ try {
253
+ const { createClient: _c } = await import("@libsql/client");
254
+ return typeof _c === "function";
255
+ } catch {
256
+ return false;
257
+ }
258
+ }
259
+ };
260
+
261
+ // src/fuse/daemon.ts
262
+ var _nextFd = 42;
263
+ function allocFd() {
264
+ return _nextFd++;
265
+ }
266
+ async function mountFuse(params) {
267
+ const firstEntry = Object.entries(params.namespaces)[0];
268
+ if (!firstEntry) throw new Error("mountFuse: at least one namespace required");
269
+ const [, namespace] = firstEntry;
270
+ await configure({
271
+ mounts: {
272
+ "/": {
273
+ backend: LibSQLBackend,
274
+ url: `https://${namespace}.${params.libsqlUrl}`,
275
+ authToken: params.authToken,
276
+ organizationId: namespace,
277
+ disableAsyncCache: true
278
+ }
279
+ }
280
+ });
281
+ console.log(`[fuse] backend ready: ${params.mountPoint}`);
282
+ const fuse = new Fuse(params.mountPoint, {
283
+ readdir(path, cb) {
284
+ (async () => {
285
+ try {
286
+ const entries = await fs.promises.readdir(path);
287
+ cb(0, entries);
288
+ } catch {
289
+ cb(Fuse.ENOENT);
290
+ }
291
+ })();
292
+ },
293
+ getattr(path, cb) {
294
+ (async () => {
295
+ try {
296
+ const stat = await fs.promises.stat(path);
297
+ cb(0, stat);
298
+ } catch {
299
+ cb(Fuse.ENOENT);
300
+ }
301
+ })();
302
+ },
303
+ open(_path, _flags, cb) {
304
+ cb(0, allocFd());
305
+ },
306
+ fgetattr(path, _fd, cb) {
307
+ (async () => {
308
+ try {
309
+ const stat = await fs.promises.stat(path);
310
+ cb(0, stat);
311
+ } catch {
312
+ cb(Fuse.ENOENT);
313
+ }
314
+ })();
315
+ },
316
+ release(_path, _fd, cb) {
317
+ cb(0);
318
+ },
319
+ lstat(path, cb) {
320
+ (async () => {
321
+ try {
322
+ const stat = await fs.promises.stat(path);
323
+ cb(0, stat);
324
+ } catch {
325
+ cb(Fuse.ENOENT);
326
+ }
327
+ })();
328
+ },
329
+ readlink(_path, cb) {
330
+ cb(-22);
331
+ },
332
+ access(path, _mode, cb) {
333
+ (async () => {
334
+ try {
335
+ await fs.promises.stat(path);
336
+ cb(0);
337
+ } catch {
338
+ cb(Fuse.ENOENT);
339
+ }
340
+ })();
341
+ },
342
+ read(path, _fd, buf, len, pos, cb) {
343
+ (async () => {
344
+ try {
345
+ const data = Buffer.from(await fs.promises.readFile(path));
346
+ const slice = data.slice(pos, pos + len);
347
+ slice.copy(buf);
348
+ cb(slice.length);
349
+ } catch {
350
+ cb(-Fuse.EIO);
351
+ }
352
+ })();
353
+ },
354
+ write(path, _fd, buf, len, pos, cb) {
355
+ (async () => {
356
+ try {
357
+ let existing;
358
+ try {
359
+ existing = Buffer.from(await fs.promises.readFile(path));
360
+ } catch {
361
+ existing = Buffer.alloc(0);
362
+ }
363
+ const data = Buffer.from(buf).slice(0, len);
364
+ let final;
365
+ if (pos === 0 && existing.length <= len) {
366
+ final = data;
367
+ } else {
368
+ const newBuf = Buffer.alloc(Math.max(existing.length, pos + len));
369
+ existing.copy(newBuf);
370
+ data.copy(newBuf, pos);
371
+ final = newBuf;
372
+ }
373
+ await fs.promises.writeFile(path, final);
374
+ cb(len);
375
+ } catch (err) {
376
+ console.error(`[fuse] write error: ${path}`, err);
377
+ cb(-Fuse.EIO);
378
+ }
379
+ })();
380
+ },
381
+ mkdir(path, _mode, cb) {
382
+ (async () => {
383
+ try {
384
+ await fs.promises.mkdir(path);
385
+ cb(0);
386
+ } catch (e) {
387
+ if (e?.code === "EEXIST") {
388
+ cb(0);
389
+ } else {
390
+ cb(Fuse.ENOENT);
391
+ }
392
+ }
393
+ })();
394
+ },
395
+ create(path, _mode, cb) {
396
+ (async () => {
397
+ try {
398
+ await fs.promises.writeFile(path, Buffer.alloc(0));
399
+ cb(0, allocFd());
400
+ } catch (err) {
401
+ console.error(`[fuse] create error: ${path}`, err);
402
+ cb(Fuse.EACCES, -1);
403
+ }
404
+ })();
405
+ },
406
+ unlink(path, cb) {
407
+ (async () => {
408
+ try {
409
+ await fs.promises.unlink(path);
410
+ cb(0);
411
+ } catch {
412
+ cb(Fuse.ENOENT);
413
+ }
414
+ })();
415
+ },
416
+ rmdir(path, cb) {
417
+ (async () => {
418
+ try {
419
+ await fs.promises.rmdir(path);
420
+ cb(0);
421
+ } catch {
422
+ cb(Fuse.ENOENT);
423
+ }
424
+ })();
425
+ },
426
+ rename(src, dst, cb) {
427
+ (async () => {
428
+ try {
429
+ const content = await fs.promises.readFile(src);
430
+ await fs.promises.writeFile(dst, content);
431
+ await fs.promises.unlink(src);
432
+ cb(0);
433
+ } catch {
434
+ cb(Fuse.ENOENT);
435
+ }
436
+ })();
437
+ }
438
+ }, { debug: false, force: true, allowOther: true });
439
+ await new Promise((resolve, reject) => {
440
+ fuse.mount((err) => err ? reject(err) : resolve());
441
+ });
442
+ console.log(`[fuse] mounted ZenFS at ${params.mountPoint}`);
443
+ let shuttingDown = false;
444
+ const gracefulShutdown = () => {
445
+ if (shuttingDown) return;
446
+ shuttingDown = true;
447
+ console.log("[fuse] shutting down...");
448
+ fuse.unmount(() => {
449
+ console.log("[fuse] unmounted");
450
+ process.exit(0);
451
+ });
452
+ };
453
+ process.on("SIGINT", gracefulShutdown);
454
+ process.on("SIGTERM", gracefulShutdown);
455
+ return fuse;
456
+ }
457
+ export {
458
+ mountFuse
459
+ };