engine-dj-mcp 0.11.2 → 0.11.3
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 +4 -1
- package/dist/store/backup.d.ts +24 -0
- package/dist/store/backup.js +54 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# engine-dj-mcp
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Venut-Labs/engine-dj-mcp/actions/workflows/ci.yml)
|
|
3
4
|
[](https://www.npmjs.com/package/engine-dj-mcp)
|
|
4
5
|
[](./LICENSE)
|
|
5
6
|
|
|
@@ -58,7 +59,9 @@ the configuration you are reading:
|
|
|
58
59
|
}
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
**Requirements:** Node.js 22.
|
|
62
|
+
**Requirements:** Node.js 22.16 or newer (`node:sqlite` stopped needing a
|
|
63
|
+
flag in 22.13, but the pre-write snapshot uses its `backup()`, added in
|
|
64
|
+
22.16;
|
|
62
65
|
there are no native dependencies), and an Engine DJ library at schema 3.0.0
|
|
63
66
|
through 3.0.2 — Engine DJ 4.5 and 5.x.
|
|
64
67
|
|
package/dist/store/backup.d.ts
CHANGED
|
@@ -1,2 +1,26 @@
|
|
|
1
1
|
import { type EngineError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* The snapshots to delete, oldest first, from a directory listing.
|
|
4
|
+
*
|
|
5
|
+
* Two name shapes live here: the tagged `${uuid}-${tag}-${stamp}.db` written
|
|
6
|
+
* now, and the untagged `${uuid}-${stamp}.db` an earlier version wrote.
|
|
7
|
+
* Without the second, those sat outside every namespace and were never
|
|
8
|
+
* reclaimed -- up to KEEP full copies of a library, kept forever, on any
|
|
9
|
+
* upgrading user. Two libraries sharing a uuid is precisely why the tag
|
|
10
|
+
* exists, and an untagged file cannot say which of them it came from, so
|
|
11
|
+
* ageing them out under whichever library writes next is the only thing left
|
|
12
|
+
* to do with them.
|
|
13
|
+
*
|
|
14
|
+
* Ordering is by the stamp alone, never by the whole filename. Sorting the
|
|
15
|
+
* names as text compares a tag against a year at the same offset, and a tag
|
|
16
|
+
* is hex: `1e269292c523` sorts *before* `2026-...`, so a library whose path
|
|
17
|
+
* happens to hash to a tag starting 0 or 1 had its newest snapshot land at
|
|
18
|
+
* the head of the list and be deleted -- the file the caller had just been
|
|
19
|
+
* handed as its way back. Measured: it passed locally under the tag
|
|
20
|
+
* `cf11e2d00f88` and failed in CI under `1e269292c523`, same code.
|
|
21
|
+
*
|
|
22
|
+
* Untagged files sort before every tagged one regardless of stamp: they
|
|
23
|
+
* predate the tagged scheme, so they predate anything written since.
|
|
24
|
+
*/
|
|
25
|
+
export declare function evictable(names: string[], uuid: string, tag: string): string[];
|
|
2
26
|
export declare function snapshotLibrary(mdbPath: string, uuid: string, baseDir: string): Promise<string | EngineError>;
|
package/dist/store/backup.js
CHANGED
|
@@ -33,7 +33,59 @@ function stamp() {
|
|
|
33
33
|
const iso = new Date().toISOString().replace(/[:.]/g, "-");
|
|
34
34
|
return `${iso}-${String(++counter).padStart(10, "0")}`;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The snapshots to delete, oldest first, from a directory listing.
|
|
38
|
+
*
|
|
39
|
+
* Two name shapes live here: the tagged `${uuid}-${tag}-${stamp}.db` written
|
|
40
|
+
* now, and the untagged `${uuid}-${stamp}.db` an earlier version wrote.
|
|
41
|
+
* Without the second, those sat outside every namespace and were never
|
|
42
|
+
* reclaimed -- up to KEEP full copies of a library, kept forever, on any
|
|
43
|
+
* upgrading user. Two libraries sharing a uuid is precisely why the tag
|
|
44
|
+
* exists, and an untagged file cannot say which of them it came from, so
|
|
45
|
+
* ageing them out under whichever library writes next is the only thing left
|
|
46
|
+
* to do with them.
|
|
47
|
+
*
|
|
48
|
+
* Ordering is by the stamp alone, never by the whole filename. Sorting the
|
|
49
|
+
* names as text compares a tag against a year at the same offset, and a tag
|
|
50
|
+
* is hex: `1e269292c523` sorts *before* `2026-...`, so a library whose path
|
|
51
|
+
* happens to hash to a tag starting 0 or 1 had its newest snapshot land at
|
|
52
|
+
* the head of the list and be deleted -- the file the caller had just been
|
|
53
|
+
* handed as its way back. Measured: it passed locally under the tag
|
|
54
|
+
* `cf11e2d00f88` and failed in CI under `1e269292c523`, same code.
|
|
55
|
+
*
|
|
56
|
+
* Untagged files sort before every tagged one regardless of stamp: they
|
|
57
|
+
* predate the tagged scheme, so they predate anything written since.
|
|
58
|
+
*/
|
|
59
|
+
export function evictable(names, uuid, tag) {
|
|
60
|
+
const tagged = `${uuid}-${tag}-`;
|
|
61
|
+
const legacy = new RegExp(`^${uuid}-(\\d{4}-.*)\\.db$`);
|
|
62
|
+
const mine = [];
|
|
63
|
+
for (const name of names) {
|
|
64
|
+
if (!name.endsWith(".db"))
|
|
65
|
+
continue;
|
|
66
|
+
if (name.startsWith(tagged)) {
|
|
67
|
+
mine.push({ name, old: false, stamp: name.slice(tagged.length, -3) });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const m = legacy.exec(name);
|
|
71
|
+
if (m)
|
|
72
|
+
mine.push({ name, old: true, stamp: m[1] });
|
|
73
|
+
}
|
|
74
|
+
mine.sort((a, b) => (a.old !== b.old ? (a.old ? -1 : 1) : a.stamp < b.stamp ? -1 : a.stamp > b.stamp ? 1 : 0));
|
|
75
|
+
return mine.slice(0, Math.max(0, mine.length - KEEP)).map((x) => x.name);
|
|
76
|
+
}
|
|
36
77
|
export async function snapshotLibrary(mdbPath, uuid, baseDir) {
|
|
78
|
+
// node:sqlite stopped needing a flag in 22.13, which is where this
|
|
79
|
+
// project's floor used to sit -- but backup() only arrived in 22.16. On
|
|
80
|
+
// 22.13 through 22.15 the read path works perfectly and this one throws
|
|
81
|
+
// "backup is not a function", which is what CI reported on its very first
|
|
82
|
+
// run against the declared floor. `engines` now says 22.16, and npm only
|
|
83
|
+
// enforces that under engine-strict, so the check is here too: a version
|
|
84
|
+
// number a user can act on beats a TypeError from inside a dependency.
|
|
85
|
+
if (typeof backup !== "function") {
|
|
86
|
+
return err("library_unreadable", `This Node cannot snapshot a library before writing to it: node:sqlite gained backup() in ` +
|
|
87
|
+
`22.16.0 and this is ${process.version}. Upgrade Node, or run without --allow-writes.`);
|
|
88
|
+
}
|
|
37
89
|
let src;
|
|
38
90
|
try {
|
|
39
91
|
mkdirSync(baseDir, { recursive: true });
|
|
@@ -50,21 +102,8 @@ export async function snapshotLibrary(mdbPath, uuid, baseDir) {
|
|
|
50
102
|
await backup(src, dest);
|
|
51
103
|
src.close();
|
|
52
104
|
src = undefined;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// those sat outside every namespace and were never reclaimed -- up to
|
|
56
|
-
// KEEP full copies of a library, kept forever, on any upgrading user.
|
|
57
|
-
// They predate the tag and therefore predate everything written since,
|
|
58
|
-
// which is why folding them into one window evicts them first. Two
|
|
59
|
-
// libraries sharing a uuid is precisely why the tag exists, and an
|
|
60
|
-
// untagged file cannot say which of them it came from -- ageing them out
|
|
61
|
-
// under whichever library writes next is the only thing left to do.
|
|
62
|
-
const legacy = new RegExp(`^${uuid}-\\d{4}-`);
|
|
63
|
-
const mine = readdirSync(baseDir)
|
|
64
|
-
.filter((f) => f.endsWith(".db") && (f.startsWith(prefix) || legacy.test(f)))
|
|
65
|
-
.sort();
|
|
66
|
-
for (const old of mine.slice(0, Math.max(0, mine.length - KEEP))) {
|
|
67
|
-
rmSync(join(baseDir, old), { force: true });
|
|
105
|
+
for (const stale of evictable(readdirSync(baseDir), uuid, libraryTag(mdbPath))) {
|
|
106
|
+
rmSync(join(baseDir, stale), { force: true });
|
|
68
107
|
}
|
|
69
108
|
return dest;
|
|
70
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engine-dj-mcp",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "MCP server for an Engine DJ library: search and audit it, read cues and beatgrids, and build playlists when you ask. Not affiliated with inMusic or Denon DJ.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"type": "module",
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=22.
|
|
30
|
+
"node": ">=22.16.0"
|
|
31
31
|
},
|
|
32
32
|
"bin": {
|
|
33
33
|
"engine-dj-mcp": "dist/index.js"
|