@vzn/vx 0.0.379 → 0.0.381

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": "@vzn/vx",
3
- "version": "0.0.379",
3
+ "version": "0.0.381",
4
4
  "description": "The fastest task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,10 +21,10 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "optionalDependencies": {
24
- "@vzn/vx-linux-x64": "0.0.379",
25
- "@vzn/vx-linux-arm64": "0.0.379",
26
- "@vzn/vx-darwin-x64": "0.0.379",
27
- "@vzn/vx-darwin-arm64": "0.0.379"
24
+ "@vzn/vx-linux-x64": "0.0.381",
25
+ "@vzn/vx-linux-arm64": "0.0.381",
26
+ "@vzn/vx-darwin-x64": "0.0.381",
27
+ "@vzn/vx-darwin-arm64": "0.0.381"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -38,7 +38,7 @@ import {
38
38
  } from 'node:fs'
39
39
  import { readdir, rm, stat, unlink, writeFile } from 'node:fs/promises'
40
40
  import path from 'node:path'
41
- import { createTables } from './schema.js'
41
+ import { createTables, lazyStatement } from './schema.js'
42
42
  import {
43
43
  UserError,
44
44
  formatBytes,
@@ -451,7 +451,6 @@ export class Cache implements CacheLayer {
451
451
  private readonly upsertStdout: ReturnType<Database['prepare']>
452
452
  private readonly deleteStdout: ReturnType<Database['prepare']>
453
453
  private readonly entryExists: ReturnType<Database['prepare']>
454
- private readonly bumpAccessed: ReturnType<Database['prepare']>
455
454
  private readonly touched = new Set<string>()
456
455
  private readonly insertEntryInput: ReturnType<Database['prepare']>
457
456
  /** The per-file (mtime, size) → blob-OID memo behind `hashFile`. */
@@ -706,8 +705,10 @@ export class Cache implements CacheLayer {
706
705
 
707
706
  createTables(this.db)
708
707
 
709
- this.deleteEntryRow = this.db.prepare('DELETE FROM entries WHERE hash = ?')
710
- this.insertEntry = this.db.prepare(`
708
+ this.deleteEntryRow = lazyStatement(this.db, 'DELETE FROM entries WHERE hash = ?')
709
+ this.insertEntry = lazyStatement(
710
+ this.db,
711
+ `
711
712
  INSERT INTO entries(hash, project, task, command, exit_code, duration_ms, size_bytes, created_at, accessed_at, cpu_ms, peak_rss_bytes)
712
713
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
713
714
  ON CONFLICT(hash) DO UPDATE SET
@@ -720,25 +721,29 @@ export class Cache implements CacheLayer {
720
721
  accessed_at = excluded.accessed_at,
721
722
  cpu_ms = excluded.cpu_ms,
722
723
  peak_rss_bytes = excluded.peak_rss_bytes
723
- `)
724
- this.upsertStdout = this.db.prepare(
724
+ `,
725
+ )
726
+ this.upsertStdout = lazyStatement(
727
+ this.db,
725
728
  'INSERT INTO entry_stdout(hash, stdout) VALUES (?, ?) ON CONFLICT(hash) DO UPDATE SET stdout = excluded.stdout',
726
729
  )
727
- this.deleteStdout = this.db.prepare('DELETE FROM entry_stdout WHERE hash = ?')
728
- this.selectEntry = this.db.prepare(`${SELECT_ENTRY} WHERE e.hash = ?`)
730
+ this.deleteStdout = lazyStatement(this.db, 'DELETE FROM entry_stdout WHERE hash = ?')
731
+ this.selectEntry = lazyStatement(this.db, `${SELECT_ENTRY} WHERE e.hash = ?`)
729
732
  // `has` asks only whether the row is there, with no stdout joined. A
730
733
  // column off the index, so the table row is still read and a corrupt
731
734
  // table refuses here as it does on `get` (`SELECT 1` answers from the
732
735
  // index alone).
733
- this.entryExists = this.db.prepare('SELECT exit_code FROM entries WHERE hash = ?')
734
- this.bumpAccessed = this.db.prepare('UPDATE entries SET accessed_at = ? WHERE hash = ?')
736
+ this.entryExists = lazyStatement(this.db, 'SELECT exit_code FROM entries WHERE hash = ?')
735
737
  // INSERT OR IGNORE: re-saving the same hash (idempotent ingest /
736
738
  // overlapping concurrent saves) leaves the existing rows untouched —
737
739
  // identical inputs derive the identical hash, so the rows are too.
738
- this.insertEntryInput = this.db.prepare(`
740
+ this.insertEntryInput = lazyStatement(
741
+ this.db,
742
+ `
739
743
  INSERT OR IGNORE INTO entry_inputs(entry_hash, kind, name, hash)
740
744
  VALUES (?, ?, ?, ?)
741
- `)
745
+ `,
746
+ )
742
747
  // The slices: each owns its statements over this handle and its table(s);
743
748
  // the schema above is the one place every table is declared.
744
749
  this.files = new FileHashStore(this.db, cacheDir, this.write, repoDir)
@@ -4,6 +4,7 @@
4
4
  // Owns its statements over the store's handle; `Cache` delegates.
5
5
 
6
6
  import type { Database, SQLQueryBindings } from 'bun:sqlite'
7
+ import { lazyStatement } from './schema.js'
7
8
 
8
9
  export class ConfigEvalTable {
9
10
  private readonly selectConfigEval: ReturnType<Database['prepare']>
@@ -18,11 +19,13 @@ export class ConfigEvalTable {
18
19
  ) {
19
20
  this.read = policy.read
20
21
  this.write = policy.write
21
- this.selectConfigEval = this.db.prepare('SELECT json FROM config_evals WHERE key = ?')
22
- this.insertConfigEval = this.db.prepare(
22
+ this.selectConfigEval = lazyStatement(this.db, 'SELECT json FROM config_evals WHERE key = ?')
23
+ this.insertConfigEval = lazyStatement(
24
+ this.db,
23
25
  'INSERT OR REPLACE INTO config_evals(key, json, created_at) VALUES (?, ?, ?)',
24
26
  )
25
- this.upsertConfigClosure = this.db.prepare(
27
+ this.upsertConfigClosure = lazyStatement(
28
+ this.db,
26
29
  'INSERT INTO config_closures(config_path, files_json, created_at) VALUES (?, ?, ?) ON CONFLICT(config_path) DO UPDATE SET files_json = excluded.files_json, created_at = excluded.created_at',
27
30
  )
28
31
  }
@@ -7,6 +7,7 @@
7
7
  // the store's handle; `Cache` delegates.
8
8
 
9
9
  import type { Database } from 'bun:sqlite'
10
+ import { lazyStatement } from './schema.js'
10
11
  import { lstatSync, readlinkSync } from 'node:fs'
11
12
  import path from 'node:path'
12
13
  import { fileIdentity, repoFacts } from './git-inputs.js'
@@ -35,10 +36,13 @@ export class FileHashStore {
35
36
  */
36
37
  private readonly repoDir?: string,
37
38
  ) {
38
- this.selectFileHash = this.db.prepare(
39
+ this.selectFileHash = lazyStatement(
40
+ this.db,
39
41
  'SELECT mtime_ms, size_bytes, ctime_ms, ino, content_hash FROM file_hashes WHERE path = ?',
40
42
  )
41
- this.upsertFileHash = this.db.prepare(`
43
+ this.upsertFileHash = lazyStatement(
44
+ this.db,
45
+ `
42
46
  INSERT INTO file_hashes(path, mtime_ms, size_bytes, ctime_ms, ino, content_hash, seen_at)
43
47
  VALUES (?, ?, ?, ?, ?, ?, ?)
44
48
  ON CONFLICT(path) DO UPDATE SET
@@ -48,7 +52,8 @@ export class FileHashStore {
48
52
  ino = excluded.ino,
49
53
  content_hash = excluded.content_hash,
50
54
  seen_at = excluded.seen_at
51
- `)
55
+ `,
56
+ )
52
57
  }
53
58
 
54
59
  /**
@@ -9,6 +9,7 @@
9
9
  const ABSENT_DIR_MTIME = -1
10
10
 
11
11
  import type { Database, SQLQueryBindings } from 'bun:sqlite'
12
+ import { lazyStatement } from './schema.js'
12
13
  import { lstatSync, statSync } from 'node:fs'
13
14
  import { lstat, readdir } from 'node:fs/promises'
14
15
  import path from 'node:path'
@@ -42,21 +43,26 @@ export class OutputIndex {
42
43
  private readonly pendingStamps = new Map<string, Array<[string, number, number]>>()
43
44
 
44
45
  constructor(private readonly db: Database) {
45
- this.insertOutputFile = this.db.prepare(`
46
+ this.insertOutputFile = lazyStatement(
47
+ this.db,
48
+ `
46
49
  INSERT INTO output_files(entry_hash, path, size_bytes, mode, mtime_ms)
47
50
  VALUES (?, ?, ?, ?, ?)
48
51
  ON CONFLICT(entry_hash, path) DO UPDATE SET
49
52
  size_bytes = excluded.size_bytes,
50
53
  mode = excluded.mode,
51
54
  mtime_ms = excluded.mtime_ms
52
- `)
53
- this.deleteOutputFiles = this.db.prepare('DELETE FROM output_files WHERE entry_hash = ?')
54
- this.insertOutputDir = this.db.prepare(
55
+ `,
56
+ )
57
+ this.deleteOutputFiles = lazyStatement(this.db, 'DELETE FROM output_files WHERE entry_hash = ?')
58
+ this.insertOutputDir = lazyStatement(
59
+ this.db,
55
60
  'INSERT INTO output_dirs(entry_hash, path, mtime_ms) VALUES (?, ?, ?)',
56
61
  )
57
- this.deleteOutputDirs = this.db.prepare('DELETE FROM output_dirs WHERE entry_hash = ?')
58
- this.entryExists = this.db.prepare('SELECT 1 FROM entries WHERE hash = ?')
59
- this.stampOutputFile = this.db.prepare(
62
+ this.deleteOutputDirs = lazyStatement(this.db, 'DELETE FROM output_dirs WHERE entry_hash = ?')
63
+ this.entryExists = lazyStatement(this.db, 'SELECT 1 FROM entries WHERE hash = ?')
64
+ this.stampOutputFile = lazyStatement(
65
+ this.db,
60
66
  'UPDATE output_files SET ino = ?, ctime_ms = ? WHERE entry_hash = ? AND path = ?',
61
67
  )
62
68
  }
@@ -4,6 +4,7 @@
4
4
  // its statements over the store's handle; `Cache` delegates.
5
5
 
6
6
  import type { Database, SQLQueryBindings } from 'bun:sqlite'
7
+ import { lazyStatement } from './schema.js'
7
8
  import type { InvocationRecord, RunRecord } from './layer.js'
8
9
 
9
10
  export class RunHistory {
@@ -15,7 +16,9 @@ export class RunHistory {
15
16
  /** The store's salted value digest (L-4). */
16
17
  private readonly digestValue: (value: string) => string,
17
18
  ) {
18
- this.insertRun = this.db.prepare(`
19
+ this.insertRun = lazyStatement(
20
+ this.db,
21
+ `
19
22
  INSERT INTO runs(
20
23
  hash, project, task, status, exit_code, duration_ms, forward_args,
21
24
  started_at, ended_at,
@@ -24,8 +27,11 @@ export class RunHistory {
24
27
  blocked_by, timed_out, sandbox_violations, not_ready
25
28
  )
26
29
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
27
- `)
28
- this.insertInvocation = this.db.prepare(`
30
+ `,
31
+ )
32
+ this.insertInvocation = lazyStatement(
33
+ this.db,
34
+ `
29
35
  INSERT INTO invocations(
30
36
  run_id, command, requested_tasks, cache_policy, concurrency, flow,
31
37
  started_at, ended_at, total_duration_ms,
@@ -36,7 +42,8 @@ export class RunHistory {
36
42
  )
37
43
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
38
44
  ON CONFLICT(run_id) DO NOTHING
39
- `)
45
+ `,
46
+ )
40
47
  }
41
48
 
42
49
  recordRun(run: RunRecord): void {
@@ -3,7 +3,21 @@
3
3
  // them lives in the store that owns the table (file-hashes, config-evals,
4
4
  // output-index, history) or in cache.ts for `entries` and `entry_inputs`.
5
5
 
6
- import type { Database } from 'bun:sqlite'
6
+ import type { Database, SQLQueryBindings } from 'bun:sqlite'
7
+
8
+ /**
9
+ * A statement prepared on its first `run` or `get`. A warm run executes few
10
+ * of the index's statements, and preparing every one at the open cost each
11
+ * run ~0.4 ms (2026-10-03).
12
+ */
13
+ export function lazyStatement(db: Database, sql: string): ReturnType<Database['prepare']> {
14
+ let statement: ReturnType<Database['prepare']> | undefined
15
+ const prepared = (): ReturnType<Database['prepare']> => (statement ??= db.prepare(sql))
16
+ return {
17
+ run: (...params: SQLQueryBindings[]) => prepared().run(...params),
18
+ get: (...params: SQLQueryBindings[]) => prepared().get(...params),
19
+ } as unknown as ReturnType<Database['prepare']>
20
+ }
7
21
 
8
22
  export function createTables(db: Database): void {
9
23
  // Cached config evaluations (workspace/config-cache.ts): the validated