@gmickel/gno 1.10.0 → 1.10.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "Local semantic search for your documents. Index Markdown, PDF, and Office files with hybrid BM25 + vector search.",
5
5
  "keywords": [
6
6
  "embeddings",
@@ -4,8 +4,8 @@
4
4
  * @module src/core/file-lock
5
5
  */
6
6
 
7
- // node:fs/promises for mkdir (no Bun equivalent for recursive dir creation)
8
- import { mkdir } from "node:fs/promises";
7
+ // node:fs/promises for mkdir/rm (no Bun equivalent for filesystem structure ops)
8
+ import { mkdir, rm } from "node:fs/promises";
9
9
  // node:path for dirname (no Bun path utils)
10
10
  import { dirname } from "node:path";
11
11
 
@@ -13,6 +13,8 @@ import { MCP_ERRORS } from "./errors";
13
13
  const DEFAULT_TIMEOUT_MS = 5000;
14
14
  const HOLD_SECONDS = 60 * 60 * 24 * 365;
15
15
  const READY_TOKEN = "READY";
16
+ const DIRECTORY_LOCK_SUFFIX = ".dir";
17
+ const DIRECTORY_LOCK_POLL_MS = 50;
16
18
 
17
19
  export interface WriteLockHandle {
18
20
  release: () => Promise<void>;
@@ -65,6 +67,10 @@ function buildHoldCommand(): string {
65
67
  return `printf '${READY_TOKEN}\\n'; exec sleep ${HOLD_SECONDS}`;
66
68
  }
67
69
 
70
+ function delay(ms: number): Promise<void> {
71
+ return new Promise((resolve) => setTimeout(resolve, ms));
72
+ }
73
+
68
74
  async function waitForReady(
69
75
  proc: ReturnType<typeof Bun.spawn>
70
76
  ): Promise<boolean> {
@@ -90,13 +96,42 @@ async function waitForReady(
90
96
  }
91
97
  }
92
98
 
99
+ async function acquireDirectoryLock(
100
+ lockPath: string,
101
+ timeoutMs: number
102
+ ): Promise<WriteLockHandle | null> {
103
+ const directoryLockPath = `${lockPath}${DIRECTORY_LOCK_SUFFIX}`;
104
+ await mkdir(dirname(directoryLockPath), { recursive: true });
105
+
106
+ const deadline = Date.now() + Math.max(0, timeoutMs);
107
+ while (true) {
108
+ try {
109
+ await mkdir(directoryLockPath);
110
+ return {
111
+ release: async () => {
112
+ await rm(directoryLockPath, { force: true, recursive: true });
113
+ },
114
+ };
115
+ } catch (error) {
116
+ const code = (error as { code?: string }).code;
117
+ if (code !== "EEXIST") {
118
+ throw error;
119
+ }
120
+ if (Date.now() >= deadline) {
121
+ return null;
122
+ }
123
+ await delay(Math.min(DIRECTORY_LOCK_POLL_MS, deadline - Date.now()));
124
+ }
125
+ }
126
+ }
127
+
93
128
  export async function acquireWriteLock(
94
129
  lockPath: string,
95
130
  timeoutMs: number = DEFAULT_TIMEOUT_MS
96
131
  ): Promise<WriteLockHandle | null> {
97
132
  const cmd = resolveLockCommand();
98
133
  if (!cmd) {
99
- throw new Error("No lockf/flock available for write locking");
134
+ return acquireDirectoryLock(lockPath, timeoutMs);
100
135
  }
101
136
 
102
137
  await mkdir(dirname(lockPath), { recursive: true });