openzca 0.1.50 → 0.1.51
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/README.md +3 -1
- package/dist/cli.js +372 -324
- package/dist/db-worker.js +292 -0
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -19,7 +19,9 @@ Or run without installing:
|
|
|
19
19
|
npx openzca --help
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Requires Node.js
|
|
22
|
+
Requires Node.js 22.13+.
|
|
23
|
+
|
|
24
|
+
The built-in DB backend now uses Node's official `node:sqlite` module, so no extra `sqlite3` native addon is installed.
|
|
23
25
|
|
|
24
26
|
## Quick start
|
|
25
27
|
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { createRequire } from "module";
|
|
4
|
+
import { createRequire as createRequire2 } from "module";
|
|
5
5
|
import { spawn as spawn2 } from "child_process";
|
|
6
6
|
import fsSync from "fs";
|
|
7
7
|
import fs6 from "fs/promises";
|
|
@@ -214,13 +214,273 @@ async function clearCache(profileName) {
|
|
|
214
214
|
// src/lib/db.ts
|
|
215
215
|
import crypto from "crypto";
|
|
216
216
|
import fs2 from "fs/promises";
|
|
217
|
+
import { createRequire } from "module";
|
|
217
218
|
import path2 from "path";
|
|
218
|
-
import {
|
|
219
|
-
|
|
219
|
+
import { Worker } from "worker_threads";
|
|
220
|
+
var require2 = createRequire(import.meta.url);
|
|
221
|
+
function buildDbError(error) {
|
|
222
|
+
const built = new Error(error.message);
|
|
223
|
+
built.name = error.name || "Error";
|
|
224
|
+
built.stack = error.stack ?? built.stack;
|
|
225
|
+
built.code = error.code;
|
|
226
|
+
return built;
|
|
227
|
+
}
|
|
228
|
+
function resolveWorkerSpec() {
|
|
229
|
+
const currentUrl = new URL(import.meta.url);
|
|
230
|
+
if (currentUrl.pathname.endsWith("/src/lib/db.ts")) {
|
|
231
|
+
return {
|
|
232
|
+
url: new URL("./db-worker.ts", currentUrl),
|
|
233
|
+
execArgv: ["--import", require2.resolve("tsx")]
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
url: new URL("./db-worker.js", currentUrl)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
var Database = class _Database {
|
|
241
|
+
#worker;
|
|
242
|
+
#closed = false;
|
|
243
|
+
#closing = false;
|
|
244
|
+
#released = false;
|
|
245
|
+
#nextId = 1;
|
|
246
|
+
#activeRequests = 0;
|
|
247
|
+
#idleTimer;
|
|
248
|
+
#closePromise;
|
|
249
|
+
#pending = /* @__PURE__ */ new Map();
|
|
250
|
+
#ready;
|
|
251
|
+
#exited;
|
|
252
|
+
#releaseConnection;
|
|
253
|
+
constructor(worker, releaseConnection) {
|
|
254
|
+
this.#worker = worker;
|
|
255
|
+
this.#releaseConnection = releaseConnection;
|
|
256
|
+
let resolveReady;
|
|
257
|
+
let rejectReady;
|
|
258
|
+
this.#ready = new Promise((resolve, reject) => {
|
|
259
|
+
resolveReady = resolve;
|
|
260
|
+
rejectReady = reject;
|
|
261
|
+
});
|
|
262
|
+
let resolveExited;
|
|
263
|
+
this.#exited = new Promise((resolve) => {
|
|
264
|
+
resolveExited = resolve;
|
|
265
|
+
});
|
|
266
|
+
const rejectPending = (error) => {
|
|
267
|
+
for (const { reject } of this.#pending.values()) {
|
|
268
|
+
reject(error);
|
|
269
|
+
}
|
|
270
|
+
this.#pending.clear();
|
|
271
|
+
};
|
|
272
|
+
worker.on("message", (message) => {
|
|
273
|
+
if (message.type === "ready") {
|
|
274
|
+
resolveReady();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (message.type === "fatal") {
|
|
278
|
+
const error = buildDbError(message.error);
|
|
279
|
+
rejectReady(error);
|
|
280
|
+
rejectPending(error);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const pending = this.#pending.get(message.id);
|
|
284
|
+
if (!pending) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this.#pending.delete(message.id);
|
|
288
|
+
if (message.type === "error") {
|
|
289
|
+
pending.reject(buildDbError(message.error));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
pending.resolve(message.result);
|
|
293
|
+
});
|
|
294
|
+
worker.once("error", (error) => {
|
|
295
|
+
rejectReady(error);
|
|
296
|
+
rejectPending(error instanceof Error ? error : new Error(String(error)));
|
|
297
|
+
});
|
|
298
|
+
worker.once("exit", (code) => {
|
|
299
|
+
this.#closed = true;
|
|
300
|
+
this.#release();
|
|
301
|
+
resolveExited();
|
|
302
|
+
const error = new Error(
|
|
303
|
+
code === 0 ? "DB worker exited" : `DB worker exited with code ${code}`
|
|
304
|
+
);
|
|
305
|
+
if (code !== 0) {
|
|
306
|
+
rejectReady(error);
|
|
307
|
+
}
|
|
308
|
+
if (this.#pending.size > 0) {
|
|
309
|
+
rejectPending(error);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
static async open(filename, onClosed) {
|
|
314
|
+
const { url, execArgv } = resolveWorkerSpec();
|
|
315
|
+
const worker = new Worker(url, {
|
|
316
|
+
execArgv,
|
|
317
|
+
workerData: { filename }
|
|
318
|
+
});
|
|
319
|
+
worker.unref();
|
|
320
|
+
const db = new _Database(worker, onClosed);
|
|
321
|
+
await db.#ready;
|
|
322
|
+
return db;
|
|
323
|
+
}
|
|
324
|
+
get isClosing() {
|
|
325
|
+
return this.#closing || this.#closed;
|
|
326
|
+
}
|
|
327
|
+
#release() {
|
|
328
|
+
if (this.#released) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
this.#released = true;
|
|
332
|
+
this.#releaseConnection();
|
|
333
|
+
}
|
|
334
|
+
#clearIdleClose() {
|
|
335
|
+
if (this.#idleTimer) {
|
|
336
|
+
clearTimeout(this.#idleTimer);
|
|
337
|
+
this.#idleTimer = void 0;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
#scheduleIdleClose() {
|
|
341
|
+
this.#clearIdleClose();
|
|
342
|
+
this.#idleTimer = setTimeout(() => {
|
|
343
|
+
if (this.#activeRequests === 0 && !this.#closed) {
|
|
344
|
+
void this.close().catch(() => {
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}, 100);
|
|
348
|
+
this.#idleTimer.unref();
|
|
349
|
+
}
|
|
350
|
+
async #request(type, payload) {
|
|
351
|
+
if (this.#closed) {
|
|
352
|
+
throw new Error("DB worker is closed");
|
|
353
|
+
}
|
|
354
|
+
if (this.#closing && type !== "close") {
|
|
355
|
+
throw new Error("DB worker is closing");
|
|
356
|
+
}
|
|
357
|
+
this.#clearIdleClose();
|
|
358
|
+
this.#activeRequests += 1;
|
|
359
|
+
await this.#ready;
|
|
360
|
+
const id = this.#nextId;
|
|
361
|
+
this.#nextId += 1;
|
|
362
|
+
const request = payload === void 0 ? { id, type } : { id, type, payload };
|
|
363
|
+
const result = new Promise((resolve, reject) => {
|
|
364
|
+
this.#pending.set(id, { resolve, reject });
|
|
365
|
+
});
|
|
366
|
+
try {
|
|
367
|
+
this.#worker.postMessage(request);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
this.#pending.delete(id);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
return await result;
|
|
374
|
+
} finally {
|
|
375
|
+
this.#activeRequests -= 1;
|
|
376
|
+
if (this.#activeRequests === 0 && !this.#closed && !this.#closing) {
|
|
377
|
+
this.#scheduleIdleClose();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async exec(sql) {
|
|
382
|
+
await this.#request("exec", { sql });
|
|
383
|
+
}
|
|
384
|
+
async run(sql, params = []) {
|
|
385
|
+
return await this.#request("run", { sql, params });
|
|
386
|
+
}
|
|
387
|
+
async get(sql, params = []) {
|
|
388
|
+
const result = await this.#request("get", { sql, params });
|
|
389
|
+
return result ?? void 0;
|
|
390
|
+
}
|
|
391
|
+
async all(sql, params = []) {
|
|
392
|
+
return await this.#request("all", { sql, params });
|
|
393
|
+
}
|
|
394
|
+
async batch(commands, transactional = false) {
|
|
395
|
+
await this.#request("batch", { commands, transactional });
|
|
396
|
+
}
|
|
397
|
+
async close() {
|
|
398
|
+
if (this.#closed) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (this.#closePromise) {
|
|
402
|
+
return this.#closePromise;
|
|
403
|
+
}
|
|
404
|
+
this.#closePromise = (async () => {
|
|
405
|
+
this.#closing = true;
|
|
406
|
+
this.#release();
|
|
407
|
+
this.#clearIdleClose();
|
|
408
|
+
await this.#request("close");
|
|
409
|
+
this.#closed = true;
|
|
410
|
+
await this.#exited;
|
|
411
|
+
})();
|
|
412
|
+
return this.#closePromise;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
220
415
|
var DB_CONFIG_FILE = "db.json";
|
|
221
416
|
var DB_FILENAME = "messages.sqlite";
|
|
222
417
|
var connections = /* @__PURE__ */ new Map();
|
|
223
418
|
var writeQueues = /* @__PURE__ */ new Map();
|
|
419
|
+
var UPSERT_THREAD_SQL = `
|
|
420
|
+
INSERT INTO threads (
|
|
421
|
+
profile, scope_thread_id, raw_thread_id, thread_type, peer_id, title,
|
|
422
|
+
is_pinned, is_hidden, is_archived, raw_json, created_at, updated_at
|
|
423
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
424
|
+
ON CONFLICT(profile, scope_thread_id) DO UPDATE SET
|
|
425
|
+
raw_thread_id = excluded.raw_thread_id,
|
|
426
|
+
thread_type = excluded.thread_type,
|
|
427
|
+
peer_id = COALESCE(excluded.peer_id, threads.peer_id),
|
|
428
|
+
title = COALESCE(excluded.title, threads.title),
|
|
429
|
+
is_pinned = excluded.is_pinned,
|
|
430
|
+
is_hidden = excluded.is_hidden,
|
|
431
|
+
is_archived = excluded.is_archived,
|
|
432
|
+
raw_json = COALESCE(excluded.raw_json, threads.raw_json),
|
|
433
|
+
updated_at = excluded.updated_at
|
|
434
|
+
`;
|
|
435
|
+
var INSERT_THREAD_MEMBER_SQL = `
|
|
436
|
+
INSERT INTO thread_members (
|
|
437
|
+
profile, scope_thread_id, user_id, display_name, zalo_name, avatar,
|
|
438
|
+
account_status, member_type, raw_json, snapshot_at_ms, created_at, updated_at
|
|
439
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
440
|
+
`;
|
|
441
|
+
var UPSERT_MESSAGE_SQL = `
|
|
442
|
+
INSERT INTO messages (
|
|
443
|
+
profile, message_uid, scope_thread_id, raw_thread_id, thread_type,
|
|
444
|
+
msg_id, cli_msg_id, action_id, sender_id, sender_name, to_id,
|
|
445
|
+
timestamp_ms, msg_type, content_text, content_json,
|
|
446
|
+
quote_msg_id, quote_cli_msg_id, quote_owner_id, quote_text,
|
|
447
|
+
source, raw_message_json, raw_payload_json, created_at, updated_at
|
|
448
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
449
|
+
ON CONFLICT(profile, message_uid) DO UPDATE SET
|
|
450
|
+
scope_thread_id = excluded.scope_thread_id,
|
|
451
|
+
raw_thread_id = excluded.raw_thread_id,
|
|
452
|
+
thread_type = excluded.thread_type,
|
|
453
|
+
msg_id = COALESCE(excluded.msg_id, messages.msg_id),
|
|
454
|
+
cli_msg_id = COALESCE(excluded.cli_msg_id, messages.cli_msg_id),
|
|
455
|
+
action_id = COALESCE(excluded.action_id, messages.action_id),
|
|
456
|
+
sender_id = COALESCE(excluded.sender_id, messages.sender_id),
|
|
457
|
+
sender_name = COALESCE(excluded.sender_name, messages.sender_name),
|
|
458
|
+
to_id = COALESCE(excluded.to_id, messages.to_id),
|
|
459
|
+
timestamp_ms = excluded.timestamp_ms,
|
|
460
|
+
msg_type = COALESCE(excluded.msg_type, messages.msg_type),
|
|
461
|
+
content_text = COALESCE(excluded.content_text, messages.content_text),
|
|
462
|
+
content_json = COALESCE(excluded.content_json, messages.content_json),
|
|
463
|
+
quote_msg_id = COALESCE(excluded.quote_msg_id, messages.quote_msg_id),
|
|
464
|
+
quote_cli_msg_id = COALESCE(excluded.quote_cli_msg_id, messages.quote_cli_msg_id),
|
|
465
|
+
quote_owner_id = COALESCE(excluded.quote_owner_id, messages.quote_owner_id),
|
|
466
|
+
quote_text = COALESCE(excluded.quote_text, messages.quote_text),
|
|
467
|
+
source = excluded.source,
|
|
468
|
+
raw_message_json = COALESCE(excluded.raw_message_json, messages.raw_message_json),
|
|
469
|
+
raw_payload_json = COALESCE(excluded.raw_payload_json, messages.raw_payload_json),
|
|
470
|
+
updated_at = excluded.updated_at
|
|
471
|
+
`;
|
|
472
|
+
var INSERT_MESSAGE_MEDIA_SQL = `
|
|
473
|
+
INSERT INTO message_media (
|
|
474
|
+
profile, message_uid, item_index, media_kind, media_url,
|
|
475
|
+
media_path, media_type, raw_json, created_at, updated_at
|
|
476
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
477
|
+
`;
|
|
478
|
+
var INSERT_MESSAGE_MENTION_SQL = `
|
|
479
|
+
INSERT INTO message_mentions (
|
|
480
|
+
profile, message_uid, item_index, target_user_id, pos, len,
|
|
481
|
+
mention_type, raw_json, created_at, updated_at
|
|
482
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
483
|
+
`;
|
|
224
484
|
function nowIso2() {
|
|
225
485
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
226
486
|
}
|
|
@@ -326,165 +586,21 @@ async function resolveDbPath(profile) {
|
|
|
326
586
|
}
|
|
327
587
|
return path2.isAbsolute(configured) ? configured : path2.resolve(getProfileDir(profile), configured);
|
|
328
588
|
}
|
|
329
|
-
async function migrateDb(db) {
|
|
330
|
-
await db.exec(`
|
|
331
|
-
PRAGMA journal_mode = WAL;
|
|
332
|
-
PRAGMA foreign_keys = ON;
|
|
333
|
-
|
|
334
|
-
CREATE TABLE IF NOT EXISTS threads (
|
|
335
|
-
profile TEXT NOT NULL,
|
|
336
|
-
scope_thread_id TEXT NOT NULL,
|
|
337
|
-
raw_thread_id TEXT NOT NULL,
|
|
338
|
-
thread_type TEXT NOT NULL,
|
|
339
|
-
peer_id TEXT,
|
|
340
|
-
title TEXT,
|
|
341
|
-
is_pinned INTEGER NOT NULL DEFAULT 0,
|
|
342
|
-
is_hidden INTEGER NOT NULL DEFAULT 0,
|
|
343
|
-
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
344
|
-
raw_json TEXT,
|
|
345
|
-
created_at TEXT NOT NULL,
|
|
346
|
-
updated_at TEXT NOT NULL,
|
|
347
|
-
PRIMARY KEY (profile, scope_thread_id)
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
CREATE TABLE IF NOT EXISTS thread_members (
|
|
351
|
-
profile TEXT NOT NULL,
|
|
352
|
-
scope_thread_id TEXT NOT NULL,
|
|
353
|
-
user_id TEXT NOT NULL,
|
|
354
|
-
display_name TEXT,
|
|
355
|
-
zalo_name TEXT,
|
|
356
|
-
avatar TEXT,
|
|
357
|
-
account_status INTEGER,
|
|
358
|
-
member_type INTEGER,
|
|
359
|
-
raw_json TEXT,
|
|
360
|
-
snapshot_at_ms INTEGER NOT NULL,
|
|
361
|
-
created_at TEXT NOT NULL,
|
|
362
|
-
updated_at TEXT NOT NULL,
|
|
363
|
-
PRIMARY KEY (profile, scope_thread_id, user_id)
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
CREATE TABLE IF NOT EXISTS friends (
|
|
367
|
-
profile TEXT NOT NULL,
|
|
368
|
-
user_id TEXT NOT NULL,
|
|
369
|
-
display_name TEXT,
|
|
370
|
-
zalo_name TEXT,
|
|
371
|
-
avatar TEXT,
|
|
372
|
-
account_status INTEGER,
|
|
373
|
-
raw_json TEXT,
|
|
374
|
-
created_at TEXT NOT NULL,
|
|
375
|
-
updated_at TEXT NOT NULL,
|
|
376
|
-
PRIMARY KEY (profile, user_id)
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
CREATE TABLE IF NOT EXISTS self_profiles (
|
|
380
|
-
profile TEXT NOT NULL,
|
|
381
|
-
user_id TEXT NOT NULL,
|
|
382
|
-
display_name TEXT,
|
|
383
|
-
info_json TEXT,
|
|
384
|
-
created_at TEXT NOT NULL,
|
|
385
|
-
updated_at TEXT NOT NULL,
|
|
386
|
-
PRIMARY KEY (profile)
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
390
|
-
profile TEXT NOT NULL,
|
|
391
|
-
message_uid TEXT NOT NULL,
|
|
392
|
-
scope_thread_id TEXT NOT NULL,
|
|
393
|
-
raw_thread_id TEXT NOT NULL,
|
|
394
|
-
thread_type TEXT NOT NULL,
|
|
395
|
-
msg_id TEXT,
|
|
396
|
-
cli_msg_id TEXT,
|
|
397
|
-
action_id TEXT,
|
|
398
|
-
sender_id TEXT,
|
|
399
|
-
sender_name TEXT,
|
|
400
|
-
to_id TEXT,
|
|
401
|
-
timestamp_ms INTEGER NOT NULL,
|
|
402
|
-
msg_type TEXT,
|
|
403
|
-
content_text TEXT,
|
|
404
|
-
content_json TEXT,
|
|
405
|
-
quote_msg_id TEXT,
|
|
406
|
-
quote_cli_msg_id TEXT,
|
|
407
|
-
quote_owner_id TEXT,
|
|
408
|
-
quote_text TEXT,
|
|
409
|
-
source TEXT NOT NULL,
|
|
410
|
-
raw_message_json TEXT,
|
|
411
|
-
raw_payload_json TEXT,
|
|
412
|
-
created_at TEXT NOT NULL,
|
|
413
|
-
updated_at TEXT NOT NULL,
|
|
414
|
-
PRIMARY KEY (profile, message_uid)
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
CREATE TABLE IF NOT EXISTS message_media (
|
|
418
|
-
profile TEXT NOT NULL,
|
|
419
|
-
message_uid TEXT NOT NULL,
|
|
420
|
-
item_index INTEGER NOT NULL,
|
|
421
|
-
media_kind TEXT,
|
|
422
|
-
media_url TEXT,
|
|
423
|
-
media_path TEXT,
|
|
424
|
-
media_type TEXT,
|
|
425
|
-
raw_json TEXT,
|
|
426
|
-
created_at TEXT NOT NULL,
|
|
427
|
-
updated_at TEXT NOT NULL,
|
|
428
|
-
PRIMARY KEY (profile, message_uid, item_index)
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
432
|
-
profile TEXT NOT NULL,
|
|
433
|
-
message_uid TEXT NOT NULL,
|
|
434
|
-
item_index INTEGER NOT NULL,
|
|
435
|
-
target_user_id TEXT NOT NULL,
|
|
436
|
-
pos INTEGER,
|
|
437
|
-
len INTEGER,
|
|
438
|
-
mention_type INTEGER,
|
|
439
|
-
raw_json TEXT,
|
|
440
|
-
created_at TEXT NOT NULL,
|
|
441
|
-
updated_at TEXT NOT NULL,
|
|
442
|
-
PRIMARY KEY (profile, message_uid, item_index)
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
CREATE TABLE IF NOT EXISTS sync_state (
|
|
446
|
-
profile TEXT NOT NULL,
|
|
447
|
-
scope TEXT NOT NULL,
|
|
448
|
-
scope_thread_id TEXT NOT NULL,
|
|
449
|
-
thread_type TEXT NOT NULL,
|
|
450
|
-
status TEXT NOT NULL,
|
|
451
|
-
completeness TEXT,
|
|
452
|
-
cursor TEXT,
|
|
453
|
-
last_sync_at TEXT,
|
|
454
|
-
error TEXT,
|
|
455
|
-
created_at TEXT NOT NULL,
|
|
456
|
-
updated_at TEXT NOT NULL,
|
|
457
|
-
PRIMARY KEY (profile, scope)
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
CREATE INDEX IF NOT EXISTS idx_messages_thread_time
|
|
461
|
-
ON messages (profile, scope_thread_id, timestamp_ms DESC);
|
|
462
|
-
CREATE INDEX IF NOT EXISTS idx_messages_msg_id
|
|
463
|
-
ON messages (profile, msg_id);
|
|
464
|
-
CREATE INDEX IF NOT EXISTS idx_messages_cli_msg_id
|
|
465
|
-
ON messages (profile, cli_msg_id);
|
|
466
|
-
CREATE INDEX IF NOT EXISTS idx_threads_type
|
|
467
|
-
ON threads (profile, thread_type, updated_at DESC);
|
|
468
|
-
CREATE INDEX IF NOT EXISTS idx_members_thread
|
|
469
|
-
ON thread_members (profile, scope_thread_id);
|
|
470
|
-
CREATE INDEX IF NOT EXISTS idx_friends_name
|
|
471
|
-
ON friends (profile, display_name, zalo_name, user_id);
|
|
472
|
-
`);
|
|
473
|
-
}
|
|
474
589
|
async function openDb(profile) {
|
|
475
590
|
const filename = await resolveDbPath(profile);
|
|
476
591
|
await fs2.mkdir(path2.dirname(filename), { recursive: true });
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
driver: sqlite3.Database
|
|
592
|
+
return Database.open(filename, () => {
|
|
593
|
+
connections.delete(profile);
|
|
480
594
|
});
|
|
481
|
-
await migrateDb(db);
|
|
482
|
-
return db;
|
|
483
595
|
}
|
|
484
596
|
async function getDb(profile) {
|
|
485
597
|
const existing = connections.get(profile);
|
|
486
598
|
if (existing) {
|
|
487
|
-
|
|
599
|
+
const db = await existing;
|
|
600
|
+
if (!db.isClosing) {
|
|
601
|
+
return db;
|
|
602
|
+
}
|
|
603
|
+
connections.delete(profile);
|
|
488
604
|
}
|
|
489
605
|
const created = openDb(profile).catch((error) => {
|
|
490
606
|
connections.delete(profile);
|
|
@@ -596,77 +712,48 @@ function enqueueDbWrite(profile, task) {
|
|
|
596
712
|
async function persistThread(record) {
|
|
597
713
|
const db = await getDb(record.profile);
|
|
598
714
|
const now = nowIso2();
|
|
599
|
-
await db.run(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
raw_json = COALESCE(excluded.raw_json, threads.raw_json),
|
|
614
|
-
updated_at = excluded.updated_at
|
|
615
|
-
`,
|
|
616
|
-
[
|
|
617
|
-
record.profile,
|
|
618
|
-
record.scopeThreadId,
|
|
619
|
-
record.rawThreadId,
|
|
620
|
-
record.threadType,
|
|
621
|
-
record.peerId ?? null,
|
|
622
|
-
record.title ?? null,
|
|
623
|
-
record.isPinned ? 1 : 0,
|
|
624
|
-
record.isHidden ? 1 : 0,
|
|
625
|
-
record.isArchived ? 1 : 0,
|
|
626
|
-
record.rawJson ?? null,
|
|
627
|
-
now,
|
|
628
|
-
now
|
|
629
|
-
]
|
|
630
|
-
);
|
|
715
|
+
await db.run(UPSERT_THREAD_SQL, [
|
|
716
|
+
record.profile,
|
|
717
|
+
record.scopeThreadId,
|
|
718
|
+
record.rawThreadId,
|
|
719
|
+
record.threadType,
|
|
720
|
+
record.peerId ?? null,
|
|
721
|
+
record.title ?? null,
|
|
722
|
+
record.isPinned ? 1 : 0,
|
|
723
|
+
record.isHidden ? 1 : 0,
|
|
724
|
+
record.isArchived ? 1 : 0,
|
|
725
|
+
record.rawJson ?? null,
|
|
726
|
+
now,
|
|
727
|
+
now
|
|
728
|
+
]);
|
|
631
729
|
}
|
|
632
730
|
async function replaceThreadMembers(profile, scopeThreadId, members) {
|
|
633
731
|
const db = await getDb(profile);
|
|
634
732
|
const now = nowIso2();
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
)
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
member.snapshotAtMs,
|
|
660
|
-
now,
|
|
661
|
-
now
|
|
662
|
-
]
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
await db.exec("COMMIT");
|
|
666
|
-
} catch (error) {
|
|
667
|
-
await db.exec("ROLLBACK");
|
|
668
|
-
throw error;
|
|
669
|
-
}
|
|
733
|
+
const commands = [
|
|
734
|
+
{
|
|
735
|
+
sql: `DELETE FROM thread_members WHERE profile = ? AND scope_thread_id = ?`,
|
|
736
|
+
params: [profile, scopeThreadId]
|
|
737
|
+
},
|
|
738
|
+
...members.map((member) => ({
|
|
739
|
+
sql: INSERT_THREAD_MEMBER_SQL,
|
|
740
|
+
params: [
|
|
741
|
+
member.profile,
|
|
742
|
+
member.scopeThreadId,
|
|
743
|
+
member.userId,
|
|
744
|
+
member.displayName ?? null,
|
|
745
|
+
member.zaloName ?? null,
|
|
746
|
+
member.avatar ?? null,
|
|
747
|
+
member.accountStatus ?? null,
|
|
748
|
+
member.memberType ?? null,
|
|
749
|
+
member.rawJson ?? null,
|
|
750
|
+
member.snapshotAtMs,
|
|
751
|
+
now,
|
|
752
|
+
now
|
|
753
|
+
]
|
|
754
|
+
}))
|
|
755
|
+
];
|
|
756
|
+
await db.batch(commands, true);
|
|
670
757
|
}
|
|
671
758
|
async function persistFriend(record) {
|
|
672
759
|
const db = await getDb(record.profile);
|
|
@@ -726,49 +813,27 @@ async function persistMessage(record) {
|
|
|
726
813
|
const db = await getDb(record.profile);
|
|
727
814
|
const now = nowIso2();
|
|
728
815
|
const messageUid = toMessageUid(record);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
raw_thread_id = excluded.raw_thread_id,
|
|
751
|
-
thread_type = excluded.thread_type,
|
|
752
|
-
msg_id = COALESCE(excluded.msg_id, messages.msg_id),
|
|
753
|
-
cli_msg_id = COALESCE(excluded.cli_msg_id, messages.cli_msg_id),
|
|
754
|
-
action_id = COALESCE(excluded.action_id, messages.action_id),
|
|
755
|
-
sender_id = COALESCE(excluded.sender_id, messages.sender_id),
|
|
756
|
-
sender_name = COALESCE(excluded.sender_name, messages.sender_name),
|
|
757
|
-
to_id = COALESCE(excluded.to_id, messages.to_id),
|
|
758
|
-
timestamp_ms = excluded.timestamp_ms,
|
|
759
|
-
msg_type = COALESCE(excluded.msg_type, messages.msg_type),
|
|
760
|
-
content_text = COALESCE(excluded.content_text, messages.content_text),
|
|
761
|
-
content_json = COALESCE(excluded.content_json, messages.content_json),
|
|
762
|
-
quote_msg_id = COALESCE(excluded.quote_msg_id, messages.quote_msg_id),
|
|
763
|
-
quote_cli_msg_id = COALESCE(excluded.quote_cli_msg_id, messages.quote_cli_msg_id),
|
|
764
|
-
quote_owner_id = COALESCE(excluded.quote_owner_id, messages.quote_owner_id),
|
|
765
|
-
quote_text = COALESCE(excluded.quote_text, messages.quote_text),
|
|
766
|
-
source = excluded.source,
|
|
767
|
-
raw_message_json = COALESCE(excluded.raw_message_json, messages.raw_message_json),
|
|
768
|
-
raw_payload_json = COALESCE(excluded.raw_payload_json, messages.raw_payload_json),
|
|
769
|
-
updated_at = excluded.updated_at
|
|
770
|
-
`,
|
|
771
|
-
[
|
|
816
|
+
const commands = [
|
|
817
|
+
{
|
|
818
|
+
sql: UPSERT_THREAD_SQL,
|
|
819
|
+
params: [
|
|
820
|
+
record.profile,
|
|
821
|
+
record.scopeThreadId,
|
|
822
|
+
record.rawThreadId,
|
|
823
|
+
record.threadType,
|
|
824
|
+
record.peerId ?? null,
|
|
825
|
+
record.title ?? null,
|
|
826
|
+
0,
|
|
827
|
+
0,
|
|
828
|
+
0,
|
|
829
|
+
null,
|
|
830
|
+
now,
|
|
831
|
+
now
|
|
832
|
+
]
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
sql: UPSERT_MESSAGE_SQL,
|
|
836
|
+
params: [
|
|
772
837
|
record.profile,
|
|
773
838
|
messageUid,
|
|
774
839
|
record.scopeThreadId,
|
|
@@ -794,64 +859,47 @@ async function persistMessage(record) {
|
|
|
794
859
|
now,
|
|
795
860
|
now
|
|
796
861
|
]
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
`DELETE FROM message_media WHERE profile = ? AND message_uid = ?`,
|
|
800
|
-
[record.profile, messageUid]
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
`DELETE FROM message_mentions WHERE profile = ? AND message_uid = ?`,
|
|
804
|
-
[record.profile, messageUid]
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
messageUid,
|
|
839
|
-
index,
|
|
840
|
-
mention.uid,
|
|
841
|
-
mention.pos ?? null,
|
|
842
|
-
mention.len ?? null,
|
|
843
|
-
mention.type ?? null,
|
|
844
|
-
mention.rawJson ?? null,
|
|
845
|
-
now,
|
|
846
|
-
now
|
|
847
|
-
]
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
await db.exec("COMMIT");
|
|
851
|
-
} catch (error) {
|
|
852
|
-
await db.exec("ROLLBACK");
|
|
853
|
-
throw error;
|
|
854
|
-
}
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
sql: `DELETE FROM message_media WHERE profile = ? AND message_uid = ?`,
|
|
865
|
+
params: [record.profile, messageUid]
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
sql: `DELETE FROM message_mentions WHERE profile = ? AND message_uid = ?`,
|
|
869
|
+
params: [record.profile, messageUid]
|
|
870
|
+
},
|
|
871
|
+
...(record.media ?? []).map((media, index) => ({
|
|
872
|
+
sql: INSERT_MESSAGE_MEDIA_SQL,
|
|
873
|
+
params: [
|
|
874
|
+
record.profile,
|
|
875
|
+
messageUid,
|
|
876
|
+
index,
|
|
877
|
+
media.mediaKind ?? null,
|
|
878
|
+
media.mediaUrl ?? null,
|
|
879
|
+
media.mediaPath ?? null,
|
|
880
|
+
media.mediaType ?? null,
|
|
881
|
+
media.rawJson ?? null,
|
|
882
|
+
now,
|
|
883
|
+
now
|
|
884
|
+
]
|
|
885
|
+
})),
|
|
886
|
+
...(record.mentions ?? []).map((mention, index) => ({
|
|
887
|
+
sql: INSERT_MESSAGE_MENTION_SQL,
|
|
888
|
+
params: [
|
|
889
|
+
record.profile,
|
|
890
|
+
messageUid,
|
|
891
|
+
index,
|
|
892
|
+
mention.uid,
|
|
893
|
+
mention.pos ?? null,
|
|
894
|
+
mention.len ?? null,
|
|
895
|
+
mention.type ?? null,
|
|
896
|
+
mention.rawJson ?? null,
|
|
897
|
+
now,
|
|
898
|
+
now
|
|
899
|
+
]
|
|
900
|
+
}))
|
|
901
|
+
];
|
|
902
|
+
await db.batch(commands, true);
|
|
855
903
|
}
|
|
856
904
|
async function setSyncState(params) {
|
|
857
905
|
const db = await getDb(params.profile);
|
|
@@ -2720,8 +2768,8 @@ function inferReplyMessageThreadId(params) {
|
|
|
2720
2768
|
}
|
|
2721
2769
|
|
|
2722
2770
|
// src/cli.ts
|
|
2723
|
-
var
|
|
2724
|
-
var { version: PKG_VERSION } =
|
|
2771
|
+
var require3 = createRequire2(import.meta.url);
|
|
2772
|
+
var { version: PKG_VERSION } = require3("../package.json");
|
|
2725
2773
|
var program = new Command();
|
|
2726
2774
|
var EMOJI_REACTION_MAP = {
|
|
2727
2775
|
"\u2764\uFE0F": Reactions.HEART,
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// src/lib/db-worker.ts
|
|
2
|
+
import { parentPort, workerData } from "worker_threads";
|
|
3
|
+
var INIT_SQL = `
|
|
4
|
+
PRAGMA journal_mode = WAL;
|
|
5
|
+
PRAGMA synchronous = FULL;
|
|
6
|
+
PRAGMA busy_timeout = 5000;
|
|
7
|
+
PRAGMA foreign_keys = ON;
|
|
8
|
+
|
|
9
|
+
CREATE TABLE IF NOT EXISTS threads (
|
|
10
|
+
profile TEXT NOT NULL,
|
|
11
|
+
scope_thread_id TEXT NOT NULL,
|
|
12
|
+
raw_thread_id TEXT NOT NULL,
|
|
13
|
+
thread_type TEXT NOT NULL,
|
|
14
|
+
peer_id TEXT,
|
|
15
|
+
title TEXT,
|
|
16
|
+
is_pinned INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
is_hidden INTEGER NOT NULL DEFAULT 0,
|
|
18
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
raw_json TEXT,
|
|
20
|
+
created_at TEXT NOT NULL,
|
|
21
|
+
updated_at TEXT NOT NULL,
|
|
22
|
+
PRIMARY KEY (profile, scope_thread_id)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE TABLE IF NOT EXISTS thread_members (
|
|
26
|
+
profile TEXT NOT NULL,
|
|
27
|
+
scope_thread_id TEXT NOT NULL,
|
|
28
|
+
user_id TEXT NOT NULL,
|
|
29
|
+
display_name TEXT,
|
|
30
|
+
zalo_name TEXT,
|
|
31
|
+
avatar TEXT,
|
|
32
|
+
account_status INTEGER,
|
|
33
|
+
member_type INTEGER,
|
|
34
|
+
raw_json TEXT,
|
|
35
|
+
snapshot_at_ms INTEGER NOT NULL,
|
|
36
|
+
created_at TEXT NOT NULL,
|
|
37
|
+
updated_at TEXT NOT NULL,
|
|
38
|
+
PRIMARY KEY (profile, scope_thread_id, user_id)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS friends (
|
|
42
|
+
profile TEXT NOT NULL,
|
|
43
|
+
user_id TEXT NOT NULL,
|
|
44
|
+
display_name TEXT,
|
|
45
|
+
zalo_name TEXT,
|
|
46
|
+
avatar TEXT,
|
|
47
|
+
account_status INTEGER,
|
|
48
|
+
raw_json TEXT,
|
|
49
|
+
created_at TEXT NOT NULL,
|
|
50
|
+
updated_at TEXT NOT NULL,
|
|
51
|
+
PRIMARY KEY (profile, user_id)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS self_profiles (
|
|
55
|
+
profile TEXT NOT NULL,
|
|
56
|
+
user_id TEXT NOT NULL,
|
|
57
|
+
display_name TEXT,
|
|
58
|
+
info_json TEXT,
|
|
59
|
+
created_at TEXT NOT NULL,
|
|
60
|
+
updated_at TEXT NOT NULL,
|
|
61
|
+
PRIMARY KEY (profile)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
65
|
+
profile TEXT NOT NULL,
|
|
66
|
+
message_uid TEXT NOT NULL,
|
|
67
|
+
scope_thread_id TEXT NOT NULL,
|
|
68
|
+
raw_thread_id TEXT NOT NULL,
|
|
69
|
+
thread_type TEXT NOT NULL,
|
|
70
|
+
msg_id TEXT,
|
|
71
|
+
cli_msg_id TEXT,
|
|
72
|
+
action_id TEXT,
|
|
73
|
+
sender_id TEXT,
|
|
74
|
+
sender_name TEXT,
|
|
75
|
+
to_id TEXT,
|
|
76
|
+
timestamp_ms INTEGER NOT NULL,
|
|
77
|
+
msg_type TEXT,
|
|
78
|
+
content_text TEXT,
|
|
79
|
+
content_json TEXT,
|
|
80
|
+
quote_msg_id TEXT,
|
|
81
|
+
quote_cli_msg_id TEXT,
|
|
82
|
+
quote_owner_id TEXT,
|
|
83
|
+
quote_text TEXT,
|
|
84
|
+
source TEXT NOT NULL,
|
|
85
|
+
raw_message_json TEXT,
|
|
86
|
+
raw_payload_json TEXT,
|
|
87
|
+
created_at TEXT NOT NULL,
|
|
88
|
+
updated_at TEXT NOT NULL,
|
|
89
|
+
PRIMARY KEY (profile, message_uid)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS message_media (
|
|
93
|
+
profile TEXT NOT NULL,
|
|
94
|
+
message_uid TEXT NOT NULL,
|
|
95
|
+
item_index INTEGER NOT NULL,
|
|
96
|
+
media_kind TEXT,
|
|
97
|
+
media_url TEXT,
|
|
98
|
+
media_path TEXT,
|
|
99
|
+
media_type TEXT,
|
|
100
|
+
raw_json TEXT,
|
|
101
|
+
created_at TEXT NOT NULL,
|
|
102
|
+
updated_at TEXT NOT NULL,
|
|
103
|
+
PRIMARY KEY (profile, message_uid, item_index)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
107
|
+
profile TEXT NOT NULL,
|
|
108
|
+
message_uid TEXT NOT NULL,
|
|
109
|
+
item_index INTEGER NOT NULL,
|
|
110
|
+
target_user_id TEXT NOT NULL,
|
|
111
|
+
pos INTEGER,
|
|
112
|
+
len INTEGER,
|
|
113
|
+
mention_type INTEGER,
|
|
114
|
+
raw_json TEXT,
|
|
115
|
+
created_at TEXT NOT NULL,
|
|
116
|
+
updated_at TEXT NOT NULL,
|
|
117
|
+
PRIMARY KEY (profile, message_uid, item_index)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE IF NOT EXISTS sync_state (
|
|
121
|
+
profile TEXT NOT NULL,
|
|
122
|
+
scope TEXT NOT NULL,
|
|
123
|
+
scope_thread_id TEXT NOT NULL,
|
|
124
|
+
thread_type TEXT NOT NULL,
|
|
125
|
+
status TEXT NOT NULL,
|
|
126
|
+
completeness TEXT,
|
|
127
|
+
cursor TEXT,
|
|
128
|
+
last_sync_at TEXT,
|
|
129
|
+
error TEXT,
|
|
130
|
+
created_at TEXT NOT NULL,
|
|
131
|
+
updated_at TEXT NOT NULL,
|
|
132
|
+
PRIMARY KEY (profile, scope)
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_messages_thread_time
|
|
136
|
+
ON messages (profile, scope_thread_id, timestamp_ms DESC);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_messages_msg_id
|
|
138
|
+
ON messages (profile, msg_id);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_messages_cli_msg_id
|
|
140
|
+
ON messages (profile, cli_msg_id);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_threads_type
|
|
142
|
+
ON threads (profile, thread_type, updated_at DESC);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_members_thread
|
|
144
|
+
ON thread_members (profile, scope_thread_id);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_friends_name
|
|
146
|
+
ON friends (profile, display_name, zalo_name, user_id);
|
|
147
|
+
`;
|
|
148
|
+
if (!parentPort) {
|
|
149
|
+
throw new Error("DB worker requires parentPort");
|
|
150
|
+
}
|
|
151
|
+
var port = parentPort;
|
|
152
|
+
function serializeError(error) {
|
|
153
|
+
if (error instanceof Error) {
|
|
154
|
+
return {
|
|
155
|
+
name: error.name,
|
|
156
|
+
message: error.message,
|
|
157
|
+
stack: error.stack,
|
|
158
|
+
code: typeof error.code === "string" ? error.code : void 0
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
name: "Error",
|
|
163
|
+
message: String(error)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function loadSqliteModule() {
|
|
167
|
+
const originalEmitWarning = process.emitWarning;
|
|
168
|
+
const importDynamic = new Function("specifier", "return import(specifier);");
|
|
169
|
+
process.emitWarning = ((warning, options, ...args) => {
|
|
170
|
+
const type = typeof options === "string" ? options : void 0;
|
|
171
|
+
const message = warning instanceof Error ? warning.message : String(warning);
|
|
172
|
+
if (type === "ExperimentalWarning" && message.includes("SQLite")) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
Reflect.apply(originalEmitWarning, process, [warning, options, ...args]);
|
|
176
|
+
});
|
|
177
|
+
try {
|
|
178
|
+
return await importDynamic("node:sqlite");
|
|
179
|
+
} finally {
|
|
180
|
+
process.emitWarning = originalEmitWarning;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function setDefensiveMode(db) {
|
|
184
|
+
const maybeDb = db;
|
|
185
|
+
if (typeof maybeDb.enableDefensive === "function") {
|
|
186
|
+
maybeDb.enableDefensive(true);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function runStatement(db, statement) {
|
|
190
|
+
return db.prepare(statement.sql).run(...statement.params ?? []);
|
|
191
|
+
}
|
|
192
|
+
function getStatement(db, statement) {
|
|
193
|
+
return db.prepare(statement.sql).get(...statement.params ?? []);
|
|
194
|
+
}
|
|
195
|
+
function allStatement(db, statement) {
|
|
196
|
+
return db.prepare(statement.sql).all(...statement.params ?? []);
|
|
197
|
+
}
|
|
198
|
+
async function main() {
|
|
199
|
+
const { DatabaseSync } = await loadSqliteModule();
|
|
200
|
+
const { filename } = workerData;
|
|
201
|
+
const db = new DatabaseSync(filename);
|
|
202
|
+
db.exec(INIT_SQL);
|
|
203
|
+
setDefensiveMode(db);
|
|
204
|
+
port.postMessage({ type: "ready" });
|
|
205
|
+
port.on("message", (message) => {
|
|
206
|
+
try {
|
|
207
|
+
switch (message.type) {
|
|
208
|
+
case "exec":
|
|
209
|
+
db.exec(message.payload.sql);
|
|
210
|
+
port.postMessage({
|
|
211
|
+
type: "result",
|
|
212
|
+
id: message.id,
|
|
213
|
+
result: null
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
case "run":
|
|
217
|
+
port.postMessage({
|
|
218
|
+
type: "result",
|
|
219
|
+
id: message.id,
|
|
220
|
+
result: runStatement(db, message.payload)
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
case "get":
|
|
224
|
+
port.postMessage({
|
|
225
|
+
type: "result",
|
|
226
|
+
id: message.id,
|
|
227
|
+
result: getStatement(db, message.payload) ?? null
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
case "all":
|
|
231
|
+
port.postMessage({
|
|
232
|
+
type: "result",
|
|
233
|
+
id: message.id,
|
|
234
|
+
result: allStatement(db, message.payload)
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
case "batch":
|
|
238
|
+
if (message.payload.transactional) {
|
|
239
|
+
db.exec("BEGIN");
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
for (const command of message.payload.commands) {
|
|
243
|
+
if ((command.params ?? []).length === 0) {
|
|
244
|
+
db.exec(command.sql);
|
|
245
|
+
} else {
|
|
246
|
+
runStatement(db, command);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (message.payload.transactional) {
|
|
250
|
+
db.exec("COMMIT");
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (message.payload.transactional) {
|
|
254
|
+
try {
|
|
255
|
+
db.exec("ROLLBACK");
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
port.postMessage({
|
|
262
|
+
type: "result",
|
|
263
|
+
id: message.id,
|
|
264
|
+
result: null
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
case "close":
|
|
268
|
+
db.close();
|
|
269
|
+
port.postMessage({
|
|
270
|
+
type: "result",
|
|
271
|
+
id: message.id,
|
|
272
|
+
result: null
|
|
273
|
+
});
|
|
274
|
+
setImmediate(() => process.exit(0));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
port.postMessage({
|
|
279
|
+
type: "error",
|
|
280
|
+
id: message.id,
|
|
281
|
+
error: serializeError(error)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
void main().catch((error) => {
|
|
287
|
+
port.postMessage({
|
|
288
|
+
type: "fatal",
|
|
289
|
+
error: serializeError(error)
|
|
290
|
+
});
|
|
291
|
+
process.exit(1);
|
|
292
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzca",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"description": "Open-source zca-compatible CLI to integrate Zalo with OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "
|
|
17
|
+
"build": "npm run build:cli && npm run build:worker",
|
|
18
|
+
"build:cli": "tsup src/cli.ts --format esm --target node22 --out-dir dist --clean",
|
|
19
|
+
"build:worker": "tsup src/lib/db-worker.ts --format esm --target node22 --out-dir dist",
|
|
18
20
|
"dev": "tsx src/cli.ts",
|
|
19
21
|
"test": "tsx --test tests/*.test.ts",
|
|
20
22
|
"typecheck": "tsc -p tsconfig.json",
|
|
21
23
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
24
|
+
"prepare": "npm run build",
|
|
22
25
|
"prepublishOnly": "npm run build"
|
|
23
26
|
},
|
|
24
27
|
"keywords": [
|
|
@@ -39,15 +42,13 @@
|
|
|
39
42
|
"url": "https://github.com/darkamenosa/openzca/issues"
|
|
40
43
|
},
|
|
41
44
|
"engines": {
|
|
42
|
-
"node": ">=
|
|
45
|
+
"node": ">=22.13.0"
|
|
43
46
|
},
|
|
44
47
|
"dependencies": {
|
|
45
48
|
"@types/qrcode-terminal": "^0.12.2",
|
|
46
49
|
"commander": "^14.0.3",
|
|
47
50
|
"image-size": "^2.0.2",
|
|
48
51
|
"qrcode-terminal": "^0.12.0",
|
|
49
|
-
"sqlite": "^5.1.1",
|
|
50
|
-
"sqlite3": "^6.0.1",
|
|
51
52
|
"zca-js": "^2.1.2"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|