fix: replace better-sqlite3 with sql.js (WASM) to fix install on Node 25+ (#356)
* fix: normalize .ts import extensions to .js for Node 22.22+ compatibility Node 22.22.0's --experimental-strip-types handles .ts import specifiers differently, causing ERR_INVALID_TYPESCRIPT_SYNTAX in CI. The project convention uses .js specifiers with a custom resolve hook that rewrites them to .ts at test time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace better-sqlite3 with sql.js to eliminate native compilation failures better-sqlite3 requires prebuilt binaries or node-gyp compilation, which fails on newer Node versions (e.g. 25.x) that lack prebuilds. sql.js uses WASM-compiled SQLite with zero native dependencies, making installation reliable across all platforms and Node versions. Closes #355 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7b17138ea4
commit
06d1237ac5
4 changed files with 238 additions and 583 deletions
374
package-lock.json
generated
374
package-lock.json
generated
|
|
@ -2167,16 +2167,6 @@
|
|||
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/better-sqlite3": {
|
||||
"version": "7.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
|
||||
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/diff": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.2.tgz",
|
||||
|
|
@ -2184,6 +2174,13 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/emscripten": {
|
||||
"version": "1.41.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
|
||||
"integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/hosted-git-info": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hosted-git-info/-/hosted-git-info-3.0.5.tgz",
|
||||
|
|
@ -2229,6 +2226,17 @@
|
|||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/sql.js": {
|
||||
"version": "1.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/sql.js/-/sql.js-1.4.9.tgz",
|
||||
"integrity": "sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/emscripten": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||
|
|
@ -2343,17 +2351,6 @@
|
|||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "11.10.0",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
|
||||
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||
|
|
@ -2363,26 +2360,6 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bowser": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
|
||||
|
|
@ -2401,30 +2378,6 @@
|
|||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
|
|
@ -2452,12 +2405,6 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
|
|
@ -2484,30 +2431,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/degenerator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
|
||||
|
|
@ -2610,15 +2533,6 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
|
|
@ -2751,12 +2665,6 @@
|
|||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
|
|
@ -2769,12 +2677,6 @@
|
|||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
|
|
@ -2867,12 +2769,6 @@
|
|||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "13.0.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
||||
|
|
@ -2989,18 +2885,6 @@
|
|||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||
|
|
@ -3132,18 +3016,6 @@
|
|||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||
|
|
@ -3159,15 +3031,6 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
||||
|
|
@ -3177,24 +3040,12 @@
|
|||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/netmask": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
|
||||
|
|
@ -3204,18 +3055,6 @@
|
|||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.88.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.88.0.tgz",
|
||||
"integrity": "sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
|
|
@ -3428,33 +3267,6 @@
|
|||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^2.0.0",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/proper-lockfile": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
|
||||
|
|
@ -3543,35 +3355,6 @@
|
|||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
|
|
@ -3672,51 +3455,6 @@
|
|||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
|
|
@ -3771,21 +3509,18 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sql.js": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz",
|
||||
"integrity": "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||
|
|
@ -3801,15 +3536,6 @@
|
|||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz",
|
||||
|
|
@ -3838,34 +3564,6 @@
|
|||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/token-types": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz",
|
||||
|
|
@ -3896,18 +3594,6 @@
|
|||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
|
|
@ -3949,12 +3635,6 @@
|
|||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
|
|
@ -4086,7 +3766,6 @@
|
|||
"@gsd/pi-tui": "*",
|
||||
"@mariozechner/jiti": "^2.6.2",
|
||||
"@silvia-odwyer/photon-node": "^0.3.4",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"chalk": "^5.5.0",
|
||||
"diff": "^8.0.2",
|
||||
"extract-zip": "^2.0.1",
|
||||
|
|
@ -4097,15 +3776,16 @@
|
|||
"marked": "^15.0.12",
|
||||
"minimatch": "^10.2.3",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"sql.js": "^1.14.1",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"undici": "^7.19.1",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.12",
|
||||
"@types/diff": "^7.0.2",
|
||||
"@types/hosted-git-info": "^3.0.5",
|
||||
"@types/proper-lockfile": "^4.1.4"
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
"@types/sql.js": "^1.4.9"
|
||||
}
|
||||
},
|
||||
"packages/pi-tui": {
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@
|
|||
"proper-lockfile": "^4.1.2",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"undici": "^7.19.1",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"sql.js": "^1.14.1",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.12",
|
||||
"@types/sql.js": "^1.4.9",
|
||||
"@types/diff": "^7.0.2",
|
||||
"@types/hosted-git-info": "^3.0.5",
|
||||
"@types/proper-lockfile": "^4.1.4"
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ function getDbPath(): string {
|
|||
|
||||
let storageInstance: MemoryStorage | null = null;
|
||||
|
||||
function getStorage(): MemoryStorage {
|
||||
async function getStorage(): Promise<MemoryStorage> {
|
||||
if (!storageInstance) {
|
||||
storageInstance = new MemoryStorage(getDbPath());
|
||||
storageInstance = await MemoryStorage.create(getDbPath());
|
||||
}
|
||||
return storageInstance;
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ export default function memoryExtension(api: ExtensionAPI): void {
|
|||
|
||||
// Fire and forget
|
||||
runStartup(
|
||||
getStorage(),
|
||||
await getStorage(),
|
||||
{
|
||||
sessionsDir,
|
||||
memoryDir,
|
||||
|
|
@ -203,7 +203,7 @@ export default function memoryExtension(api: ExtensionAPI): void {
|
|||
"Delete all extracted memories for this project?",
|
||||
);
|
||||
if (confirmed) {
|
||||
getStorage().clearForCwd(ctx.cwd);
|
||||
(await getStorage()).clearForCwd(ctx.cwd);
|
||||
if (existsSync(projectMemoryDir)) {
|
||||
rmSync(projectMemoryDir, { recursive: true, force: true });
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ export default function memoryExtension(api: ExtensionAPI): void {
|
|||
"Re-extract all memories from session history? This may take a while.",
|
||||
);
|
||||
if (confirmed) {
|
||||
getStorage().resetAllForCwd(ctx.cwd);
|
||||
(await getStorage()).resetAllForCwd(ctx.cwd);
|
||||
if (existsSync(projectMemoryDir)) {
|
||||
rmSync(projectMemoryDir, { recursive: true, force: true });
|
||||
}
|
||||
|
|
@ -231,7 +231,7 @@ export default function memoryExtension(api: ExtensionAPI): void {
|
|||
}
|
||||
|
||||
case "stats": {
|
||||
const stats = getStorage().getStats();
|
||||
const stats = (await getStorage()).getStats();
|
||||
const statsText = [
|
||||
"Memory Pipeline Statistics:",
|
||||
` Total sessions tracked: ${stats.totalThreads}`,
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
* - jobs: lease-based job queue for pipeline phases
|
||||
*/
|
||||
|
||||
import Database from "better-sqlite3";
|
||||
import initSqlJs, { type Database as SqlJsDatabase } from "sql.js";
|
||||
import { randomUUID } from "crypto";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { dirname } from "path";
|
||||
|
||||
export interface ThreadRow {
|
||||
|
|
@ -44,23 +44,40 @@ export interface JobRow {
|
|||
}
|
||||
|
||||
export class MemoryStorage {
|
||||
private db: Database.Database;
|
||||
private db: SqlJsDatabase;
|
||||
private dbPath: string;
|
||||
|
||||
constructor(dbPath: string) {
|
||||
private constructor(db: SqlJsDatabase, dbPath: string) {
|
||||
this.db = db;
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
static async create(dbPath: string): Promise<MemoryStorage> {
|
||||
const dir = dirname(dbPath);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
this.db = new Database(dbPath);
|
||||
this.db.pragma("journal_mode = WAL");
|
||||
this.db.pragma("synchronous = NORMAL");
|
||||
this.db.pragma("busy_timeout = 5000");
|
||||
this.initSchema();
|
||||
const SQL = await initSqlJs();
|
||||
const buffer = existsSync(dbPath) ? readFileSync(dbPath) : undefined;
|
||||
const db = buffer ? new SQL.Database(buffer) : new SQL.Database();
|
||||
|
||||
db.run("PRAGMA journal_mode = WAL");
|
||||
db.run("PRAGMA synchronous = NORMAL");
|
||||
db.run("PRAGMA busy_timeout = 5000");
|
||||
|
||||
const storage = new MemoryStorage(db, dbPath);
|
||||
storage.initSchema();
|
||||
return storage;
|
||||
}
|
||||
|
||||
private persist(): void {
|
||||
const data = this.db.export();
|
||||
writeFileSync(this.dbPath, Buffer.from(data));
|
||||
}
|
||||
|
||||
private initSchema(): void {
|
||||
this.db.exec(`
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS threads (
|
||||
thread_id TEXT PRIMARY KEY,
|
||||
file_path TEXT NOT NULL,
|
||||
|
|
@ -71,15 +88,17 @@ export class MemoryStorage {
|
|||
error_message TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
)
|
||||
`);
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS stage1_outputs (
|
||||
thread_id TEXT PRIMARY KEY,
|
||||
extraction_json TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (thread_id) REFERENCES threads(thread_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
)
|
||||
`);
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
phase TEXT NOT NULL,
|
||||
|
|
@ -91,12 +110,28 @@ export class MemoryStorage {
|
|||
error_message TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_jobs_phase_status ON jobs(phase, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_threads_status ON threads(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_threads_cwd ON threads(cwd);
|
||||
)
|
||||
`);
|
||||
this.db.run("CREATE INDEX IF NOT EXISTS idx_jobs_phase_status ON jobs(phase, status)");
|
||||
this.db.run("CREATE INDEX IF NOT EXISTS idx_threads_status ON threads(status)");
|
||||
this.db.run("CREATE INDEX IF NOT EXISTS idx_threads_cwd ON threads(cwd)");
|
||||
this.persist();
|
||||
}
|
||||
|
||||
private queryAll<T>(sql: string, params: unknown[] = []): T[] {
|
||||
const stmt = this.db.prepare(sql);
|
||||
stmt.bind(params as (string | number | null | Uint8Array)[]);
|
||||
const rows: T[] = [];
|
||||
while (stmt.step()) {
|
||||
rows.push(stmt.getAsObject() as T);
|
||||
}
|
||||
stmt.free();
|
||||
return rows;
|
||||
}
|
||||
|
||||
private queryOne<T>(sql: string, params: unknown[] = []): T | undefined {
|
||||
const rows = this.queryAll<T>(sql, params);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -116,47 +151,40 @@ export class MemoryStorage {
|
|||
let updated = 0;
|
||||
let skipped = 0;
|
||||
|
||||
const selectStmt = this.db.prepare(
|
||||
"SELECT file_size, file_mtime, status FROM threads WHERE thread_id = ?",
|
||||
);
|
||||
const insertStmt = this.db.prepare(`
|
||||
INSERT INTO threads (thread_id, file_path, file_size, file_mtime, cwd, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'pending')
|
||||
`);
|
||||
const updateStmt = this.db.prepare(`
|
||||
UPDATE threads SET file_path = ?, file_size = ?, file_mtime = ?, cwd = ?,
|
||||
status = 'pending', updated_at = datetime('now')
|
||||
WHERE thread_id = ?
|
||||
`);
|
||||
const insertJobStmt = this.db.prepare(`
|
||||
INSERT OR IGNORE INTO jobs (id, phase, thread_id, status)
|
||||
VALUES (?, 'stage1', ?, 'pending')
|
||||
`);
|
||||
for (const t of threads) {
|
||||
const existing = this.queryOne<{ file_size: number; file_mtime: number; status: string }>(
|
||||
"SELECT file_size, file_mtime, status FROM threads WHERE thread_id = ?",
|
||||
[t.threadId],
|
||||
);
|
||||
|
||||
const upsertAll = this.db.transaction(() => {
|
||||
for (const t of threads) {
|
||||
const existing = selectStmt.get(t.threadId) as
|
||||
| { file_size: number; file_mtime: number; status: string }
|
||||
| undefined;
|
||||
|
||||
if (!existing) {
|
||||
insertStmt.run(t.threadId, t.filePath, t.fileSize, t.fileMtime, t.cwd);
|
||||
insertJobStmt.run(randomUUID(), t.threadId);
|
||||
inserted++;
|
||||
} else if (existing.file_size !== t.fileSize || existing.file_mtime !== t.fileMtime) {
|
||||
updateStmt.run(t.filePath, t.fileSize, t.fileMtime, t.cwd, t.threadId);
|
||||
// Re-enqueue if file changed and previous processing is done
|
||||
if (existing.status === "done" || existing.status === "error") {
|
||||
insertJobStmt.run(randomUUID(), t.threadId);
|
||||
}
|
||||
updated++;
|
||||
} else {
|
||||
skipped++;
|
||||
if (!existing) {
|
||||
this.db.run(
|
||||
"INSERT INTO threads (thread_id, file_path, file_size, file_mtime, cwd, status) VALUES (?, ?, ?, ?, ?, 'pending')",
|
||||
[t.threadId, t.filePath, t.fileSize, t.fileMtime, t.cwd],
|
||||
);
|
||||
this.db.run(
|
||||
"INSERT OR IGNORE INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')",
|
||||
[randomUUID(), t.threadId],
|
||||
);
|
||||
inserted++;
|
||||
} else if (existing.file_size !== t.fileSize || existing.file_mtime !== t.fileMtime) {
|
||||
this.db.run(
|
||||
"UPDATE threads SET file_path = ?, file_size = ?, file_mtime = ?, cwd = ?, status = 'pending', updated_at = datetime('now') WHERE thread_id = ?",
|
||||
[t.filePath, t.fileSize, t.fileMtime, t.cwd, t.threadId],
|
||||
);
|
||||
if (existing.status === "done" || existing.status === "error") {
|
||||
this.db.run(
|
||||
"INSERT OR IGNORE INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')",
|
||||
[randomUUID(), t.threadId],
|
||||
);
|
||||
}
|
||||
updated++;
|
||||
} else {
|
||||
skipped++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
upsertAll();
|
||||
this.persist();
|
||||
return { inserted, updated, skipped };
|
||||
}
|
||||
|
||||
|
|
@ -172,8 +200,8 @@ export class MemoryStorage {
|
|||
const token = randomUUID();
|
||||
const expiresAt = new Date(Date.now() + leaseSeconds * 1000).toISOString();
|
||||
|
||||
const claimStmt = this.db.prepare(`
|
||||
UPDATE jobs SET
|
||||
this.db.run(
|
||||
`UPDATE jobs SET
|
||||
status = 'claimed',
|
||||
worker_id = ?,
|
||||
ownership_token = ?,
|
||||
|
|
@ -184,16 +212,16 @@ export class MemoryStorage {
|
|||
WHERE phase = 'stage1'
|
||||
AND (status = 'pending' OR (status = 'claimed' AND lease_expires_at < datetime('now')))
|
||||
LIMIT ?
|
||||
)
|
||||
`);
|
||||
)`,
|
||||
[workerId, token, expiresAt, limit],
|
||||
);
|
||||
|
||||
const selectStmt = this.db.prepare(`
|
||||
SELECT id, thread_id FROM jobs
|
||||
WHERE ownership_token = ? AND status = 'claimed'
|
||||
`);
|
||||
const rows = this.queryAll<{ id: string; thread_id: string }>(
|
||||
"SELECT id, thread_id FROM jobs WHERE ownership_token = ? AND status = 'claimed'",
|
||||
[token],
|
||||
);
|
||||
|
||||
claimStmt.run(workerId, token, expiresAt, limit);
|
||||
const rows = selectStmt.all(token) as Array<{ id: string; thread_id: string }>;
|
||||
this.persist();
|
||||
|
||||
return rows.map((r) => ({
|
||||
jobId: r.id,
|
||||
|
|
@ -206,53 +234,34 @@ export class MemoryStorage {
|
|||
* Mark a stage1 job as complete and store the extraction output.
|
||||
*/
|
||||
completeStage1Job(threadId: string, output: string): void {
|
||||
const completeAll = this.db.transaction(() => {
|
||||
this.db
|
||||
.prepare(
|
||||
`UPDATE jobs SET status = 'done', updated_at = datetime('now')
|
||||
WHERE thread_id = ? AND phase = 'stage1' AND status = 'claimed'`,
|
||||
)
|
||||
.run(threadId);
|
||||
|
||||
this.db
|
||||
.prepare(
|
||||
`INSERT OR REPLACE INTO stage1_outputs (thread_id, extraction_json, created_at)
|
||||
VALUES (?, ?, datetime('now'))`,
|
||||
)
|
||||
.run(threadId, output);
|
||||
|
||||
this.db
|
||||
.prepare(
|
||||
`UPDATE threads SET status = 'done', updated_at = datetime('now')
|
||||
WHERE thread_id = ?`,
|
||||
)
|
||||
.run(threadId);
|
||||
});
|
||||
|
||||
completeAll();
|
||||
this.db.run(
|
||||
"UPDATE jobs SET status = 'done', updated_at = datetime('now') WHERE thread_id = ? AND phase = 'stage1' AND status = 'claimed'",
|
||||
[threadId],
|
||||
);
|
||||
this.db.run(
|
||||
"INSERT OR REPLACE INTO stage1_outputs (thread_id, extraction_json, created_at) VALUES (?, ?, datetime('now'))",
|
||||
[threadId, output],
|
||||
);
|
||||
this.db.run(
|
||||
"UPDATE threads SET status = 'done', updated_at = datetime('now') WHERE thread_id = ?",
|
||||
[threadId],
|
||||
);
|
||||
this.persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a stage1 job as errored.
|
||||
*/
|
||||
failStage1Job(threadId: string, errorMessage: string): void {
|
||||
const failAll = this.db.transaction(() => {
|
||||
this.db
|
||||
.prepare(
|
||||
`UPDATE jobs SET status = 'error', error_message = ?, updated_at = datetime('now')
|
||||
WHERE thread_id = ? AND phase = 'stage1' AND status = 'claimed'`,
|
||||
)
|
||||
.run(errorMessage, threadId);
|
||||
|
||||
this.db
|
||||
.prepare(
|
||||
`UPDATE threads SET status = 'error', error_message = ?, updated_at = datetime('now')
|
||||
WHERE thread_id = ?`,
|
||||
)
|
||||
.run(errorMessage, threadId);
|
||||
});
|
||||
|
||||
failAll();
|
||||
this.db.run(
|
||||
"UPDATE jobs SET status = 'error', error_message = ?, updated_at = datetime('now') WHERE thread_id = ? AND phase = 'stage1' AND status = 'claimed'",
|
||||
[errorMessage, threadId],
|
||||
);
|
||||
this.db.run(
|
||||
"UPDATE threads SET status = 'error', error_message = ?, updated_at = datetime('now') WHERE thread_id = ?",
|
||||
[errorMessage, threadId],
|
||||
);
|
||||
this.persist();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -266,73 +275,58 @@ export class MemoryStorage {
|
|||
const token = randomUUID();
|
||||
const expiresAt = new Date(Date.now() + leaseSeconds * 1000).toISOString();
|
||||
|
||||
const result = this.db.transaction(() => {
|
||||
// Check if all stage1 jobs are done
|
||||
const pendingStage1 = this.db
|
||||
.prepare(
|
||||
`SELECT COUNT(*) as cnt FROM jobs
|
||||
WHERE phase = 'stage1' AND status IN ('pending', 'claimed')`,
|
||||
)
|
||||
.get() as { cnt: number };
|
||||
const pendingStage1 = this.queryOne<{ cnt: number }>(
|
||||
"SELECT COUNT(*) as cnt FROM jobs WHERE phase = 'stage1' AND status IN ('pending', 'claimed')",
|
||||
);
|
||||
|
||||
if (pendingStage1.cnt > 0) {
|
||||
return null;
|
||||
}
|
||||
if (pendingStage1 && pendingStage1.cnt > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if there's already an active phase2 job
|
||||
const existingPhase2 = this.db
|
||||
.prepare(
|
||||
`SELECT id FROM jobs
|
||||
WHERE phase = 'stage2' AND status = 'claimed' AND lease_expires_at > datetime('now')`,
|
||||
)
|
||||
.get();
|
||||
const existingPhase2 = this.queryOne<{ id: string }>(
|
||||
"SELECT id FROM jobs WHERE phase = 'stage2' AND status = 'claimed' AND lease_expires_at > datetime('now')",
|
||||
);
|
||||
|
||||
if (existingPhase2) {
|
||||
return null;
|
||||
}
|
||||
if (existingPhase2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if there are any stage1 outputs to consolidate
|
||||
const outputCount = this.db
|
||||
.prepare("SELECT COUNT(*) as cnt FROM stage1_outputs")
|
||||
.get() as { cnt: number };
|
||||
const outputCount = this.queryOne<{ cnt: number }>(
|
||||
"SELECT COUNT(*) as cnt FROM stage1_outputs",
|
||||
);
|
||||
|
||||
if (outputCount.cnt === 0) {
|
||||
return null;
|
||||
}
|
||||
if (!outputCount || outputCount.cnt === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const jobId = randomUUID();
|
||||
this.db
|
||||
.prepare(
|
||||
`INSERT INTO jobs (id, phase, status, worker_id, ownership_token, lease_expires_at)
|
||||
VALUES (?, 'stage2', 'claimed', ?, ?, ?)`,
|
||||
)
|
||||
.run(jobId, workerId, token, expiresAt);
|
||||
const jobId = randomUUID();
|
||||
this.db.run(
|
||||
"INSERT INTO jobs (id, phase, status, worker_id, ownership_token, lease_expires_at) VALUES (?, 'stage2', 'claimed', ?, ?, ?)",
|
||||
[jobId, workerId, token, expiresAt],
|
||||
);
|
||||
|
||||
return { jobId, ownershipToken: token };
|
||||
})();
|
||||
|
||||
return result;
|
||||
this.persist();
|
||||
return { jobId, ownershipToken: token };
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the phase 2 consolidation job.
|
||||
*/
|
||||
completePhase2Job(jobId: string): void {
|
||||
this.db
|
||||
.prepare(
|
||||
`UPDATE jobs SET status = 'done', updated_at = datetime('now')
|
||||
WHERE id = ? AND phase = 'stage2'`,
|
||||
)
|
||||
.run(jobId);
|
||||
this.db.run(
|
||||
"UPDATE jobs SET status = 'done', updated_at = datetime('now') WHERE id = ? AND phase = 'stage2'",
|
||||
[jobId],
|
||||
);
|
||||
this.persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all stage1 extraction outputs.
|
||||
*/
|
||||
getStage1Outputs(): Array<{ threadId: string; extractionJson: string }> {
|
||||
const rows = this.db
|
||||
.prepare("SELECT thread_id, extraction_json FROM stage1_outputs")
|
||||
.all() as Array<{ thread_id: string; extraction_json: string }>;
|
||||
const rows = this.queryAll<{ thread_id: string; extraction_json: string }>(
|
||||
"SELECT thread_id, extraction_json FROM stage1_outputs",
|
||||
);
|
||||
|
||||
return rows.map((r) => ({
|
||||
threadId: r.thread_id,
|
||||
|
|
@ -344,13 +338,12 @@ export class MemoryStorage {
|
|||
* Get all stage1 outputs for a specific cwd.
|
||||
*/
|
||||
getStage1OutputsForCwd(cwd: string): Array<{ threadId: string; extractionJson: string }> {
|
||||
const rows = this.db
|
||||
.prepare(
|
||||
`SELECT s.thread_id, s.extraction_json FROM stage1_outputs s
|
||||
INNER JOIN threads t ON t.thread_id = s.thread_id
|
||||
WHERE t.cwd = ?`,
|
||||
)
|
||||
.all(cwd) as Array<{ thread_id: string; extraction_json: string }>;
|
||||
const rows = this.queryAll<{ thread_id: string; extraction_json: string }>(
|
||||
`SELECT s.thread_id, s.extraction_json FROM stage1_outputs s
|
||||
INNER JOIN threads t ON t.thread_id = s.thread_id
|
||||
WHERE t.cwd = ?`,
|
||||
[cwd],
|
||||
);
|
||||
|
||||
return rows.map((r) => ({
|
||||
threadId: r.thread_id,
|
||||
|
|
@ -362,9 +355,10 @@ export class MemoryStorage {
|
|||
* Get thread info by ID.
|
||||
*/
|
||||
getThread(threadId: string): ThreadRow | undefined {
|
||||
return this.db.prepare("SELECT * FROM threads WHERE thread_id = ?").get(threadId) as
|
||||
| ThreadRow
|
||||
| undefined;
|
||||
return this.queryOne<ThreadRow>(
|
||||
"SELECT * FROM threads WHERE thread_id = ?",
|
||||
[threadId],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -378,24 +372,22 @@ export class MemoryStorage {
|
|||
totalStage1Outputs: number;
|
||||
pendingStage1Jobs: number;
|
||||
} {
|
||||
const threads = this.db.prepare(`
|
||||
const threads = this.queryOne<{ total: number; pending: number; done: number; errors: number }>(`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
|
||||
SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done,
|
||||
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errors
|
||||
FROM threads
|
||||
`).get() as { total: number; pending: number; done: number; errors: number };
|
||||
`)!;
|
||||
|
||||
const outputs = this.db.prepare("SELECT COUNT(*) as cnt FROM stage1_outputs").get() as {
|
||||
cnt: number;
|
||||
};
|
||||
const outputs = this.queryOne<{ cnt: number }>(
|
||||
"SELECT COUNT(*) as cnt FROM stage1_outputs",
|
||||
)!;
|
||||
|
||||
const pendingJobs = this.db
|
||||
.prepare(
|
||||
"SELECT COUNT(*) as cnt FROM jobs WHERE phase = 'stage1' AND status IN ('pending', 'claimed')",
|
||||
)
|
||||
.get() as { cnt: number };
|
||||
const pendingJobs = this.queryOne<{ cnt: number }>(
|
||||
"SELECT COUNT(*) as cnt FROM jobs WHERE phase = 'stage1' AND status IN ('pending', 'claimed')",
|
||||
)!;
|
||||
|
||||
return {
|
||||
totalThreads: threads.total,
|
||||
|
|
@ -411,78 +403,61 @@ export class MemoryStorage {
|
|||
* Clear all data (for /memory clear).
|
||||
*/
|
||||
clearAll(): void {
|
||||
this.db.transaction(() => {
|
||||
this.db.exec("DELETE FROM stage1_outputs");
|
||||
this.db.exec("DELETE FROM jobs");
|
||||
this.db.exec("DELETE FROM threads");
|
||||
})();
|
||||
this.db.run("DELETE FROM stage1_outputs");
|
||||
this.db.run("DELETE FROM jobs");
|
||||
this.db.run("DELETE FROM threads");
|
||||
this.persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear data for a specific cwd (for /memory clear in project scope).
|
||||
*/
|
||||
clearForCwd(cwd: string): void {
|
||||
this.db.transaction(() => {
|
||||
this.db
|
||||
.prepare(
|
||||
`DELETE FROM stage1_outputs WHERE thread_id IN (
|
||||
SELECT thread_id FROM threads WHERE cwd = ?)`,
|
||||
)
|
||||
.run(cwd);
|
||||
this.db
|
||||
.prepare(
|
||||
`DELETE FROM jobs WHERE thread_id IN (
|
||||
SELECT thread_id FROM threads WHERE cwd = ?)`,
|
||||
)
|
||||
.run(cwd);
|
||||
this.db.prepare("DELETE FROM threads WHERE cwd = ?").run(cwd);
|
||||
})();
|
||||
this.db.run(
|
||||
"DELETE FROM stage1_outputs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)",
|
||||
[cwd],
|
||||
);
|
||||
this.db.run(
|
||||
"DELETE FROM jobs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)",
|
||||
[cwd],
|
||||
);
|
||||
this.db.run("DELETE FROM threads WHERE cwd = ?", [cwd]);
|
||||
this.persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all threads to pending (for /memory rebuild).
|
||||
*/
|
||||
resetAllForCwd(cwd: string): void {
|
||||
this.db.transaction(() => {
|
||||
// Delete existing stage1 outputs
|
||||
this.db
|
||||
.prepare(
|
||||
`DELETE FROM stage1_outputs WHERE thread_id IN (
|
||||
SELECT thread_id FROM threads WHERE cwd = ?)`,
|
||||
)
|
||||
.run(cwd);
|
||||
this.db.run(
|
||||
"DELETE FROM stage1_outputs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)",
|
||||
[cwd],
|
||||
);
|
||||
this.db.run(
|
||||
"DELETE FROM jobs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)",
|
||||
[cwd],
|
||||
);
|
||||
this.db.run(
|
||||
"UPDATE threads SET status = 'pending', updated_at = datetime('now') WHERE cwd = ?",
|
||||
[cwd],
|
||||
);
|
||||
|
||||
// Delete old jobs
|
||||
this.db
|
||||
.prepare(
|
||||
`DELETE FROM jobs WHERE thread_id IN (
|
||||
SELECT thread_id FROM threads WHERE cwd = ?)`,
|
||||
)
|
||||
.run(cwd);
|
||||
const threads = this.queryAll<{ thread_id: string }>(
|
||||
"SELECT thread_id FROM threads WHERE cwd = ?",
|
||||
[cwd],
|
||||
);
|
||||
|
||||
// Reset thread status
|
||||
this.db
|
||||
.prepare(
|
||||
`UPDATE threads SET status = 'pending', updated_at = datetime('now')
|
||||
WHERE cwd = ?`,
|
||||
)
|
||||
.run(cwd);
|
||||
|
||||
// Re-create stage1 jobs
|
||||
const threads = this.db
|
||||
.prepare("SELECT thread_id FROM threads WHERE cwd = ?")
|
||||
.all(cwd) as Array<{ thread_id: string }>;
|
||||
|
||||
const insertJobStmt = this.db.prepare(
|
||||
for (const t of threads) {
|
||||
this.db.run(
|
||||
"INSERT INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')",
|
||||
[randomUUID(), t.thread_id],
|
||||
);
|
||||
for (const t of threads) {
|
||||
insertJobStmt.run(randomUUID(), t.thread_id);
|
||||
}
|
||||
})();
|
||||
}
|
||||
this.persist();
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.persist();
|
||||
this.db.close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue