@oh-my-pi/pi-utils 17.3.7 → 17.4.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
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.3.8] - 2026-08-19
6
+
7
+ ### Added
8
+
9
+ - Exported `BINARY_SNIFF_BYTES`, the header window `isProbablyBinary` sniffs, so a caller holding the whole file in memory can classify the identical prefix through `isProbablyBinaryHeader` instead of reopening the file.
10
+
5
11
  ## [17.3.5] - 2026-08-16
6
12
 
7
13
  ### Fixed
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Header window sniffed for the binary heuristic; mirrors git's 8000-byte scan.
3
+ * Exported so callers that already hold the whole file in memory can sniff the
4
+ * identical prefix through {@link isProbablyBinaryHeader} instead of reopening.
5
+ */
6
+ export declare const BINARY_SNIFF_BYTES = 8192;
1
7
  /**
2
8
  * Classify an in-memory byte header as binary (non-UTF-8-text).
3
9
  *
@@ -181,6 +181,8 @@ export declare function getGpuCachePath(): string;
181
181
  * cache file without touching the rest of the config root.
182
182
  */
183
183
  export declare function getGithubCacheDbPath(): string;
184
+ /** Get the legacy Pi extension parse cache database path. */
185
+ export declare function getLegacyPiExtensionCacheDbPath(): string;
184
186
  /**
185
187
  * Get the encrypted auth-broker snapshot cache path (~/.omp/cache/auth-broker-snapshot.enc).
186
188
  * Honors the `OMP_AUTH_BROKER_SNAPSHOT_CACHE` env var when set so tests and
@@ -243,8 +245,12 @@ export declare function getCrashLogPath(agentDir?: string): string;
243
245
  export declare function getDebugLogPath(agentDir?: string): string;
244
246
  /** Get the secret placeholder key path (~/.omp/agent/secret-placeholder.key; XDG default: $XDG_STATE_HOME/omp/secret-placeholder.key). Adopts a legacy key on first XDG resolution. */
245
247
  export declare function getSecretPlaceholderKeyPath(): string;
248
+ /** Root directory containing every per-project daemon runtime scope (~/.omp/run/daemons; XDG default: $XDG_STATE_HOME/omp/run/daemons). */
249
+ export declare function getDaemonRuntimeRoot(): string;
246
250
  /** Get the daemon runtime directory for a project (~/.omp/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/omp/run/daemons/<hash>). */
247
251
  export declare function getDaemonRuntimeDir(projectDir: string): string;
252
+ /** Root directory containing every machine-global daemon service scope. */
253
+ export declare function getGlobalDaemonRuntimeRoot(): string;
248
254
  /** Get a profile-independent runtime directory for a machine-global daemon service. */
249
255
  export declare function getGlobalDaemonRuntimeDir(service: string): string;
250
256
  /** Get the provider in-flight root directory (~/.omp/run/provider-inflight; XDG default: $XDG_STATE_HOME/omp/run/provider-inflight). */
@@ -28,6 +28,7 @@ export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree.j
28
28
  export * from "./runtime-install.js";
29
29
  export * from "./sanitize-text.js";
30
30
  export * from "./snowflake.js";
31
+ export * from "./sqlite.js";
31
32
  export * from "./stderr-guard.js";
32
33
  export * from "./stream.js";
33
34
  export * from "./tab-spacing.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared classifiers for `bun:sqlite` error result codes.
3
+ *
4
+ * Every omp SQLite store (`agent.db` credential/usage store, `models.db` model
5
+ * cache, `history.db`) needs the same two distinctions: a transient BUSY that
6
+ * clears by retrying, and an unrecoverable corruption that never does. Keeping
7
+ * one implementation here prevents the classifiers from drifting between the
8
+ * credential store and the model cache.
9
+ */
10
+ /**
11
+ * SQLite's busy result-code family — base `SQLITE_BUSY` plus the extended
12
+ * variants `SQLITE_BUSY_RECOVERY` (concurrent WAL recovery), `SQLITE_BUSY_SNAPSHOT`,
13
+ * and `SQLITE_BUSY_TIMEOUT`. All warrant the same backoff-and-retry treatment.
14
+ */
15
+ export declare function isSqliteBusyError(err: unknown): boolean;
16
+ /**
17
+ * SQLite's unrecoverable-corruption result codes — the `SQLITE_CORRUPT` family
18
+ * (base plus extended variants like `SQLITE_CORRUPT_VTAB` / `SQLITE_CORRUPT_INDEX`)
19
+ * and `SQLITE_NOTADB` (the file header is not a database). Unlike
20
+ * {@link isSqliteBusyError}, these never clear by retrying: the store must be
21
+ * repaired or replaced, so callers latch, quarantine, or recreate the file.
22
+ */
23
+ export declare function isSqliteCorruptionError(err: unknown): boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "17.3.7",
4
+ "version": "17.4.0",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-natives": "17.3.7"
34
+ "@oh-my-pi/pi-natives": "17.4.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/bun": "^1.3.14"
package/src/binary.ts CHANGED
@@ -14,8 +14,12 @@
14
14
  */
15
15
  import { peekFile, peekFileSync } from "./peek-file";
16
16
 
