untappd-mcp 2.5.1 → 2.5.2

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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "MCP server for Untappd — beers, breweries, venues, check-ins, wishlists, and your friend feed",
10
- "version": "2.5.1"
10
+ "version": "2.5.2"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "Untappd",
16
16
  "source": "./",
17
17
  "description": "MCP server for Untappd — search beers/breweries/venues, read profiles/check-ins/wishlists, and post check-ins, toasts, and comments",
18
- "version": "2.5.1",
18
+ "version": "2.5.2",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "untappd-mcp",
3
3
  "displayName": "Untappd",
4
- "version": "2.5.1",
4
+ "version": "2.5.2",
5
5
  "description": "MCP server for Untappd — search beers/breweries/venues, read check-ins and wishlists, and post check-ins, toasts, and comments",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/bundle.js CHANGED
@@ -59906,7 +59906,7 @@ function toolAnnotations(opts = {}) {
59906
59906
  }
59907
59907
 
59908
59908
  // src/version.ts
59909
- var VERSION = "2.5.1";
59909
+ var VERSION = "2.5.2";
59910
59910
 
59911
59911
  // src/client.ts
59912
59912
  import { dirname as dirname2, join as join4 } from "path";
@@ -66702,7 +66702,7 @@ var NodeSqlDriver = class {
66702
66702
  return this.prepared(sql).all(...params);
66703
66703
  }
