mcard-js 2.1.47 → 2.1.49
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/AbstractSqlEngine-DKka6XjT.d.cts +451 -0
- package/dist/AbstractSqlEngine-DKka6XjT.d.ts +451 -0
- package/dist/CardCollection-TYC67XOH.js +10 -0
- package/dist/CardCollection-ZQ3G3Q3A.js +10 -0
- package/dist/EventProducer-VFDOM5W2.js +47 -0
- package/dist/IndexedDBEngine-5CEFZDOG.js +12 -0
- package/dist/IndexedDBEngine-BWXAB46W.js +12 -0
- package/dist/LLMRuntime-PH3MOQ2Y.js +17 -0
- package/dist/LambdaRuntime-DMEBYJIN.js +19 -0
- package/dist/LambdaRuntime-YH74FHIW.js +19 -0
- package/dist/Loader-OBPDJNFH.js +12 -0
- package/dist/Loader-WZXYG4GE.js +12 -0
- package/dist/MCard-RHTWJPHJ.js +8 -0
- package/dist/NetworkRuntime-KBQURQ6A.js +1598 -0
- package/dist/NetworkRuntime-S4DZCGVN.js +1598 -0
- package/dist/OllamaProvider-SPGO5Z5E.js +9 -0
- package/dist/chunk-3FFEA2XK.js +149 -0
- package/dist/chunk-7AXRV7NS.js +112 -0
- package/dist/chunk-AAO4GDBI.js +2360 -0
- package/dist/chunk-ASW6AOA7.js +140 -0
- package/dist/chunk-BJJZWPIF.js +112 -0
- package/dist/chunk-GGQCF7ZK.js +170 -0
- package/dist/chunk-HIVVDGE5.js +497 -0
- package/dist/chunk-HWBEGVEN.js +364 -0
- package/dist/chunk-ISY5LYLF.js +217 -0
- package/dist/chunk-KVZYFZJ5.js +427 -0
- package/dist/chunk-NGTY4P6A.js +275 -0
- package/dist/chunk-OAHWTOEB.js +275 -0
- package/dist/chunk-OUW2SUGM.js +368 -0
- package/dist/chunk-QKH3N62B.js +2360 -0
- package/dist/chunk-QPVEUPMU.js +299 -0
- package/dist/chunk-RZENJZGX.js +299 -0
- package/dist/chunk-VYDZR4ZD.js +364 -0
- package/dist/chunk-XJZOEM5F.js +903 -0
- package/dist/chunk-Z7EFXSTO.js +217 -0
- package/dist/index.browser.cjs +58 -20
- package/dist/index.browser.d.cts +34 -17
- package/dist/index.browser.d.ts +34 -17
- package/dist/index.browser.js +12 -8
- package/dist/index.cjs +644 -167
- package/dist/index.d.cts +725 -5
- package/dist/index.d.ts +725 -5
- package/dist/index.js +536 -95
- package/dist/storage/SqliteNodeEngine.cjs +28 -20
- package/dist/storage/SqliteNodeEngine.d.cts +1 -1
- package/dist/storage/SqliteNodeEngine.d.ts +1 -1
- package/dist/storage/SqliteNodeEngine.js +5 -5
- package/dist/storage/SqliteWasmEngine.cjs +28 -20
- package/dist/storage/SqliteWasmEngine.d.cts +1 -1
- package/dist/storage/SqliteWasmEngine.d.ts +1 -1
- package/dist/storage/SqliteWasmEngine.js +5 -5
- package/package.json +3 -1
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_FILE_SIZE,
|
|
3
|
+
READ_TIMEOUT_MS
|
|
4
|
+
} from "./chunk-J3IDWMDN.js";
|
|
5
|
+
import {
|
|
6
|
+
ContentTypeInterpreter,
|
|
7
|
+
MCard
|
|
8
|
+
} from "./chunk-GGQCF7ZK.js";
|
|
9
|
+
import {
|
|
10
|
+
__export
|
|
11
|
+
} from "./chunk-PNKVD2UK.js";
|
|
12
|
+
|
|
13
|
+
// src/Loader.ts
|
|
14
|
+
var Loader_exports = {};
|
|
15
|
+
__export(Loader_exports, {
|
|
16
|
+
loadFileToCollection: () => loadFileToCollection,
|
|
17
|
+
processAndStoreFile: () => processAndStoreFile
|
|
18
|
+
});
|
|
19
|
+
import * as fs2 from "fs/promises";
|
|
20
|
+
import * as path2 from "path";
|
|
21
|
+
|
|
22
|
+
// src/FileIO.ts
|
|
23
|
+
var FileIO_exports = {};
|
|
24
|
+
__export(FileIO_exports, {
|
|
25
|
+
isProblematicFile: () => isProblematicFile,
|
|
26
|
+
listFiles: () => listFiles,
|
|
27
|
+
processFileContent: () => processFileContent,
|
|
28
|
+
readFileSafely: () => readFileSafely,
|
|
29
|
+
streamReadNormalizedText: () => streamReadNormalizedText
|
|
30
|
+
});
|
|
31
|
+
import * as crypto from "crypto";
|
|
32
|
+
import * as fs from "fs/promises";
|
|
33
|
+
import * as path from "path";
|
|
34
|
+
async function streamReadNormalizedText(filePath, options) {
|
|
35
|
+
const { byteCap, wrapWidth } = options;
|
|
36
|
+
const sha = crypto.createHash("sha256");
|
|
37
|
+
let totalSize = 0;
|
|
38
|
+
let producedText = "";
|
|
39
|
+
let currentLen = 0;
|
|
40
|
+
const handle = await fs.open(filePath, "r");
|
|
41
|
+
try {
|
|
42
|
+
const buffer = new Uint8Array(8192);
|
|
43
|
+
let remaining = byteCap;
|
|
44
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
45
|
+
let position = 0;
|
|
46
|
+
while (remaining > 0) {
|
|
47
|
+
const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, remaining), position);
|
|
48
|
+
if (bytesRead === 0) break;
|
|
49
|
+
position += bytesRead;
|
|
50
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
51
|
+
sha.update(chunk);
|
|
52
|
+
totalSize += bytesRead;
|
|
53
|
+
remaining -= bytesRead;
|
|
54
|
+
const s2 = decoder.decode(chunk, { stream: true });
|
|
55
|
+
for (const ch of s2) {
|
|
56
|
+
if (ch === "\r") continue;
|
|
57
|
+
producedText += ch;
|
|
58
|
+
if (ch === "\n") {
|
|
59
|
+
currentLen = 0;
|
|
60
|
+
} else {
|
|
61
|
+
currentLen++;
|
|
62
|
+
if (wrapWidth > 0 && currentLen >= wrapWidth) {
|
|
63
|
+
producedText += "\n";
|
|
64
|
+
currentLen = 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const s = decoder.decode();
|
|
70
|
+
for (const ch of s) {
|
|
71
|
+
if (ch === "\r") continue;
|
|
72
|
+
producedText += ch;
|
|
73
|
+
if (ch === "\n") {
|
|
74
|
+
currentLen = 0;
|
|
75
|
+
} else {
|
|
76
|
+
currentLen++;
|
|
77
|
+
if (wrapWidth > 0 && currentLen >= wrapWidth) {
|
|
78
|
+
producedText += "\n";
|
|
79
|
+
currentLen = 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} finally {
|
|
84
|
+
await handle.close();
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
text: producedText,
|
|
88
|
+
originalSize: totalSize,
|
|
89
|
+
originalSha256Prefix: sha.digest("hex").substring(0, 16)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
var MAX_FILE_SIZE2 = MAX_FILE_SIZE;
|
|
93
|
+
var READ_TIMEOUT_MS2 = READ_TIMEOUT_MS;
|
|
94
|
+
async function isProblematicFile(filePath) {
|
|
95
|
+
try {
|
|
96
|
+
const stats = await fs.stat(filePath);
|
|
97
|
+
if (stats.size === 0) return false;
|
|
98
|
+
if (path.basename(filePath).startsWith(".")) return true;
|
|
99
|
+
if (stats.size > MAX_FILE_SIZE2) return true;
|
|
100
|
+
const ext = path.extname(filePath);
|
|
101
|
+
const isKnownType = ContentTypeInterpreter.isKnownLongLineExtension(ext);
|
|
102
|
+
if (isKnownType && stats.size > 1024 * 1024) return true;
|
|
103
|
+
const handle = await fs.open(filePath, "r");
|
|
104
|
+
try {
|
|
105
|
+
const buffer = new Uint8Array(32 * 1024);
|
|
106
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
107
|
+
const sample = buffer.subarray(0, bytesRead);
|
|
108
|
+
if (ContentTypeInterpreter.isUnstructuredBinary(sample)) return true;
|
|
109
|
+
if (ContentTypeInterpreter.hasPathologicalLines(sample, isKnownType)) return true;
|
|
110
|
+
} finally {
|
|
111
|
+
await handle.close();
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
} catch {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function readFileSafely(filePath, options = {}) {
|
|
119
|
+
const stats = await fs.stat(filePath);
|
|
120
|
+
if (stats.size > MAX_FILE_SIZE2) throw new Error(`File too large: ${stats.size}`);
|
|
121
|
+
const controller = new AbortController();
|
|
122
|
+
const timeout = setTimeout(() => controller.abort(), READ_TIMEOUT_MS2);
|
|
123
|
+
try {
|
|
124
|
+
const handle = await fs.open(filePath, "r");
|
|
125
|
+
try {
|
|
126
|
+
const buffer = new Uint8Array(stats.size);
|
|
127
|
+
await handle.read(buffer, 0, stats.size, 0);
|
|
128
|
+
return buffer;
|
|
129
|
+
} finally {
|
|
130
|
+
await handle.close();
|
|
131
|
+
}
|
|
132
|
+
} catch (e) {
|
|
133
|
+
const error = e;
|
|
134
|
+
if (error.name === "AbortError") throw new Error(`Read timeout for ${filePath}`);
|
|
135
|
+
throw e;
|
|
136
|
+
} finally {
|
|
137
|
+
clearTimeout(timeout);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function listFiles(dirPath, recursive = false) {
|
|
141
|
+
let files = [];
|
|
142
|
+
try {
|
|
143
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
146
|
+
if (entry.name.startsWith(".")) continue;
|
|
147
|
+
if (entry.isDirectory()) {
|
|
148
|
+
if (recursive) {
|
|
149
|
+
files = files.concat(await listFiles(fullPath, true));
|
|
150
|
+
}
|
|
151
|
+
} else if (entry.isFile()) {
|
|
152
|
+
if (!await isProblematicFile(fullPath)) {
|
|
153
|
+
files.push(fullPath);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.warn(`Error listing directory ${dirPath}:`, e);
|
|
159
|
+
}
|
|
160
|
+
return files;
|
|
161
|
+
}
|
|
162
|
+
async function processFileContent(filePath, options = {}) {
|
|
163
|
+
const rawContent = await readFileSafely(filePath, { allowPathological: options.allowPathological, maxBytes: options.maxBytes });
|
|
164
|
+
const sample = rawContent.subarray(0, 1024 * 1024);
|
|
165
|
+
const detection = ContentTypeInterpreter.detectContentType(sample, path.extname(filePath));
|
|
166
|
+
let isBinary = ContentTypeInterpreter.isBinaryContent(sample, detection.mimeType);
|
|
167
|
+
if (options.forceBinary) isBinary = true;
|
|
168
|
+
let content = rawContent;
|
|
169
|
+
if (!isBinary) {
|
|
170
|
+
try {
|
|
171
|
+
content = new TextDecoder("utf-8", { fatal: true }).decode(rawContent);
|
|
172
|
+
} catch {
|
|
173
|
+
content = new TextDecoder("utf-8", { fatal: false }).decode(rawContent);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
content,
|
|
178
|
+
filename: path.basename(filePath),
|
|
179
|
+
mimeType: detection.mimeType,
|
|
180
|
+
extension: detection.extension,
|
|
181
|
+
isBinary,
|
|
182
|
+
size: rawContent.length
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/Loader.ts
|
|
187
|
+
var DEFAULT_MAX_PROBLEM_BYTES = 2 * 1024 * 1024;
|
|
188
|
+
var WRAP_WIDTH_KNOWN = 1e3;
|
|
189
|
+
var WRAP_WIDTH_DEFAULT = 80;
|
|
190
|
+
var Logger = {
|
|
191
|
+
info: (...args) => console.log("[Loader]", ...args),
|
|
192
|
+
warn: (...args) => console.warn("[Loader]", ...args),
|
|
193
|
+
error: (...args) => console.error("[Loader]", ...args),
|
|
194
|
+
debug: (...args) => {
|
|
195
|
+
if (process.env.DEBUG) console.log("[Loader]", ...args);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
async function processAndStoreFile(filePath, collection, options = {}) {
|
|
199
|
+
const {
|
|
200
|
+
allowProblematic = false,
|
|
201
|
+
maxBytesOnProblem = DEFAULT_MAX_PROBLEM_BYTES,
|
|
202
|
+
metadataOnly = false,
|
|
203
|
+
rootPath
|
|
204
|
+
} = options;
|
|
205
|
+
try {
|
|
206
|
+
let fileInfo;
|
|
207
|
+
if (await isProblematicFile(filePath)) {
|
|
208
|
+
if (!allowProblematic) {
|
|
209
|
+
Logger.warn(`Skipping problematic file: ${filePath}`);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const extension = path2.extname(filePath).toLowerCase();
|
|
213
|
+
const isKnownType = ContentTypeInterpreter.isKnownLongLineExtension(extension);
|
|
214
|
+
const wrapWidth = isKnownType ? WRAP_WIDTH_KNOWN : WRAP_WIDTH_DEFAULT;
|
|
215
|
+
Logger.warn(`Problematic file detected, processing as safe text: ${filePath}`);
|
|
216
|
+
try {
|
|
217
|
+
const streamed = await streamReadNormalizedText(filePath, {
|
|
218
|
+
byteCap: maxBytesOnProblem,
|
|
219
|
+
wrapWidth
|
|
220
|
+
});
|
|
221
|
+
fileInfo = {
|
|
222
|
+
content: streamed.text,
|
|
223
|
+
filename: path2.basename(filePath),
|
|
224
|
+
mimeType: "text/plain",
|
|
225
|
+
extension,
|
|
226
|
+
isBinary: false,
|
|
227
|
+
size: streamed.text.length,
|
|
228
|
+
originalSize: streamed.originalSize,
|
|
229
|
+
originalSha256Prefix: streamed.originalSha256Prefix,
|
|
230
|
+
normalized: true,
|
|
231
|
+
wrapWidth
|
|
232
|
+
};
|
|
233
|
+
} catch (e) {
|
|
234
|
+
Logger.warn(`Safe text processing failed, falling back to capped binary: ${filePath}`, e);
|
|
235
|
+
fileInfo = await processFileContent(filePath, {
|
|
236
|
+
forceBinary: true,
|
|
237
|
+
allowPathological: true,
|
|
238
|
+
maxBytes: maxBytesOnProblem
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
Logger.info(`Processing file: ${filePath}`);
|
|
243
|
+
fileInfo = await processFileContent(filePath);
|
|
244
|
+
}
|
|
245
|
+
if (!fileInfo) return null;
|
|
246
|
+
const content = fileInfo.content;
|
|
247
|
+
if (!content || typeof content === "string" && content.length === 0 || content instanceof Uint8Array && content.length === 0) {
|
|
248
|
+
Logger.debug(`Skipping empty file: ${filePath} (empty files cannot be stored as MCards)`);
|
|
249
|
+
return {
|
|
250
|
+
hash: "",
|
|
251
|
+
contentType: fileInfo.mimeType,
|
|
252
|
+
isBinary: fileInfo.isBinary,
|
|
253
|
+
filename: fileInfo.filename,
|
|
254
|
+
size: 0,
|
|
255
|
+
filePath
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
let mcard = null;
|
|
259
|
+
const isProblematic = await isProblematicFile(filePath);
|
|
260
|
+
if (metadataOnly && isProblematic) {
|
|
261
|
+
mcard = null;
|
|
262
|
+
} else {
|
|
263
|
+
mcard = await MCard.create(fileInfo.content);
|
|
264
|
+
const handle = path2.basename(filePath);
|
|
265
|
+
try {
|
|
266
|
+
await collection.addWithHandle(mcard, handle);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
let registered = false;
|
|
269
|
+
if (rootPath) {
|
|
270
|
+
const relPath = path2.relative(rootPath, filePath);
|
|
271
|
+
if (relPath !== handle) {
|
|
272
|
+
try {
|
|
273
|
+
await collection.addWithHandle(mcard, relPath);
|
|
274
|
+
registered = true;
|
|
275
|
+
} catch (e2) {
|
|
276
|
+
Logger.debug(
|
|
277
|
+
`Handle name '${handle}' already in use (common for files like README.md, LICENSE). MCard stored successfully with hash ${mcard.hash.slice(0, 8)}... (accessible by hash, not by handle)`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (!registered) {
|
|
283
|
+
try {
|
|
284
|
+
await collection.add(mcard);
|
|
285
|
+
} catch (e3) {
|
|
286
|
+
Logger.warn(`Hash fallback also failed for ${handle}:`, e3);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const result = {
|
|
292
|
+
hash: mcard ? mcard.hash : "METADATA_ONLY",
|
|
293
|
+
contentType: fileInfo.mimeType,
|
|
294
|
+
isBinary: fileInfo.isBinary,
|
|
295
|
+
filename: fileInfo.filename,
|
|
296
|
+
size: fileInfo.size,
|
|
297
|
+
filePath
|
|
298
|
+
};
|
|
299
|
+
if (fileInfo.originalSize !== void 0) result.originalSize = fileInfo.originalSize;
|
|
300
|
+
if (fileInfo.originalSha256Prefix) result.originalSha256Prefix = fileInfo.originalSha256Prefix;
|
|
301
|
+
if (metadataOnly && isProblematic) result.metadataOnly = true;
|
|
302
|
+
return result;
|
|
303
|
+
} catch (e) {
|
|
304
|
+
Logger.error(`Error processing ${filePath}:`, e);
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function loadFileToCollection(targetPath, collection, options = {}) {
|
|
309
|
+
const {
|
|
310
|
+
recursive = false,
|
|
311
|
+
includeProblematic = false,
|
|
312
|
+
maxBytesOnProblem = DEFAULT_MAX_PROBLEM_BYTES,
|
|
313
|
+
metadataOnly = false
|
|
314
|
+
} = options;
|
|
315
|
+
const resolvedPath = path2.resolve(targetPath);
|
|
316
|
+
const stats = await fs2.stat(resolvedPath);
|
|
317
|
+
const results = [];
|
|
318
|
+
let files = [];
|
|
319
|
+
let rootPath = resolvedPath;
|
|
320
|
+
if (stats.isFile()) {
|
|
321
|
+
files = [resolvedPath];
|
|
322
|
+
rootPath = path2.dirname(resolvedPath);
|
|
323
|
+
} else if (stats.isDirectory()) {
|
|
324
|
+
files = await listFiles(resolvedPath, recursive);
|
|
325
|
+
rootPath = resolvedPath;
|
|
326
|
+
} else {
|
|
327
|
+
throw new Error(`Path ${targetPath} is not a file or directory`);
|
|
328
|
+
}
|
|
329
|
+
const uniqueDirs = /* @__PURE__ */ new Set();
|
|
330
|
+
let maxDepth = 0;
|
|
331
|
+
for (const file of files) {
|
|
332
|
+
const dir = path2.dirname(file);
|
|
333
|
+
if (dir.startsWith(rootPath)) {
|
|
334
|
+
uniqueDirs.add(dir);
|
|
335
|
+
const rel = path2.relative(rootPath, file);
|
|
336
|
+
const parts = rel.split(path2.sep);
|
|
337
|
+
const depth = parts.length - 1;
|
|
338
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const metrics = {
|
|
342
|
+
filesCount: files.length,
|
|
343
|
+
directoriesCount: uniqueDirs.size,
|
|
344
|
+
directoryLevels: maxDepth
|
|
345
|
+
};
|
|
346
|
+
Logger.info(`About to process ${files.length} files`);
|
|
347
|
+
for (const file of files) {
|
|
348
|
+
const result = await processAndStoreFile(file, collection, {
|
|
349
|
+
allowProblematic: includeProblematic,
|
|
350
|
+
maxBytesOnProblem,
|
|
351
|
+
metadataOnly,
|
|
352
|
+
rootPath
|
|
353
|
+
});
|
|
354
|
+
if (result) results.push(result);
|
|
355
|
+
}
|
|
356
|
+
return { metrics, results };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export {
|
|
360
|
+
FileIO_exports,
|
|
361
|
+
processAndStoreFile,
|
|
362
|
+
loadFileToCollection,
|
|
363
|
+
Loader_exports
|
|
364
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbstractSqlEngine,
|
|
3
|
+
initCoreSchemas
|
|
4
|
+
} from "./chunk-FPW33LMV.js";
|
|
5
|
+
import {
|
|
6
|
+
createPage
|
|
7
|
+
} from "./chunk-3EIBJPNF.js";
|
|
8
|
+
import {
|
|
9
|
+
Handle_exports,
|
|
10
|
+
init_Handle
|
|
11
|
+
} from "./chunk-ADV52544.js";
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_PAGE_SIZE,
|
|
14
|
+
SQLITE_BUSY_TIMEOUT_MS
|
|
15
|
+
} from "./chunk-J3IDWMDN.js";
|
|
16
|
+
import {
|
|
17
|
+
MCard
|
|
18
|
+
} from "./chunk-GGQCF7ZK.js";
|
|
19
|
+
import {
|
|
20
|
+
__toCommonJS
|
|
21
|
+
} from "./chunk-PNKVD2UK.js";
|
|
22
|
+
|
|
23
|
+
// src/storage/engines/SqliteNodeEngine.ts
|
|
24
|
+
var SqliteNodeEngine = class _SqliteNodeEngine extends AbstractSqlEngine {
|
|
25
|
+
db;
|
|
26
|
+
dbPath;
|
|
27
|
+
/**
|
|
28
|
+
* Convert a database row into an MCard instance.
|
|
29
|
+
*/
|
|
30
|
+
rowToCard(row) {
|
|
31
|
+
const rawContent = row.content;
|
|
32
|
+
const content = rawContent instanceof Buffer ? new Uint8Array(rawContent) : new Uint8Array(rawContent);
|
|
33
|
+
return MCard.fromData(content, String(row.hash), String(row.g_time));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a new SqliteNodeEngine via async factory.
|
|
37
|
+
* @param dbPath Path to database file, or ':memory:' for in-memory database
|
|
38
|
+
*/
|
|
39
|
+
static async create(dbPath = ":memory:") {
|
|
40
|
+
const engine = new _SqliteNodeEngine(dbPath);
|
|
41
|
+
await engine.init();
|
|
42
|
+
return engine;
|
|
43
|
+
}
|
|
44
|
+
constructor(dbPath) {
|
|
45
|
+
super();
|
|
46
|
+
this.dbPath = dbPath;
|
|
47
|
+
}
|
|
48
|
+
async init() {
|
|
49
|
+
try {
|
|
50
|
+
const mod = await import("better-sqlite3");
|
|
51
|
+
const Database = mod.default || mod;
|
|
52
|
+
this.db = new Database(this.dbPath);
|
|
53
|
+
this.setupDatabase();
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`SqliteNodeEngine: Failed to load better-sqlite3: ${e.message}`);
|
|
56
|
+
throw e;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Initialize database schema
|
|
61
|
+
*/
|
|
62
|
+
setupDatabase() {
|
|
63
|
+
this.db.pragma("journal_mode = WAL");
|
|
64
|
+
this.db.pragma(`busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS}`);
|
|
65
|
+
this.db.pragma("synchronous = NORMAL");
|
|
66
|
+
initCoreSchemas(this.db);
|
|
67
|
+
}
|
|
68
|
+
// ======================================================================
|
|
69
|
+
// AbstractSqlEngine primitives
|
|
70
|
+
// ======================================================================
|
|
71
|
+
async queryRows(sql, ...params) {
|
|
72
|
+
const stmt = this.db.prepare(sql);
|
|
73
|
+
return stmt.all(...params);
|
|
74
|
+
}
|
|
75
|
+
async execSql(sql, ...params) {
|
|
76
|
+
if (params.length === 0) {
|
|
77
|
+
this.db.exec(sql);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
const stmt = this.db.prepare(sql);
|
|
81
|
+
const result = stmt.run(...params);
|
|
82
|
+
return result.changes;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Close the database connection
|
|
86
|
+
*/
|
|
87
|
+
close() {
|
|
88
|
+
this.db.close();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the database path
|
|
92
|
+
*/
|
|
93
|
+
getDbPath() {
|
|
94
|
+
return this.dbPath;
|
|
95
|
+
}
|
|
96
|
+
// =========== Card Operations (overrides to use Buffer.from for better-sqlite3) ===========
|
|
97
|
+
async add(card) {
|
|
98
|
+
const stmt = this.db.prepare(
|
|
99
|
+
"INSERT OR REPLACE INTO card (hash, content, g_time) VALUES (?, ?, ?)"
|
|
100
|
+
);
|
|
101
|
+
stmt.run(card.hash, Buffer.from(card.content), card.g_time);
|
|
102
|
+
return card.hash;
|
|
103
|
+
}
|
|
104
|
+
// =========== Sync card operations (for performance-critical callers) ===========
|
|
105
|
+
addSync(card) {
|
|
106
|
+
const stmt = this.db.prepare(
|
|
107
|
+
"INSERT OR REPLACE INTO card (hash, content, g_time) VALUES (?, ?, ?)"
|
|
108
|
+
);
|
|
109
|
+
stmt.run(card.hash, Buffer.from(card.content), card.g_time);
|
|
110
|
+
return card.hash;
|
|
111
|
+
}
|
|
112
|
+
getSync(hash) {
|
|
113
|
+
const stmt = this.db.prepare("SELECT hash, content, g_time FROM card WHERE hash = ?");
|
|
114
|
+
const row = stmt.get(hash);
|
|
115
|
+
if (!row) return null;
|
|
116
|
+
return this.rowToCard(row);
|
|
117
|
+
}
|
|
118
|
+
deleteSync(hash) {
|
|
119
|
+
const stmt = this.db.prepare("DELETE FROM card WHERE hash = ?");
|
|
120
|
+
const result = stmt.run(hash);
|
|
121
|
+
return result.changes > 0;
|
|
122
|
+
}
|
|
123
|
+
getPageSync(pageNumber = 1, pageSize = DEFAULT_PAGE_SIZE) {
|
|
124
|
+
if (pageNumber < 1) throw new Error("Page number must be >= 1");
|
|
125
|
+
if (pageSize < 1) throw new Error("Page size must be >= 1");
|
|
126
|
+
const totalItems = this.countSync();
|
|
127
|
+
const offset = (pageNumber - 1) * pageSize;
|
|
128
|
+
const stmt = this.db.prepare(
|
|
129
|
+
"SELECT hash, content, g_time FROM card ORDER BY g_time DESC LIMIT ? OFFSET ?"
|
|
130
|
+
);
|
|
131
|
+
const rows = stmt.all(pageSize, offset);
|
|
132
|
+
const items = rows.map((row) => this.rowToCard(row));
|
|
133
|
+
return createPage(items, totalItems, pageNumber, pageSize);
|
|
134
|
+
}
|
|
135
|
+
countSync() {
|
|
136
|
+
const stmt = this.db.prepare("SELECT COUNT(*) as cnt FROM card");
|
|
137
|
+
const row = stmt.get();
|
|
138
|
+
return row.cnt;
|
|
139
|
+
}
|
|
140
|
+
clearSync() {
|
|
141
|
+
this.db.exec("DELETE FROM handle_history");
|
|
142
|
+
this.db.exec("DELETE FROM handle_registry");
|
|
143
|
+
this.db.exec("DELETE FROM card");
|
|
144
|
+
}
|
|
145
|
+
// =========== Additional sync search operations ===========
|
|
146
|
+
searchByString(searchString, pageNumber = 1, pageSize = DEFAULT_PAGE_SIZE) {
|
|
147
|
+
if (pageNumber < 1) throw new Error("Page number must be >= 1");
|
|
148
|
+
if (pageSize < 1) throw new Error("Page size must be >= 1");
|
|
149
|
+
const pattern = `%${searchString}%`;
|
|
150
|
+
const offset = (pageNumber - 1) * pageSize;
|
|
151
|
+
const countStmt = this.db.prepare(`
|
|
152
|
+
SELECT COUNT(*) as cnt FROM card
|
|
153
|
+
WHERE hash LIKE ? OR g_time LIKE ? OR CAST(content AS TEXT) LIKE ?
|
|
154
|
+
`);
|
|
155
|
+
const countRow = countStmt.get(pattern, pattern, pattern);
|
|
156
|
+
const totalItems = countRow.cnt;
|
|
157
|
+
const stmt = this.db.prepare(`
|
|
158
|
+
SELECT hash, content, g_time FROM card
|
|
159
|
+
WHERE hash LIKE ? OR g_time LIKE ? OR CAST(content AS TEXT) LIKE ?
|
|
160
|
+
ORDER BY g_time DESC LIMIT ? OFFSET ?
|
|
161
|
+
`);
|
|
162
|
+
const rows = stmt.all(pattern, pattern, pattern, pageSize, offset);
|
|
163
|
+
const items = rows.map((row) => this.rowToCard(row));
|
|
164
|
+
return createPage(items, totalItems, pageNumber, pageSize);
|
|
165
|
+
}
|
|
166
|
+
searchByContent(searchPattern, pageNumber = 1, pageSize = DEFAULT_PAGE_SIZE) {
|
|
167
|
+
if (pageNumber < 1) throw new Error("Page number must be >= 1");
|
|
168
|
+
if (pageSize < 1) throw new Error("Page size must be >= 1");
|
|
169
|
+
if (!searchPattern || typeof searchPattern === "string" && searchPattern.length === 0) {
|
|
170
|
+
throw new Error("Search pattern cannot be empty");
|
|
171
|
+
}
|
|
172
|
+
const searchBytes = typeof searchPattern === "string" ? Buffer.from(searchPattern, "utf-8") : Buffer.from(searchPattern);
|
|
173
|
+
const offset = (pageNumber - 1) * pageSize;
|
|
174
|
+
const countStmt = this.db.prepare("SELECT COUNT(*) as cnt FROM card WHERE INSTR(content, ?) > 0");
|
|
175
|
+
const countRow = countStmt.get(searchBytes);
|
|
176
|
+
const totalItems = countRow.cnt;
|
|
177
|
+
const stmt = this.db.prepare(`
|
|
178
|
+
SELECT hash, content, g_time FROM card
|
|
179
|
+
WHERE INSTR(content, ?) > 0
|
|
180
|
+
ORDER BY g_time DESC LIMIT ? OFFSET ?
|
|
181
|
+
`);
|
|
182
|
+
const rows = stmt.all(searchBytes, pageSize, offset);
|
|
183
|
+
const items = rows.map((row) => this.rowToCard(row));
|
|
184
|
+
return createPage(items, totalItems, pageNumber, pageSize);
|
|
185
|
+
}
|
|
186
|
+
getAllCards() {
|
|
187
|
+
const stmt = this.db.prepare("SELECT hash, content, g_time FROM card ORDER BY g_time DESC");
|
|
188
|
+
const rows = stmt.all();
|
|
189
|
+
const items = rows.map((row) => this.rowToCard(row));
|
|
190
|
+
return createPage(items, items.length, 1, items.length || 1);
|
|
191
|
+
}
|
|
192
|
+
// =========== Unique sync method (not in AbstractSqlEngine) ===========
|
|
193
|
+
deleteHistoryEntrySync(handle, previousHash) {
|
|
194
|
+
const { validateHandle } = (init_Handle(), __toCommonJS(Handle_exports));
|
|
195
|
+
const normalized = validateHandle(handle);
|
|
196
|
+
const performStitch = this.db.transaction(() => {
|
|
197
|
+
const rOut = this.db.prepare(
|
|
198
|
+
"SELECT id, previous_hash FROM handle_history WHERE handle = ? AND previous_hash = ? ORDER BY id DESC LIMIT 1"
|
|
199
|
+
).get(normalized, previousHash);
|
|
200
|
+
if (!rOut) return;
|
|
201
|
+
const rIn = this.db.prepare(
|
|
202
|
+
"SELECT id, previous_hash FROM handle_history WHERE handle = ? AND id < ? ORDER BY id DESC LIMIT 1"
|
|
203
|
+
).get(normalized, rOut.id);
|
|
204
|
+
if (rIn) {
|
|
205
|
+
this.db.prepare("UPDATE handle_history SET previous_hash = ? WHERE id = ?").run(rIn.previous_hash, rOut.id);
|
|
206
|
+
this.db.prepare("DELETE FROM handle_history WHERE id = ?").run(rIn.id);
|
|
207
|
+
} else {
|
|
208
|
+
this.db.prepare("DELETE FROM handle_history WHERE id = ?").run(rOut.id);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
performStitch();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export {
|
|
216
|
+
SqliteNodeEngine
|
|
217
|
+
};
|