17
- /** Header window sniffed for the binary heuristic; mirrors git's 8000-byte scan. */
18
- const BINARY_SNIFF_BYTES = 8192;
17
+ /**
18
+ * Header window sniffed for the binary heuristic; mirrors git's 8000-byte scan.
19
+ * Exported so callers that already hold the whole file in memory can sniff the
20
+ * identical prefix through {@link isProbablyBinaryHeader} instead of reopening.
21
+ */
22
+ export const BINARY_SNIFF_BYTES = 8192;
19
23
 
20
24
  /**
21
25
  * Classify an in-memory byte header as binary (non-UTF-8-text).
package/src/dirs.ts CHANGED
@@ -678,6 +678,11 @@ export function getGithubCacheDbPath(): string {
678
678
  return dirs.rootSubdir(path.join("cache", "github-cache.db"), "cache");
679
679
  }
680
680
 
681
+ /** Get the legacy Pi extension parse cache database path. */
682
+ export function getLegacyPiExtensionCacheDbPath(): string {
683
+ return dirs.rootSubdir(path.join("cache", "legacy-pi-extension-cache.db"), "cache");
684
+ }
685
+
681
686
  /**
682
687
  * Get the encrypted auth-broker snapshot cache path (~/.omp/cache/auth-broker-snapshot.enc).
683
688
  * Honors the `OMP_AUTH_BROKER_SNAPSHOT_CACHE` env var when set so tests and
@@ -854,10 +859,20 @@ export function getSecretPlaceholderKeyPath(): string {
854
859
  return keyPath;
855
860
  }
856
861
 
862
+ /** Root directory containing every per-project daemon runtime scope (~/.omp/run/daemons; XDG default: $XDG_STATE_HOME/omp/run/daemons). */
863
+ export function getDaemonRuntimeRoot(): string {
864
+ return dirs.rootSubdir(path.join("run", "daemons"), "state");
865
+ }
866
+
857
867
  /** Get the daemon runtime directory for a project (~/.omp/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/omp/run/daemons/<hash>). */
858
868
  export function getDaemonRuntimeDir(projectDir: string): string {
859
869
  const key = Bun.hash.wyhash(path.resolve(projectDir)).toString(16).padStart(16, "0");
860
- return dirs.rootSubdir(path.join("run", "daemons", key), "state");
870
+ return path.join(getDaemonRuntimeRoot(), key);
871
+ }
872
+
873
+ /** Root directory containing every machine-global daemon service scope. */
874
+ export function getGlobalDaemonRuntimeRoot(): string {
875
+ return path.join(getBaseConfigRoot(), "run", "daemons", "global");
861
876
  }
862
877
 
863
878
  /** Get a profile-independent runtime directory for a machine-global daemon service. */
@@ -865,7 +880,7 @@ export function getGlobalDaemonRuntimeDir(service: string): string {
865
880
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(service)) {
866
881
  throw new Error(`Invalid global daemon service name: ${JSON.stringify(service)}`);
867
882
  }
868
- return path.join(getBaseConfigRoot(), "run", "daemons", "global", service);
883
+ return path.join(getGlobalDaemonRuntimeRoot(), service);
869
884
  }
870
885
 
871
886
  /** Get the provider in-flight root directory (~/.omp/run/provider-inflight; XDG default: $XDG_STATE_HOME/omp/run/provider-inflight). */
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree";
28
28
  export * from "./runtime-install";
29
29
  export * from "./sanitize-text";
30
30
  export * from "./snowflake";
31
+ export * from "./sqlite";
31
32
  export * from "./stderr-guard";
32
33
  export * from "./stream";
33
34
  export * from "./tab-spacing";
package/src/sqlite.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared classifiers for `bun:sqlite` error result codes.
3
+ *
4
+ * Every omp SQLite store (`agent.db` credential/usage store, `models.db` model
5
+ * cache, `history.db`) needs the same two distinctions: a transient BUSY that
6
+ * clears by retrying, and an unrecoverable corruption that never does. Keeping
7
+ * one implementation here prevents the classifiers from drifting between the
8
+ * credential store and the model cache.
9
+ */
10
+
11
+ /**
12
+ * SQLite's busy result-code family — base `SQLITE_BUSY` plus the extended
13
+ * variants `SQLITE_BUSY_RECOVERY` (concurrent WAL recovery), `SQLITE_BUSY_SNAPSHOT`,
14
+ * and `SQLITE_BUSY_TIMEOUT`. All warrant the same backoff-and-retry treatment.
15
+ */
16
+ export function isSqliteBusyError(err: unknown): boolean {
17
+ if (err === null || typeof err !== "object" || !("code" in err)) return false;
18
+ const code = err.code;
19
+ return typeof code === "string" && code.startsWith("SQLITE_BUSY");
20
+ }
21
+
22
+ /**
23
+ * SQLite's unrecoverable-corruption result codes — the `SQLITE_CORRUPT` family
24
+ * (base plus extended variants like `SQLITE_CORRUPT_VTAB` / `SQLITE_CORRUPT_INDEX`)
25
+ * and `SQLITE_NOTADB` (the file header is not a database). Unlike
26
+ * {@link isSqliteBusyError}, these never clear by retrying: the store must be
27
+ * repaired or replaced, so callers latch, quarantine, or recreate the file.
28
+ */
29
+ export function isSqliteCorruptionError(err: unknown): boolean {
30
+ if (err === null || typeof err !== "object" || !("code" in err)) return false;
31
+ const code = err.code;
32
+ return typeof code === "string" && (code.startsWith("SQLITE_CORRUPT") || code === "SQLITE_NOTADB");
33
+ }