@vzn/vx 0.0.385 → 0.0.387
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/README.md +2 -0
- package/package.json +5 -5
- package/src/cache/cache.ts +8 -27
- package/src/cache/output-index.ts +11 -16
- package/src/cache/schema.ts +13 -0
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
vx N% faster: vx takes N% less time than that tool (1 − vx ÷ theirs); N% slower: N% more (vx ÷ theirs − 1).
|
|
29
29
|
|
|
30
|
+
Nx's column ran every task through npm run (~200 ms of CPU each; Turbo ran bun run), a harness fault since fixed; with bun, on a 4-core Linux box, Nx took 6m 59s cold and 4.50 s fully cached (vx 47% and 92% faster).
|
|
31
|
+
|
|
30
32
|
Time added is the wall time over the tasks' own ideal schedule (3 min 38 s).
|
|
31
33
|
Same graph, commands and concurrency: [how it is measured](https://vznjs.github.io/vx/benchmarks/).
|
|
32
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.387",
|
|
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.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.387",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.387",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.387",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.387"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/cache.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// this handle, declared by a plugin's `cache` hook. The contract every
|
|
26
26
|
// layer speaks is `CacheLayer` in layer.ts; `plugin-host.ts` enforces it.
|
|
27
27
|
|
|
28
|
-
import { Database
|
|
28
|
+
import { Database } from 'bun:sqlite'
|
|
29
29
|
import {
|
|
30
30
|
accessSync,
|
|
31
31
|
closeSync,
|
|
@@ -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, lazyStatement } from './schema.js'
|
|
41
|
+
import { createTables, inHashes, lazyStatement } from './schema.js'
|
|
42
42
|
import {
|
|
43
43
|
UserError,
|
|
44
44
|
formatBytes,
|
|
@@ -973,16 +973,8 @@ export class Cache implements CacheLayer {
|
|
|
973
973
|
private async getManyEntries(hashes: readonly string[]): Promise<Map<string, CacheEntry>> {
|
|
974
974
|
const out = new Map<string, CacheEntry>()
|
|
975
975
|
if (!this.read || hashes.length === 0) return out
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
const chunk = hashes.slice(i, i + 900)
|
|
979
|
-
const placeholders = chunk.map(() => '?').join(',')
|
|
980
|
-
rows.push(
|
|
981
|
-
...(this.db
|
|
982
|
-
.query(`${SELECT_ENTRY} WHERE e.hash IN (${placeholders})`)
|
|
983
|
-
.all(...(chunk as readonly SQLQueryBindings[])) as EntryRow[]),
|
|
984
|
-
)
|
|
985
|
-
}
|
|
976
|
+
const { test, params } = inHashes(hashes)
|
|
977
|
+
const rows = this.db.query(`${SELECT_ENTRY} WHERE e.hash ${test}`).all(...params) as EntryRow[]
|
|
986
978
|
if (rows.length === 0) return out
|
|
987
979
|
const present = rows.map((r) => existsSync(this.tarPath(r.hash)))
|
|
988
980
|
const live = rows.filter((_r, i) => present[i])
|
|
@@ -1696,15 +1688,8 @@ export class Cache implements CacheLayer {
|
|
|
1696
1688
|
}
|
|
1697
1689
|
|
|
1698
1690
|
private writeAccessed(hashes: readonly string[], now: number): void {
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
for (let i = 0; i < hashes.length; i += 900) {
|
|
1702
|
-
const chunk = hashes.slice(i, i + 900)
|
|
1703
|
-
const placeholders = chunk.map(() => '?').join(',')
|
|
1704
|
-
this.db
|
|
1705
|
-
.prepare(`UPDATE entries SET accessed_at = ? WHERE hash IN (${placeholders})`)
|
|
1706
|
-
.run(now, ...chunk)
|
|
1707
|
-
}
|
|
1691
|
+
const { test, params } = inHashes(hashes)
|
|
1692
|
+
this.db.prepare(`UPDATE entries SET accessed_at = ? WHERE hash ${test}`).run(now, ...params)
|
|
1708
1693
|
}
|
|
1709
1694
|
|
|
1710
1695
|
/**
|
|
@@ -1900,13 +1885,9 @@ export class Cache implements CacheLayer {
|
|
|
1900
1885
|
// space the unlinks free. Rows-first failed there before any file went,
|
|
1901
1886
|
// and the one verb meant to free a full disk freed nothing (A-14).
|
|
1902
1887
|
await Promise.all(hashes.map((h) => rm(this.tarPath(h), { force: true })))
|
|
1888
|
+
const { test, params } = inHashes(rows)
|
|
1903
1889
|
const deleteRows = this.db.transaction(() => {
|
|
1904
|
-
|
|
1905
|
-
const chunk = rows.slice(i, i + 900)
|
|
1906
|
-
this.db
|
|
1907
|
-
.prepare(`DELETE FROM entries WHERE hash IN (${chunk.map(() => '?').join(',')})`)
|
|
1908
|
-
.run(...(chunk as readonly SQLQueryBindings[]))
|
|
1909
|
-
}
|
|
1890
|
+
this.db.prepare(`DELETE FROM entries WHERE hash ${test}`).run(...params)
|
|
1910
1891
|
})
|
|
1911
1892
|
try {
|
|
1912
1893
|
deleteRows()
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
/** `mtime_ms` of a recorded output prefix that did not exist when the snapshot was taken. */
|
|
9
9
|
const ABSENT_DIR_MTIME = -1
|
|
10
10
|
|
|
11
|
-
import type { Database
|
|
12
|
-
import { lazyStatement } from './schema.js'
|
|
11
|
+
import type { Database } from 'bun:sqlite'
|
|
12
|
+
import { inHashes, lazyStatement } from './schema.js'
|
|
13
13
|
import { lstatSync, statSync } from 'node:fs'
|
|
14
14
|
import { lstat, readdir } from 'node:fs/promises'
|
|
15
15
|
import path from 'node:path'
|
|
@@ -84,21 +84,18 @@ export class OutputIndex {
|
|
|
84
84
|
loadOutputFilesBatch(hashes: readonly string[]): Map<string, OutputFileRow[]> {
|
|
85
85
|
const out = new Map<string, OutputFileRow[]>()
|
|
86
86
|
if (hashes.length === 0) return out
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// compiled statement keyed by the SQL text — so the dominant single-hash
|
|
91
|
-
// warm-hit path (called up to 3× per hit) reuses one statement instead of
|
|
92
|
-
// recompiling on every call.
|
|
87
|
+
// `db.query` (not `db.prepare`) caches the compiled statement keyed by
|
|
88
|
+
// the SQL text, which `inHashes` keeps to two: the single-hash warm-hit
|
|
89
|
+
// path (called up to 3× per hit) reuses one instead of recompiling.
|
|
93
90
|
// A reader in the same process (`vx watch`'s next cycle, a test) sees
|
|
94
91
|
// the stamps taken, not only the ones flushed: overlaid below from
|
|
95
92
|
// memory. A flush here committed a transaction per read, and a restore
|
|
96
93
|
// reads its rows twice.
|
|
97
|
-
const
|
|
94
|
+
const { test, params } = inHashes(hashes)
|
|
98
95
|
const stmt = this.db.query(
|
|
99
|
-
`SELECT entry_hash, path, size_bytes, mode, mtime_ms, ino, ctime_ms FROM output_files WHERE entry_hash
|
|
96
|
+
`SELECT entry_hash, path, size_bytes, mode, mtime_ms, ino, ctime_ms FROM output_files WHERE entry_hash ${test}`,
|
|
100
97
|
)
|
|
101
|
-
const rows = stmt.all(...
|
|
98
|
+
const rows = stmt.all(...params) as Array<{
|
|
102
99
|
entry_hash: string
|
|
103
100
|
path: string
|
|
104
101
|
size_bytes: number
|
|
@@ -324,12 +321,10 @@ export class OutputIndex {
|
|
|
324
321
|
// A reader in the same process (`vx watch`'s next cycle, a test) sees
|
|
325
322
|
// what was snapshotted, not what was flushed.
|
|
326
323
|
this.flushOutputDirs()
|
|
327
|
-
const
|
|
324
|
+
const { test, params } = inHashes(hashes)
|
|
328
325
|
const rows = this.db
|
|
329
|
-
.query(
|
|
330
|
-
|
|
331
|
-
)
|
|
332
|
-
.all(...(hashes as readonly SQLQueryBindings[])) as Array<{
|
|
326
|
+
.query(`SELECT entry_hash, path, mtime_ms FROM output_dirs WHERE entry_hash ${test}`)
|
|
327
|
+
.all(...params) as Array<{
|
|
333
328
|
entry_hash: string
|
|
334
329
|
path: string
|
|
335
330
|
mtime_ms: number
|
package/src/cache/schema.ts
CHANGED
|
@@ -242,3 +242,16 @@ export function createTables(db: Database): void {
|
|
|
242
242
|
);
|
|
243
243
|
`)
|
|
244
244
|
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* A `WHERE <column> …` test against many hashes, as one statement whatever
|
|
248
|
+
* their number. A list of `?` is a new statement per length, compiled each
|
|
249
|
+
* run: 2.0 ms against 1.4 for 900 hashes through `json_each` (2026-10-03).
|
|
250
|
+
* One hash keeps `= ?`, ~3 µs cheaper than parsing a one-element array, on
|
|
251
|
+
* the per-hit path that asks for one at a time.
|
|
252
|
+
*/
|
|
253
|
+
export function inHashes(hashes: readonly string[]): { test: string; params: string[] } {
|
|
254
|
+
return hashes.length === 1
|
|
255
|
+
? { test: '= ?', params: [hashes[0]!] }
|
|
256
|
+
: { test: 'IN (SELECT value FROM json_each(?))', params: [JSON.stringify(hashes)] }
|
|
257
|
+
}
|