@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 +11 -0
- package/dist/fuse.js +459 -0
- package/dist/index.d.ts +397 -0
- package/dist/index.js +914 -0
- package/package.json +47 -0
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
|
+
};
|