66704
66704
  transaction(fn) {
66705
- this.db.exec("BEGIN IMMEDIATE");
66705
+ retryBusy(() => this.db.exec("BEGIN IMMEDIATE"));
66706
66706
  try {
66707
66707
  fn();
66708
66708
  this.db.exec("COMMIT");
@@ -66728,19 +66728,25 @@ var CheckinCache = class _CheckinCache extends LocalCacheStore {
66728
66728
  static open(path) {
66729
66729
  if (path !== ":memory:") preparePrivateFile(path);
66730
66730
  const db = new DatabaseSync(path);
66731
- db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
66732
- if (path !== ":memory:") {
66733
- db.exec("PRAGMA journal_mode = WAL");
66734
- db.exec("PRAGMA synchronous = NORMAL");
66731
+ try {
66732
+ db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
66733
+ if (path !== ":memory:") {
66734
+ retryBusy(() => {
66735
+ if (journalModeOf(db) !== "wal") db.exec("PRAGMA journal_mode = WAL");
66736
+ });
66737
+ db.exec("PRAGMA synchronous = NORMAL");
66738
+ }
66739
+ const cache = new _CheckinCache(db, path);
66740
+ if (path !== ":memory:") tightenSidecars(path);
66741
+ return cache;
66742
+ } catch (e) {
66743
+ db.close();
66744
+ throw e;
66735
66745
  }
66736
- const cache = new _CheckinCache(db, path);
66737
- if (path !== ":memory:") tightenSidecars(path);
66738
- return cache;
66739
66746
  }
66740
66747
  /** The SQLite journal mode actually in effect (`wal` for a file, `memory` for :memory:). */
66741
66748
  journalMode() {
66742
- const row = this.db.prepare("PRAGMA journal_mode").get();
66743
- return String(row?.journal_mode ?? "unknown");
66749
+ return journalModeOf(this.db);
66744
66750
  }
66745
66751
  /** On-disk footprint: the db file plus its -wal/-shm side files. */
66746
66752
  fileInfo() {
@@ -66779,6 +66785,28 @@ function bestEffortChmod(path, mode) {
66779
66785
  }
66780
66786
  }
66781
66787
  var BUSY_TIMEOUT_MS = 5e3;
66788
+ function journalModeOf(db) {
66789
+ const row = db.prepare("PRAGMA journal_mode").get();
66790
+ return String(row?.journal_mode ?? "unknown");
66791
+ }
66792
+ function isBusy(e) {
66793
+ const code2 = e?.errcode;
66794
+ return typeof code2 === "number" && ((code2 & 255) === 5 || (code2 & 255) === 6);
66795
+ }
66796
+ var sleepCell = new Int32Array(new SharedArrayBuffer(4));
66797
+ function retryBusy(fn, timeoutMs = BUSY_TIMEOUT_MS) {
66798
+ const deadline = Date.now() + timeoutMs;
66799
+ for (let attempt = 0; ; attempt++) {
66800
+ try {
66801
+ return fn();
66802
+ } catch (e) {
66803
+ const left = deadline - Date.now();
66804
+ if (!isBusy(e) || left <= 0) throw e;
66805
+ const wait = Math.min(100, 5 * 2 ** Math.min(attempt, 5)) + Math.random() * 5;
66806
+ Atomics.wait(sleepCell, 0, 0, Math.min(wait, left));
66807
+ }
66808
+ }
66809
+ }
66782
66810
  function defaultCachePath() {
66783
66811
  const file2 = readEnvVar("UNTAPPD_CACHE_DB");
66784
66812
  if (file2) return file2 === ":memory:" ? file2 : absolutise(file2);
package/dist/cache/db.js CHANGED
@@ -47,7 +47,9 @@ class NodeSqlDriver {
47
47
  // IMMEDIATE takes the write lock up front. A DEFERRED transaction that
48
48
  // reads and then writes can hit SQLITE_BUSY on the lock upgrade, which
49
49
  // busy_timeout does NOT retry — the failure mode of two hosts open at once.
50
- this.db.exec('BEGIN IMMEDIATE');
50
+ // BEGIN itself can still get a handler-less SQLITE_BUSY (e.g. while another
51
+ // process recovers the WAL), so it is retried too.
52
+ retryBusy(() => this.db.exec('BEGIN IMMEDIATE'));
51
53
  try {
52
54
  fn();
53
55
  this.db.exec('COMMIT');
@@ -78,23 +80,36 @@ export class CheckinCache extends LocalCacheStore {
78
80
  if (path !== ':memory:')
79
81
  preparePrivateFile(path);
80
82
  const db = new DatabaseSync(path);
81
- // Set before the schema/migration transaction so even that waits out a
82
- // second process instead of failing. WAL lets readers and one writer run
83
- // concurrently (two hosts open at once); NORMAL sync is the WAL-safe default.
84
- db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
85
- if (path !== ':memory:') {
86
- db.exec('PRAGMA journal_mode = WAL');
87
- db.exec('PRAGMA synchronous = NORMAL');
83
+ try {
84
+ // Set before the schema/migration transaction so even that waits out a
85
+ // second process instead of failing. WAL lets readers and one writer run
86
+ // concurrently (two hosts open at once); NORMAL sync is the WAL-safe default.
87
+ db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
88
+ if (path !== ':memory:') {
89
+ // Switching a fresh file to WAL needs an exclusive lock, and when another
90
+ // process is opening it too SQLite answers SQLITE_BUSY WITHOUT calling
91
+ // the busy handler (#256) — so busy_timeout never applies and this has
92
+ // to retry itself. Once the file is WAL (the switch is persistent) a
93
+ // later open skips the switch entirely.
94
+ retryBusy(() => {
95
+ if (journalModeOf(db) !== 'wal')
96
+ db.exec('PRAGMA journal_mode = WAL');
97
+ });
98
+ db.exec('PRAGMA synchronous = NORMAL');
99
+ }
100
+ const cache = new CheckinCache(db, path);
101
+ if (path !== ':memory:')
102
+ tightenSidecars(path);
103
+ return cache;
104
+ }
105
+ catch (e) {
106
+ db.close();
107
+ throw e;
88
108
  }
89
- const cache = new CheckinCache(db, path);
90
- if (path !== ':memory:')
91
- tightenSidecars(path);
92
- return cache;
93
109
  }
94
110
  /** The SQLite journal mode actually in effect (`wal` for a file, `memory` for :memory:). */
95
111
  journalMode() {
96
- const row = this.db.prepare('PRAGMA journal_mode').get();
97
- return String(row?.journal_mode ?? 'unknown');
112
+ return journalModeOf(this.db);
98
113
  }
99
114
  /** On-disk footprint: the db file plus its -wal/-shm side files. */
100
115
  fileInfo() {
@@ -152,6 +167,39 @@ function bestEffortChmod(path, mode) {
152
167
  }
153
168
  // How long a write waits for another process's lock before failing.
154
169
  const BUSY_TIMEOUT_MS = 5000;
170
+ function journalModeOf(db) {
171
+ const row = db.prepare('PRAGMA journal_mode').get();
172
+ return String(row?.journal_mode ?? 'unknown');
173
+ }
174
+ /** SQLITE_BUSY / SQLITE_LOCKED, including their extended codes (e.g. BUSY_RECOVERY). */
175
+ function isBusy(e) {
176
+ const code = e?.errcode;
177
+ return typeof code === 'number' && ((code & 0xff) === 5 || (code & 0xff) === 6);
178
+ }
179
+ const sleepCell = new Int32Array(new SharedArrayBuffer(4));
180
+ /**
181
+ * Run `fn`, retrying a SQLITE_BUSY that SQLite raised without consulting the
182
+ * busy handler, until {@link BUSY_TIMEOUT_MS} after the FIRST attempt. A BUSY
183
+ * that already waited out busy_timeout lands past that deadline and is thrown
184
+ * as-is, so the total wait never exceeds busy_timeout's. The driver is
185
+ * synchronous, so the backoff blocks (Atomics.wait) like busy_timeout does.
186
+ */
187
+ export function retryBusy(fn, timeoutMs = BUSY_TIMEOUT_MS) {
188
+ const deadline = Date.now() + timeoutMs;
189
+ for (let attempt = 0;; attempt++) {
190
+ try {
191
+ return fn();
192
+ }
193
+ catch (e) {
194
+ const left = deadline - Date.now();
195
+ if (!isBusy(e) || left <= 0)
196
+ throw e;
197
+ // 5, 10, 20 … ms, capped at 100, plus jitter so colliding opens spread out.
198
+ const wait = Math.min(100, 5 * 2 ** Math.min(attempt, 5)) + Math.random() * 5;
199
+ Atomics.wait(sleepCell, 0, 0, Math.min(wait, left));
200
+ }
201
+ }
202
+ }
155
203
  /**
156
204
  * The on-disk cache path, always ABSOLUTE: `$UNTAPPD_CACHE_DB` (a file), else
157
205
  * `$UNTAPPD_CACHE_DIR/checkins.db`, else `~/.untappd-mcp/checkins.db`.
package/dist/version.js CHANGED
@@ -3,4 +3,4 @@
3
3
  // json's `extra-files`), and `versionSyncTest` guards that it stays equal to
4
4
  // package.json. Import VERSION wherever the version is needed rather than
5
5
  // re-declaring it.
6
- export const VERSION = '2.5.1'; // x-release-please-version
6
+ export const VERSION = '2.5.2'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "untappd-mcp",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
4
4
  "mcpName": "io.github.chrischall/untappd-mcp",
5
5
  "description": "Untappd MCP server for Claude — developed and maintained by AI (Claude Code)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/untappd-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.5.1",
9
+ "version": "2.5.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "untappd-mcp",
14
- "version": "2.5.1",
14
+ "version": "2.5.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },