@vzn/vx 0.0.196 → 0.0.197

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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/src/cache/cache.ts +45 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.196",
3
+ "version": "0.0.197",
4
4
  "description": "A fast, correct task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,10 +17,10 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "optionalDependencies": {
20
- "@vzn/vx-linux-x64": "0.0.196",
21
- "@vzn/vx-linux-arm64": "0.0.196",
22
- "@vzn/vx-darwin-x64": "0.0.196",
23
- "@vzn/vx-darwin-arm64": "0.0.196"
20
+ "@vzn/vx-linux-x64": "0.0.197",
21
+ "@vzn/vx-linux-arm64": "0.0.197",
22
+ "@vzn/vx-darwin-x64": "0.0.197",
23
+ "@vzn/vx-darwin-arm64": "0.0.197"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -26,7 +26,16 @@
26
26
  // layer speaks is `CacheLayer` in layer.ts; `plugin-host.ts` enforces it.
27
27
 
28
28
  import { Database, type SQLQueryBindings } from 'bun:sqlite'
29
- import { accessSync, constants, existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs'
29
+ import {
30
+ accessSync,
31
+ closeSync,
32
+ constants,
33
+ existsSync,
34
+ mkdirSync,
35
+ openSync,
36
+ renameSync,
37
+ writeFileSync,
38
+ } from 'node:fs'
30
39
  import { readdir, rm, stat, unlink, writeFile } from 'node:fs/promises'
31
40
  import path from 'node:path'
32
41
  import { createTables } from './schema.js'
@@ -129,6 +138,26 @@ function unreadableIndexError(dbFile: string, err: unknown): UserError {
129
138
  )
130
139
  }
131
140
 
141
+ /**
142
+ * SQLite reports a descriptor it could not get as `SQLITE_CANTOPEN`, never
143
+ * `EMFILE`, so an open under a low `ulimit -n` reached the user as a bare
144
+ * `vx: SQLiteError: unable to open database file` (D's lead, A-56). One
145
+ * open of a file that exists says which it was.
146
+ */
147
+ function outOfFdsAtOpen(dbFile: string, err: unknown): UserError | undefined {
148
+ const code = (err as { code?: unknown } | null)?.code
149
+ if (typeof code !== 'string' || !code.startsWith('SQLITE_CANTOPEN')) return undefined
150
+ try {
151
+ closeSync(openSync(process.execPath, 'r'))
152
+ return undefined
153
+ } catch (probe) {
154
+ if (!isOutOfFds(probe)) return undefined
155
+ return new UserError(
156
+ `the cache index ${dbFile} could not be opened (${(err as Error).message}) — ${OUT_OF_FDS_HINT}`,
157
+ )
158
+ }
159
+ }
160
+
132
161
  /** Say once, on the channel the opener has, that an upgrade emptied the index. */
133
162
  export function noteSchemaReset(cache: Cache, warn: (message: string) => void): void {
134
163
  if (cache.formatChange !== null) {
@@ -527,7 +556,11 @@ export class Cache implements CacheLayer {
527
556
  const absent = mode === 'inspect' && !existsSync(dbFile)
528
557
  this.writeBlocked = absent ? 'no index there yet' : openCacheDir(cacheDir)
529
558
  this.write = localPolicy.write && this.writeBlocked === null
530
- this.db = new Database(absent ? ':memory:' : dbFile, { create: true })
559
+ try {
560
+ this.db = new Database(absent ? ':memory:' : dbFile, { create: true })
561
+ } catch (err) {
562
+ throw outOfFdsAtOpen(dbFile, err) ?? err
563
+ }
531
564
  // busy_timeout makes concurrent writers wait for the lock instead of
532
565
  // failing immediately with SQLITE_BUSY. Two parallel `vx run`
533
566
  // invocations in CI is a normal pattern; without this the second one
@@ -544,7 +577,16 @@ export class Cache implements CacheLayer {
544
577
  try {
545
578
  return read()
546
579
  } catch (err) {
547
- if (!unreadableIndex(err)) throw err
580
+ if (!unreadableIndex(err)) {
581
+ const fds = outOfFdsAtOpen(dbFile, err)
582
+ if (fds === undefined) throw err
583
+ try {
584
+ closeDb(this.db)
585
+ } catch {
586
+ // The refusal is the error worth reporting.
587
+ }
588
+ throw fds
589
+ }
548
590
  try {
549
591
  closeDb(this.db)
550
592
  } catch {