@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/index.js ADDED
@@ -0,0 +1,914 @@
1
+ // src/index.ts
2
+ export * from "@zenfs/core";
3
+
4
+ // src/backends/libsql/backend.ts
5
+ import { createClient } from "@libsql/client";
6
+
7
+ // src/backends/libsql/transaction.ts
8
+ var AsyncTransactionBase = class {
9
+ asyncDone = Promise.resolve();
10
+ /**
11
+ * Run an async operation from sync context
12
+ */
13
+ async(promise) {
14
+ this.asyncDone = this.asyncDone.then(() => promise);
15
+ }
16
+ };
17
+ var LibSQLTransaction = class extends AsyncTransactionBase {
18
+ constructor(store, client, organizationId, agentId, onCommit) {
19
+ super();
20
+ this.store = store;
21
+ this.client = client;
22
+ this.organizationId = organizationId;
23
+ this.agentId = agentId;
24
+ this.onCommit = onCommit;
25
+ }
26
+ cache = /* @__PURE__ */ new Map();
27
+ pendingWrites = /* @__PURE__ */ new Map();
28
+ pendingDeletes = /* @__PURE__ */ new Set();
29
+ committed = false;
30
+ /**
31
+ * Get all keys (file paths) in the store
32
+ */
33
+ async keys() {
34
+ await this.asyncDone;
35
+ const result = await this.client.execute({
36
+ sql: `SELECT path FROM files
37
+ WHERE organization_id = ? AND (agent_id = ? OR (agent_id IS NULL AND ? IS NULL))`,
38
+ args: [this.organizationId, this.agentId, this.agentId]
39
+ });
40
+ return result.rows.map((row) => row.path);
41
+ }
42
+ /**
43
+ * Get data for a file path
44
+ *
45
+ * @param path - The file path to look up (e.g., '/src/main.ts')
46
+ * @param offset - Start offset in the data
47
+ * @param end - End offset (exclusive), or undefined for rest of data
48
+ */
49
+ async get(path, offset, end) {
50
+ await this.asyncDone;
51
+ if (this.pendingDeletes.has(path)) {
52
+ return void 0;
53
+ }
54
+ if (this.pendingWrites.has(path)) {
55
+ const data2 = this.pendingWrites.get(path);
56
+ return this.sliceData(data2, offset, end);
57
+ }
58
+ if (this.cache.has(path)) {
59
+ const cached = this.cache.get(path);
60
+ return cached ? this.sliceData(cached, offset, end) : void 0;
61
+ }
62
+ const result = await this.client.execute({
63
+ sql: `SELECT content FROM files
64
+ WHERE path = ? AND organization_id = ?
65
+ AND (agent_id = ? OR (agent_id IS NULL AND ? IS NULL))`,
66
+ args: [path, this.organizationId, this.agentId, this.agentId]
67
+ });
68
+ if (result.rows.length === 0) {
69
+ this.cache.set(path, void 0);
70
+ return void 0;
71
+ }
72
+ const rawData = result.rows[0].content;
73
+ let data;
74
+ if (rawData === null || rawData === void 0) {
75
+ data = void 0;
76
+ } else if (rawData instanceof Uint8Array) {
77
+ data = rawData;
78
+ } else if (rawData instanceof ArrayBuffer) {
79
+ data = new Uint8Array(rawData);
80
+ } else if (typeof rawData === "string") {
81
+ data = this.decodeBase64(rawData);
82
+ } else if (ArrayBuffer.isView(rawData)) {
83
+ data = new Uint8Array(rawData.buffer, rawData.byteOffset, rawData.byteLength);
84
+ } else {
85
+ console.warn("Unknown data type from libSQL:", typeof rawData);
86
+ data = void 0;
87
+ }
88
+ this.cache.set(path, data);
89
+ return data ? this.sliceData(data, offset, end) : void 0;
90
+ }
91
+ /**
92
+ * Synchronous get - uses cache only
93
+ * Throws EAGAIN if data not in cache
94
+ */
95
+ getSync(path, offset, end) {
96
+ if (this.pendingDeletes.has(path)) {
97
+ return void 0;
98
+ }
99
+ if (this.pendingWrites.has(path)) {
100
+ const data = this.pendingWrites.get(path);
101
+ return this.sliceData(data, offset, end);
102
+ }
103
+ if (this.cache.has(path)) {
104
+ const cached = this.cache.get(path);
105
+ return cached ? this.sliceData(cached, offset, end) : void 0;
106
+ }
107
+ this.async(this.get(path, offset, end));
108
+ const error = new Error("EAGAIN: Resource temporarily unavailable");
109
+ error.code = "EAGAIN";
110
+ throw error;
111
+ }
112
+ /**
113
+ * Set data for a file path
114
+ *
115
+ * @param path - The file path (e.g., '/src/main.ts')
116
+ * @param data - The data to store
117
+ * @param offset - Offset at which to write (0 for full replacement)
118
+ */
119
+ async set(path, data, offset) {
120
+ await this.asyncDone;
121
+ this.pendingDeletes.delete(path);
122
+ let finalData;
123
+ if (offset === 0) {
124
+ finalData = data;
125
+ } else {
126
+ const existing = await this.get(path, 0) ?? new Uint8Array(0);
127
+ const newSize = Math.max(existing.length, offset + data.length);
128
+ finalData = new Uint8Array(newSize);
129
+ finalData.set(existing);
130
+ finalData.set(data, offset);
131
+ }
132
+ this.pendingWrites.set(path, finalData);
133
+ this.cache.set(path, finalData);
134
+ }
135
+ /**
136
+ * Synchronous set - queues for async write
137
+ */
138
+ setSync(path, data, offset) {
139
+ this.async(this.set(path, data, offset));
140
+ }
141
+ /**
142
+ * Remove a file path from the store
143
+ */
144
+ async remove(path) {
145
+ await this.asyncDone;
146
+ this.pendingWrites.delete(path);
147
+ this.pendingDeletes.add(path);
148
+ this.cache.set(path, void 0);
149
+ }
150
+ /**
151
+ * Synchronous remove - queues for async delete
152
+ */
153
+ removeSync(path) {
154
+ this.async(this.remove(path));
155
+ }
156
+ /**
157
+ * Commit all pending changes to the database
158
+ */
159
+ async commit() {
160
+ if (this.committed) return;
161
+ await this.asyncDone;
162
+ const now = (/* @__PURE__ */ new Date()).toISOString();
163
+ const events = [];
164
+ const timestamp = Date.now();
165
+ for (const [path, data] of this.pendingWrites) {
166
+ await this.client.execute({
167
+ sql: `INSERT INTO files (path, organization_id, agent_id, content, mode, uid, gid, size, atime, mtime, ctime, birthtime)
168
+ VALUES (?, ?, ?, ?, 33188, 1000, 1000, ?, ?, ?, ?, ?)
169
+ ON CONFLICT (path, organization_id, agent_id) DO UPDATE SET
170
+ content = excluded.content,
171
+ size = excluded.size,
172
+ mtime = excluded.mtime,
173
+ ctime = excluded.ctime`,
174
+ args: [path, this.organizationId, this.agentId, data, data.length, now, now, now, now]
175
+ });
176
+ events.push({ eventType: "change", path, timestamp });
177
+ }
178
+ for (const path of this.pendingDeletes) {
179
+ await this.client.execute({
180
+ sql: `DELETE FROM files
181
+ WHERE path = ? AND organization_id = ?
182
+ AND (agent_id = ? OR (agent_id IS NULL AND ? IS NULL))`,
183
+ args: [path, this.organizationId, this.agentId, this.agentId]
184
+ });
185
+ events.push({ eventType: "rename", path, timestamp });
186
+ }
187
+ this.committed = true;
188
+ this.pendingWrites.clear();
189
+ this.pendingDeletes.clear();
190
+ if (events.length > 0 && this.onCommit) {
191
+ this.onCommit(events);
192
+ }
193
+ }
194
+ /**
195
+ * Abort - discard pending changes
196
+ */
197
+ abort() {
198
+ this.committed = true;
199
+ this.pendingWrites.clear();
200
+ this.pendingDeletes.clear();
201
+ this.cache.clear();
202
+ }
203
+ /**
204
+ * Slice data according to offset/end
205
+ */
206
+ sliceData(data, offset, end) {
207
+ if (offset === 0 && end === void 0) {
208
+ return data;
209
+ }
210
+ return data.subarray(offset, end);
211
+ }
212
+ /**
213
+ * Decode base64 string to Uint8Array
214
+ */
215
+ decodeBase64(str) {
216
+ const binary = atob(str);
217
+ const bytes = new Uint8Array(binary.length);
218
+ for (let i = 0; i < binary.length; i++) {
219
+ bytes[i] = binary.charCodeAt(i);
220
+ }
221
+ return bytes;
222
+ }
223
+ /**
224
+ * Symbol.asyncDispose for using/await using
225
+ */
226
+ async [Symbol.asyncDispose]() {
227
+ if (!this.committed) {
228
+ this.abort();
229
+ }
230
+ }
231
+ /**
232
+ * Symbol.dispose for using
233
+ */
234
+ [Symbol.dispose]() {
235
+ if (!this.committed) {
236
+ this.abort();
237
+ }
238
+ }
239
+ };
240
+
241
+ // src/process/base/event-emitter.ts
242
+ var BrowserEventEmitter = class {
243
+ events = {};
244
+ maxListeners = 10;
245
+ setMaxListeners(n) {
246
+ this.maxListeners = n;
247
+ return this;
248
+ }
249
+ on(event, listener) {
250
+ if (!this.events[event]) {
251
+ this.events[event] = [];
252
+ }
253
+ if (this.events[event].length >= this.maxListeners) {
254
+ console.warn(`MaxListenersExceededWarning: Possible memory leak detected. ${this.events[event].length} listeners added.`);
255
+ }
256
+ this.events[event].push(listener);
257
+ return this;
258
+ }
259
+ off(event, listener) {
260
+ return this.removeListener(event, listener);
261
+ }
262
+ emit(event, ...args) {
263
+ if (!this.events[event]) return false;
264
+ this.events[event].forEach((listener) => listener(...args));
265
+ return true;
266
+ }
267
+ removeListener(event, listener) {
268
+ if (!this.events[event]) return this;
269
+ this.events[event] = this.events[event].filter((l) => l !== listener);
270
+ return this;
271
+ }
272
+ };
273
+
274
+ // src/backends/libsql/store.ts
275
+ var LibSQLStore = class {
276
+ type = 1819505011;
277
+ // 'lsqs' in hex
278
+ name = "libsqlfs";
279
+ label;
280
+ uuid;
281
+ flags = ["partial"];
282
+ // We support partial reads/writes
283
+ client;
284
+ organizationId;
285
+ agentId;
286
+ maxSize;
287
+ initialized = false;
288
+ eventEmitter = new BrowserEventEmitter();
289
+ constructor(client, options) {
290
+ this.client = client;
291
+ this.organizationId = options.organizationId;
292
+ this.agentId = options.agentId ?? null;
293
+ this.label = options.label;
294
+ this.maxSize = options.maxSize ?? 4 * 1024 * 1024 * 1024;
295
+ }
296
+ /**
297
+ * Initialize the database schema if needed
298
+ */
299
+ async initialize() {
300
+ if (this.initialized) return;
301
+ await this.client.execute(`
302
+ CREATE TABLE IF NOT EXISTS files (
303
+ path TEXT NOT NULL,
304
+ organization_id TEXT NOT NULL,
305
+ agent_id TEXT,
306
+ content BLOB,
307
+ mode INTEGER NOT NULL DEFAULT 33188,
308
+ uid INTEGER NOT NULL DEFAULT 1000,
309
+ gid INTEGER NOT NULL DEFAULT 1000,
310
+ size INTEGER NOT NULL DEFAULT 0,
311
+ atime TEXT NOT NULL,
312
+ mtime TEXT NOT NULL,
313
+ ctime TEXT NOT NULL,
314
+ birthtime TEXT NOT NULL,
315
+ canonical_path TEXT,
316
+ PRIMARY KEY (path, organization_id, agent_id)
317
+ )
318
+ `);
319
+ await this.client.execute(`
320
+ CREATE INDEX IF NOT EXISTS idx_files_org_agent
321
+ ON files(organization_id, agent_id)
322
+ `);
323
+ await this.client.execute(`
324
+ CREATE INDEX IF NOT EXISTS idx_files_path_prefix
325
+ ON files(path, organization_id, agent_id)
326
+ `);
327
+ await this.client.execute(`
328
+ CREATE TABLE IF NOT EXISTS fs_metadata (
329
+ organization_id TEXT NOT NULL,
330
+ agent_id TEXT,
331
+ metadata TEXT NOT NULL,
332
+ PRIMARY KEY (organization_id, agent_id)
333
+ )
334
+ `);
335
+ this.initialized = true;
336
+ }
337
+ /**
338
+ * Sync the embedded replica with the remote server
339
+ * For libSQL embedded replicas, this triggers a sync operation
340
+ */
341
+ async sync() {
342
+ if ("sync" in this.client && typeof this.client.sync === "function") {
343
+ await this.client.sync();
344
+ }
345
+ }
346
+ /**
347
+ * Create a new transaction for atomic operations
348
+ *
349
+ * ZenFS StoreFS uses transactions for all operations:
350
+ * - tx.get(id, offset, end) - read data
351
+ * - tx.set(id, data, offset) - write data
352
+ * - tx.remove(id) - delete data
353
+ * - tx.commit() - persist changes
354
+ */
355
+ transaction() {
356
+ return new LibSQLTransaction(
357
+ this,
358
+ this.client,
359
+ this.organizationId,
360
+ this.agentId,
361
+ (events) => this.handleCommitEvents(events)
362
+ );
363
+ }
364
+ /**
365
+ * Handle events from committed transactions
366
+ */
367
+ handleCommitEvents(events) {
368
+ for (const event of events) {
369
+ this.eventEmitter.emit("change", event);
370
+ }
371
+ }
372
+ /**
373
+ * Subscribe to file change events
374
+ * @returns Unsubscribe function
375
+ */
376
+ onFileChange(callback) {
377
+ this.eventEmitter.on("change", callback);
378
+ return () => this.eventEmitter.off("change", callback);
379
+ }
380
+ /**
381
+ * Get storage usage information
382
+ */
383
+ usage() {
384
+ return {
385
+ totalSpace: this.maxSize,
386
+ freeSpace: this.maxSize
387
+ // TODO: Calculate actual usage
388
+ };
389
+ }
390
+ /**
391
+ * Get the libSQL client (for advanced operations)
392
+ */
393
+ getClient() {
394
+ return this.client;
395
+ }
396
+ /**
397
+ * Get organization ID
398
+ */
399
+ getOrganizationId() {
400
+ return this.organizationId;
401
+ }
402
+ /**
403
+ * Get agent ID
404
+ */
405
+ getAgentId() {
406
+ return this.agentId;
407
+ }
408
+ /**
409
+ * Check if a root directory exists
410
+ */
411
+ async hasRoot() {
412
+ const result = await this.client.execute({
413
+ sql: `SELECT 1 FROM files
414
+ WHERE path = '/' AND organization_id = ?
415
+ AND (agent_id = ? OR (agent_id IS NULL AND ? IS NULL))`,
416
+ args: [this.organizationId, this.agentId, this.agentId]
417
+ });
418
+ return result.rows.length > 0;
419
+ }
420
+ /**
421
+ * Create root directory if it doesn't exist
422
+ * Called by StoreFS during initialization
423
+ */
424
+ async ensureRoot() {
425
+ const hasRoot = await this.hasRoot();
426
+ if (hasRoot) return;
427
+ const now = (/* @__PURE__ */ new Date()).toISOString();
428
+ await this.client.execute({
429
+ sql: `INSERT INTO files (path, organization_id, agent_id, content, mode, uid, gid, size, atime, mtime, ctime, birthtime)
430
+ VALUES ('/', ?, ?, NULL, 16877, 1000, 1000, 0, ?, ?, ?, ?)`,
431
+ args: [this.organizationId, this.agentId, now, now, now, now]
432
+ });
433
+ }
434
+ /**
435
+ * Close the store and release resources
436
+ */
437
+ async close() {
438
+ await this.sync();
439
+ if ("close" in this.client && typeof this.client.close === "function") {
440
+ await this.client.close();
441
+ }
442
+ }
443
+ /**
444
+ * Text search using FTS5 with fuzzy fallback
445
+ *
446
+ * Search strategy:
447
+ * 1. FTS5 MATCH for fast, indexed word matching
448
+ * 2. Fuzzy fallback (fuzzy_damlev) for typo tolerance if no results
449
+ *
450
+ * @param query - Search query string
451
+ * @param options - Search options
452
+ * @returns Array of search matches with line information
453
+ */
454
+ async textSearch(query, options = {}) {
455
+ const limit = options.resultLimit || 500;
456
+ const fuzzyThreshold = options.fuzzyThreshold ?? 2;
457
+ const matches = [];
458
+ try {
459
+ let folderFilter = "";
460
+ const folderArgs = [];
461
+ if (options.folders && options.folders.length > 0) {
462
+ const folderClauses = options.folders.map((folder, i) => {
463
+ folderArgs.push(folder.endsWith("/") ? `${folder}%` : `${folder}/%`);
464
+ return `f.path LIKE ?`;
465
+ });
466
+ folderFilter = `AND (${folderClauses.join(" OR ")})`;
467
+ }
468
+ let excludeFilter = "";
469
+ const excludeArgs = [];
470
+ if (options.excludes && options.excludes.length > 0) {
471
+ for (const pattern of options.excludes) {
472
+ const likePattern = pattern.replace(/\*\*/g, "%").replace(/\*/g, "%").replace(/\?/g, "_");
473
+ excludeArgs.push(likePattern);
474
+ excludeFilter += ` AND f.path NOT LIKE ?`;
475
+ }
476
+ }
477
+ const ftsQuery = options.isRegex ? query : query.replace(/['"]/g, "");
478
+ const ftsResult = await this.client.execute({
479
+ sql: `
480
+ SELECT f.path, f.content
481
+ FROM files f
482
+ INNER JOIN files_fts fts ON f.rowid = fts.rowid
483
+ WHERE fts.content MATCH ?
484
+ AND f.organization_id = ?
485
+ AND (f.agent_id = ? OR (f.agent_id IS NULL AND ? IS NULL))
486
+ AND f.mode & 61440 = 32768 -- Regular files only (S_IFREG)
487
+ ${folderFilter}
488
+ ${excludeFilter}
489
+ LIMIT ?
490
+ `,
491
+ args: [
492
+ ftsQuery,
493
+ this.organizationId,
494
+ this.agentId,
495
+ this.agentId,
496
+ ...folderArgs,
497
+ ...excludeArgs,
498
+ limit
499
+ ]
500
+ });
501
+ for (const row of ftsResult.rows) {
502
+ const path = row.path;
503
+ const content = row.content;
504
+ if (!content) continue;
505
+ const lines = content.split("\n");
506
+ const searchLower = options.caseSensitive ? query : query.toLowerCase();
507
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
508
+ const line = lines[lineNum];
509
+ const lineLower = options.caseSensitive ? line : line.toLowerCase();
510
+ let matchIndex = lineLower.indexOf(searchLower);
511
+ while (matchIndex !== -1) {
512
+ matches.push({
513
+ path,
514
+ lineNumber: lineNum + 1,
515
+ lineContent: line,
516
+ matchStart: matchIndex,
517
+ matchEnd: matchIndex + query.length
518
+ });
519
+ if (matches.length >= limit) break;
520
+ matchIndex = lineLower.indexOf(searchLower, matchIndex + 1);
521
+ }
522
+ if (matches.length >= limit) break;
523
+ }
524
+ if (matches.length >= limit) break;
525
+ }
526
+ if (matches.length === 0 && fuzzyThreshold > 0) {
527
+ const fuzzyResult = await this.client.execute({
528
+ sql: `
529
+ SELECT path, content
530
+ FROM files
531
+ WHERE organization_id = ?
532
+ AND (agent_id = ? OR (agent_id IS NULL AND ? IS NULL))
533
+ AND mode & 61440 = 32768
534
+ AND (
535
+ fuzzy_damlev(path, ?) <= ?
536
+ OR path LIKE '%' || ? || '%'
537
+ )
538
+ ${folderFilter}
539
+ ${excludeFilter}
540
+ LIMIT ?
541
+ `,
542
+ args: [
543
+ this.organizationId,
544
+ this.agentId,
545
+ this.agentId,
546
+ query,
547
+ fuzzyThreshold,
548
+ query,
549
+ ...folderArgs,
550
+ ...excludeArgs,
551
+ limit
552
+ ]
553
+ });
554
+ for (const row of fuzzyResult.rows) {
555
+ const path = row.path;
556
+ const content = row.content;
557
+ if (!content) continue;
558
+ const lines = content.split("\n");
559
+ const searchLower = options.caseSensitive ? query : query.toLowerCase();
560
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
561
+ const line = lines[lineNum];
562
+ const lineLower = options.caseSensitive ? line : line.toLowerCase();
563
+ const matchIndex = lineLower.indexOf(searchLower);
564
+ if (matchIndex !== -1) {
565
+ matches.push({
566
+ path,
567
+ lineNumber: lineNum + 1,
568
+ lineContent: line,
569
+ matchStart: matchIndex,
570
+ matchEnd: matchIndex + query.length
571
+ });
572
+ if (matches.length >= limit) break;
573
+ }
574
+ }
575
+ if (matches.length >= limit) break;
576
+ }
577
+ }
578
+ return {
579
+ matches,
580
+ truncated: matches.length >= limit
581
+ };
582
+ } catch (error) {
583
+ console.error("textSearch error:", error);
584
+ return { matches: [], truncated: false };
585
+ }
586
+ }
587
+ };
588
+
589
+ // src/backends/libsql/backend.ts
590
+ import { Async, FileSystem, InMemory } from "@zenfs/core";
591
+ function errnoError(code, path, syscall) {
592
+ const err = Object.assign(new Error(`${code}: ${syscall || "unknown"} '${path || ""}'`), { code, path, syscall });
593
+ return err;
594
+ }
595
+ var DEFAULT_FILE_MODE = 33188;
596
+ var DEFAULT_DIR_MODE = 16877;
597
+ var LibSQLFS = class extends Async(FileSystem) {
598
+ _sync = InMemory.create({});
599
+ client;
600
+ organizationId;
601
+ agentId;
602
+ label;
603
+ constructor(client, options) {
604
+ super(19539, "libsqlfs");
605
+ this.client = client;
606
+ this.organizationId = options.organizationId;
607
+ this.agentId = options.agentId ?? null;
608
+ this.label = options.label;
609
+ }
610
+ /**
611
+ * Initialize schema and seed root directory.
612
+ * Called before ready(), which triggers Async() crossCopy.
613
+ */
614
+ async initialize() {
615
+ await this.client.execute(`
616
+ CREATE TABLE IF NOT EXISTS files (
617
+ path TEXT PRIMARY KEY,
618
+ content BLOB,
619
+ mode INTEGER NOT NULL DEFAULT ${DEFAULT_FILE_MODE},
620
+ uid INTEGER NOT NULL DEFAULT 1000,
621
+ gid INTEGER NOT NULL DEFAULT 1000,
622
+ size INTEGER NOT NULL DEFAULT 0,
623
+ atime TEXT NOT NULL,
624
+ mtime TEXT NOT NULL,
625
+ ctime TEXT NOT NULL,
626
+ birthtime TEXT NOT NULL,
627
+ organization_id TEXT,
628
+ agent_id TEXT
629
+ )
630
+ `);
631
+ await this.client.execute(
632
+ `CREATE INDEX IF NOT EXISTS idx_files_path_prefix ON files(path)`
633
+ );
634
+ const root = await this.client.execute(`SELECT 1 FROM files WHERE path = '/'`);
635
+ if (!root.rows.length) {
636
+ const now = (/* @__PURE__ */ new Date()).toISOString();
637
+ await this.client.execute({
638
+ sql: `INSERT INTO files (path, content, mode, uid, gid, size, atime, mtime, ctime, birthtime, organization_id, agent_id)
639
+ VALUES ('/', NULL, ?, 1000, 1000, 0, ?, ?, ?, ?, ?, ?)`,
640
+ args: [DEFAULT_DIR_MODE, now, now, now, now, this.organizationId, this.agentId]
641
+ });
642
+ }
643
+ }
644
+ /**
645
+ * Override ready() to run initialize(), then let Async() crossCopy.
646
+ */
647
+ async ready() {
648
+ await this.initialize();
649
+ await super.ready();
650
+ }
651
+ // -- async methods that Async(FileSystem) requires --
652
+ async rename(oldPath, newPath) {
653
+ await this.client.execute({
654
+ sql: `UPDATE files SET path = ? WHERE path = ?`,
655
+ args: [newPath, oldPath]
656
+ });
657
+ }
658
+ async stat(path) {
659
+ const result = await this.client.execute({
660
+ sql: `SELECT mode, size, atime, mtime, ctime, birthtime, uid, gid FROM files WHERE path = ?`,
661
+ args: [path]
662
+ });
663
+ if (!result.rows.length) {
664
+ throw errnoError("ENOENT", path, "stat");
665
+ }
666
+ const r = result.rows[0];
667
+ return {
668
+ mode: r.mode,
669
+ size: r.size,
670
+ atimeMs: new Date(r.atime).getTime(),
671
+ mtimeMs: new Date(r.mtime).getTime(),
672
+ ctimeMs: new Date(r.ctime).getTime(),
673
+ birthtimeMs: new Date(r.birthtime).getTime(),
674
+ uid: r.uid,
675
+ gid: r.gid,
676
+ ino: 0,
677
+ nlink: 1
678
+ };
679
+ }
680
+ async touch(path, metadata) {
681
+ const now = (/* @__PURE__ */ new Date()).toISOString();
682
+ const atime = metadata.atimeMs ? new Date(metadata.atimeMs).toISOString() : now;
683
+ const mtime = metadata.mtimeMs ? new Date(metadata.mtimeMs).toISOString() : now;
684
+ const ctime = metadata.ctimeMs ? new Date(metadata.ctimeMs).toISOString() : now;
685
+ await this.client.execute({
686
+ sql: `UPDATE files SET atime = ?, mtime = ?, ctime = ?, organization_id = ?, agent_id = ? WHERE path = ?`,
687
+ args: [atime, mtime, ctime, this.organizationId, this.agentId, path]
688
+ });
689
+ }
690
+ async createFile(path, options) {
691
+ const now = (/* @__PURE__ */ new Date()).toISOString();
692
+ const mode = options.mode ?? DEFAULT_FILE_MODE;
693
+ const uid = options.uid ?? 1e3;
694
+ const gid = options.gid ?? 1e3;
695
+ await this.client.execute({
696
+ sql: `INSERT INTO files (path, content, mode, uid, gid, size, atime, mtime, ctime, birthtime, organization_id, agent_id)
697
+ VALUES (?, NULL, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)
698
+ ON CONFLICT (path) DO UPDATE SET mode = excluded.mode, mtime = excluded.mtime, ctime = excluded.ctime`,
699
+ args: [path, mode, uid, gid, now, now, now, now, this.organizationId, this.agentId]
700
+ });
701
+ return {
702
+ mode,
703
+ size: 0,
704
+ uid,
705
+ gid,
706
+ atimeMs: Date.now(),
707
+ mtimeMs: Date.now(),
708
+ ctimeMs: Date.now(),
709
+ birthtimeMs: Date.now(),
710
+ ino: 0,
711
+ nlink: 1
712
+ };
713
+ }
714
+ async unlink(path) {
715
+ await this.client.execute({ sql: `DELETE FROM files WHERE path = ?`, args: [path] });
716
+ }
717
+ async rmdir(path) {
718
+ await this.client.execute({ sql: `DELETE FROM files WHERE path = ?`, args: [path] });
719
+ }
720
+ async mkdir(path, options) {
721
+ const now = (/* @__PURE__ */ new Date()).toISOString();
722
+ const mode = options.mode ?? DEFAULT_DIR_MODE;
723
+ const uid = options.uid ?? 1e3;
724
+ const gid = options.gid ?? 1e3;
725
+ await this.client.execute({
726
+ sql: `INSERT INTO files (path, content, mode, uid, gid, size, atime, mtime, ctime, birthtime, organization_id, agent_id)
727
+ VALUES (?, NULL, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)
728
+ ON CONFLICT (path) DO NOTHING`,
729
+ args: [path, mode, uid, gid, now, now, now, now, this.organizationId, this.agentId]
730
+ });
731
+ return {
732
+ mode,
733
+ size: 0,
734
+ uid,
735
+ gid,
736
+ atimeMs: Date.now(),
737
+ mtimeMs: Date.now(),
738
+ ctimeMs: Date.now(),
739
+ birthtimeMs: Date.now(),
740
+ ino: 0,
741
+ nlink: 1
742
+ };
743
+ }
744
+ async readdir(path) {
745
+ const prefix = path === "/" ? "/" : path.endsWith("/") ? path : path + "/";
746
+ let sql;
747
+ let args;
748
+ if (prefix === "/") {
749
+ sql = `SELECT path FROM files WHERE path != '/' AND path LIKE '/%' AND path NOT LIKE '/%/%'`;
750
+ args = [];
751
+ } else {
752
+ sql = `SELECT path FROM files WHERE path LIKE ? AND path NOT LIKE ?`;
753
+ args = [prefix + "%", prefix + "%/%"];
754
+ }
755
+ const result = await this.client.execute({ sql, args });
756
+ return result.rows.map((r) => r.path.slice(prefix.length)).filter((name) => name.length > 0);
757
+ }
758
+ async link(_target, _link) {
759
+ throw errnoError("ENOSYS", _target, "link");
760
+ }
761
+ async sync() {
762
+ }
763
+ async read(path, buffer, start, end) {
764
+ const result = await this.client.execute({
765
+ sql: `SELECT content FROM files WHERE path = ?`,
766
+ args: [path]
767
+ });
768
+ const raw = result.rows[0]?.content;
769
+ if (raw === null || raw === void 0) return;
770
+ let content;
771
+ if (raw instanceof Uint8Array) {
772
+ content = raw;
773
+ } else if (raw instanceof ArrayBuffer) {
774
+ content = new Uint8Array(raw);
775
+ } else if (ArrayBuffer.isView(raw)) {
776
+ content = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
777
+ } else if (typeof raw === "string") {
778
+ const binary = atob(raw);
779
+ content = new Uint8Array(binary.length);
780
+ for (let i = 0; i < binary.length; i++) content[i] = binary.charCodeAt(i);
781
+ } else {
782
+ return;
783
+ }
784
+ const slice = content.subarray(start, end);
785
+ buffer.set(slice);
786
+ }
787
+ async write(path, buffer, offset) {
788
+ const result = await this.client.execute({
789
+ sql: `SELECT content FROM files WHERE path = ?`,
790
+ args: [path]
791
+ });
792
+ let existing = new Uint8Array(0);
793
+ const raw = result.rows[0]?.content;
794
+ if (raw instanceof Uint8Array) {
795
+ existing = new Uint8Array(raw);
796
+ } else if (raw instanceof ArrayBuffer) {
797
+ existing = new Uint8Array(raw);
798
+ } else if (ArrayBuffer.isView(raw)) {
799
+ existing = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
800
+ }
801
+ const newSize = Math.max(existing.length, offset + buffer.length);
802
+ const merged = new Uint8Array(newSize);
803
+ merged.set(existing);
804
+ merged.set(buffer, offset);
805
+ const now = (/* @__PURE__ */ new Date()).toISOString();
806
+ await this.client.execute({
807
+ sql: `INSERT INTO files (path, content, mode, uid, gid, size, atime, mtime, ctime, birthtime, organization_id, agent_id)
808
+ VALUES (?, ?, ${DEFAULT_FILE_MODE}, 1000, 1000, ?, ?, ?, ?, ?, ?, ?)
809
+ ON CONFLICT (path) DO UPDATE SET
810
+ content = excluded.content, size = excluded.size,
811
+ mtime = excluded.mtime, ctime = excluded.ctime,
812
+ organization_id = excluded.organization_id, agent_id = excluded.agent_id`,
813
+ args: [path, merged, merged.length, now, now, now, now, this.organizationId, this.agentId]
814
+ });
815
+ }
816
+ };
817
+ var LibSQLBackend = {
818
+ name: "LibSQL",
819
+ options: {
820
+ url: { type: "string", required: false },
821
+ syncUrl: { type: "string", required: false },
822
+ authToken: { type: "string", required: false },
823
+ organizationId: { type: "string", required: true },
824
+ agentId: { type: ["string", "undefined"], required: false },
825
+ label: { type: "string", required: false },
826
+ maxSize: { type: "number", required: false }
827
+ },
828
+ async create(options) {
829
+ const url = options.url || options.syncUrl || ":memory:";
830
+ const client = createClient({ url, authToken: options.authToken });
831
+ const fs = new LibSQLFS(client, options);
832
+ return fs;
833
+ },
834
+ async isAvailable() {
835
+ try {
836
+ const { createClient: _c } = await import("@libsql/client");
837
+ return typeof _c === "function";
838
+ } catch {
839
+ return false;
840
+ }
841
+ }
842
+ };
843
+ async function createLibSQLStore(options) {
844
+ const url = options.url || options.syncUrl || ":memory:";
845
+ const client = createClient({ url, authToken: options.authToken });
846
+ const store = new LibSQLStore(client, options);
847
+ await store.initialize();
848
+ await store.ensureRoot();
849
+ return store;
850
+ }
851
+ async function forkTemplate(serverUrl, templateNamespace, targetNamespace, authToken) {
852
+ const headers = { "Content-Type": "application/json" };
853
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
854
+ try {
855
+ const response = await fetch(`${serverUrl}/v1/namespaces/${templateNamespace}/fork/${targetNamespace}`, { method: "POST", headers });
856
+ return response.ok;
857
+ } catch {
858
+ return false;
859
+ }
860
+ }
861
+ async function namespaceExists(serverUrl, namespace, authToken) {
862
+ const headers = {};
863
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
864
+ try {
865
+ const response = await fetch(`${serverUrl}/v1/namespaces`, { headers });
866
+ if (!response.ok) return false;
867
+ const data = await response.json();
868
+ return (data.namespaces || []).includes(namespace);
869
+ } catch {
870
+ return false;
871
+ }
872
+ }
873
+ async function createNamespace(serverUrl, namespace, authToken) {
874
+ const headers = { "Content-Type": "application/json" };
875
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
876
+ try {
877
+ const response = await fetch(`${serverUrl}/v1/namespaces/${namespace}/create`, { method: "POST", headers });
878
+ return response.ok;
879
+ } catch {
880
+ return false;
881
+ }
882
+ }
883
+
884
+ // src/backends/libsql/types.ts
885
+ var S_IFMT = 61440;
886
+ var S_IFDIR = 16384;
887
+ var S_IFREG = 32768;
888
+ var DEFAULT_DIR_MODE2 = S_IFDIR | 493;
889
+ var DEFAULT_FILE_MODE2 = S_IFREG | 420;
890
+ function isDirectory(mode) {
891
+ return (mode & S_IFMT) === S_IFDIR;
892
+ }
893
+ function isFile(mode) {
894
+ return (mode & S_IFMT) === S_IFREG;
895
+ }
896
+ function nowISO() {
897
+ return (/* @__PURE__ */ new Date()).toISOString();
898
+ }
899
+ export {
900
+ AsyncTransactionBase,
901
+ DEFAULT_DIR_MODE2 as DEFAULT_DIR_MODE,
902
+ DEFAULT_FILE_MODE2 as DEFAULT_FILE_MODE,
903
+ LibSQLBackend as LibSQL,
904
+ LibSQLBackend,
905
+ LibSQLStore,
906
+ LibSQLTransaction,
907
+ createLibSQLStore,
908
+ createNamespace,
909
+ forkTemplate,
910
+ isDirectory,
911
+ isFile,
912
+ namespaceExists,
913
+ nowISO
914
+ };