@wirecat/cli-core 0.484.0 → 0.485.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/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.485.0 — 30.10.2026
11
+
12
+ ### Fixed
13
+
14
+ - **A store write no longer fails with "database is locked" while another process writes steadily.** SQLite's own wait
15
+ for the write lock sleeps up to 100 ms between tries, so a process that commits and begins again at once was never
16
+ seen free, and the waiter failed after the 5 s `busy_timeout` (`mail sync` against `memo sync`). A write now tries
17
+ for the lock every millisecond within the same `busy_timeout`. `saveAccount` takes the lock the same way instead
18
+ of as a bare statement.
19
+
10
20
  ## 0.484.0 — 30.10.2026
11
21
 
12
22
  ### Fixed
@@ -11,8 +11,11 @@ export const accountAccountsOver = (context) => {
11
11
  const { accountPk, findAccountPk } = keysOver(context);
12
12
  return {
13
13
  saveAccount: async (key, { name }) => {
14
- const pk = accountPk(key, name);
15
- linkInbox(database, `${key.provider}:${key.account}`, pk);
14
+ const pk = write(database, () => {
15
+ const made = accountPk(key, name);
16
+ linkInbox(database, `${key.provider}:${key.account}`, made);
17
+ return made;
18
+ });
16
19
  linkOwnIdentities(context);
17
20
  return pk;
18
21
  },
@@ -1,6 +1,39 @@
1
1
  import { drainInvolvementQueue } from "./involvement-queue.js";
2
2
  import { drainMentions } from "./mention-drain.js";
3
3
  import { drainStems, stemmerCache } from "./stems.js";
4
+ const SQLITE_BUSY = 5;
5
+ const pause = new Int32Array(new SharedArrayBuffer(4));
6
+ const isBusy = (error) => {
7
+ const { errcode, errno } = (error ?? {});
8
+ const code = errcode ?? errno;
9
+ return typeof code === "number" && (code & 0xff) === SQLITE_BUSY;
10
+ };
11
+ /**
12
+ * Takes the write lock, waiting as long as the connection's `busy_timeout`. SQLite's own wait sleeps 1 ms, then up to
13
+ * 100 ms between tries, so a process that commits and begins again at once is never seen free and the waiter fails
14
+ * with "database is locked" after the full timeout. Trying every millisecond sees the gap between two writes.
15
+ */
16
+ const beginImmediate = (database) => {
17
+ const timeout = Number(database.prepare("PRAGMA busy_timeout").get()?.timeout ?? 0);
18
+ const deadline = Date.now() + timeout;
19
+ database.exec("PRAGMA busy_timeout = 0");
20
+ try {
21
+ for (;;) {
22
+ try {
23
+ database.exec("BEGIN IMMEDIATE");
24
+ return;
25
+ }
26
+ catch (error) {
27
+ if (!isBusy(error) || Date.now() >= deadline)
28
+ throw error;
29
+ Atomics.wait(pause, 0, 0, 1);
30
+ }
31
+ }
32
+ }
33
+ finally {
34
+ database.exec(`PRAGMA busy_timeout = ${timeout}`);
35
+ }
36
+ };
4
37
  const connections = new WeakMap();
5
38
  const connectionOf = (database) => {
6
39
  let connection = connections.get(database);
@@ -29,7 +62,10 @@ const transact = (database, body, drain, beforeCommit) => {
29
62
  throw new Error("a store write cannot run inside a read");
30
63
  const outer = connection.depth === 0;
31
64
  const name = `w${connection.depth + 1}`;
32
- database.exec(outer ? "BEGIN IMMEDIATE" : `SAVEPOINT ${name}`);
65
+ if (outer)
66
+ beginImmediate(database);
67
+ else
68
+ database.exec(`SAVEPOINT ${name}`);
33
69
  connection.depth += 1;
34
70
  try {
35
71
  const result = body();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.484.0",
3
+ "version": "0.485.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",