engine-dj-mcp 0.11.2 → 0.12.0
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 +152 -28
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +6 -0
- package/dist/playlists.d.ts +19 -5
- package/dist/playlists.js +32 -14
- package/dist/server.js +115 -2
- package/dist/store/backup.d.ts +24 -0
- package/dist/store/backup.js +54 -15
- package/dist/store/write.d.ts +169 -0
- package/dist/store/write.js +930 -101
- package/dist/tools/write-playlist.d.ts +38 -1
- package/dist/tools/write-playlist.js +103 -1
- package/package.json +2 -2
package/dist/store/write.d.ts
CHANGED
|
@@ -6,6 +6,44 @@ export interface CreatePlaylistResult {
|
|
|
6
6
|
tracks_added: number;
|
|
7
7
|
backup_path: string;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* What editing an existing playlist's entries returns. One shape for
|
|
11
|
+
* add/remove/reorder alike -- each op leaves the fields it did not touch
|
|
12
|
+
* undefined rather than the module growing a result type per verb.
|
|
13
|
+
*/
|
|
14
|
+
export interface EditResult {
|
|
15
|
+
playlist_id: number;
|
|
16
|
+
tracks_added?: number;
|
|
17
|
+
tracks_removed?: number;
|
|
18
|
+
positions?: number[];
|
|
19
|
+
removed?: {
|
|
20
|
+
position: number;
|
|
21
|
+
track_id: number | null;
|
|
22
|
+
}[];
|
|
23
|
+
undo: UndoStep[];
|
|
24
|
+
/**
|
|
25
|
+
* Whether replaying every step of `undo` puts the playlist back exactly as
|
|
26
|
+
* it was. Always present, never inferred from `undo`'s length: a client
|
|
27
|
+
* that read a missing field as false -- or a full-looking `undo` as
|
|
28
|
+
* complete -- would get the one question that matters here backwards.
|
|
29
|
+
*
|
|
30
|
+
* False only for removeTracksFromPlaylist, and only for a removal that
|
|
31
|
+
* included an entry whose stored origin pair matches no track in this
|
|
32
|
+
* library. Such an entry has no track id to hand back to
|
|
33
|
+
* add_tracks_to_playlist, so no undo step for it can exist; `undo_note`
|
|
34
|
+
* then names those positions. The steps that *are* emitted still run, and
|
|
35
|
+
* still restore everything else to its original position.
|
|
36
|
+
*/
|
|
37
|
+
undo_complete: boolean;
|
|
38
|
+
/** Set only when `undo_complete` is false: which positions have no way back, and why. */
|
|
39
|
+
undo_note?: string;
|
|
40
|
+
backup_path: string;
|
|
41
|
+
}
|
|
42
|
+
/** One step of the tool call that would undo an edit, in the shape a client replays it. */
|
|
43
|
+
export interface UndoStep {
|
|
44
|
+
tool: string;
|
|
45
|
+
arguments: Record<string, unknown>;
|
|
46
|
+
}
|
|
9
47
|
export interface OriginRef {
|
|
10
48
|
uuid: string;
|
|
11
49
|
trackId: number;
|
|
@@ -28,6 +66,48 @@ export declare function resetSessionSnapshots(): void;
|
|
|
28
66
|
* re-originated-track test is for, not this readback.
|
|
29
67
|
*/
|
|
30
68
|
export declare function walkFrom(db: DatabaseSync, listId: number, headId: number): OriginRef[];
|
|
69
|
+
/**
|
|
70
|
+
* One playlist's `PlaylistEntity` rows, in the shape `checkChain` wants: an
|
|
71
|
+
* entry id and the id it links to (0 for "links to nothing").
|
|
72
|
+
*
|
|
73
|
+
* Shared by every op that edits an *existing* playlist's entries --
|
|
74
|
+
* createPlaylist never calls this, because it builds a chain from nothing
|
|
75
|
+
* rather than reading one back.
|
|
76
|
+
*/
|
|
77
|
+
export declare function readChain(db: DatabaseSync, listId: number): {
|
|
78
|
+
id: number;
|
|
79
|
+
next: number;
|
|
80
|
+
}[];
|
|
81
|
+
export interface ChainCheck {
|
|
82
|
+
ok: boolean;
|
|
83
|
+
reason?: string;
|
|
84
|
+
/** Entry ids head to tail. Meaningful only when `ok`. */
|
|
85
|
+
order: number[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Whether one playlist's entry chain is sound enough to edit.
|
|
89
|
+
*
|
|
90
|
+
* Deliberately not `orderByChain` from src/playlists.ts. That function's
|
|
91
|
+
* contract is that it returns every node it was given, degrading a damaged
|
|
92
|
+
* chain to a warning, because a reader that silently returned 30 of 43
|
|
93
|
+
* entries would be worse than one that guesses an order and says so. An edit
|
|
94
|
+
* needs the opposite: a yes or no.
|
|
95
|
+
*
|
|
96
|
+
* Four conditions, and each is needed because different breakages trip
|
|
97
|
+
* different ones. Checking only that the walk covered every row is the trap:
|
|
98
|
+
* a list severed into two runs has two heads, and walking from both covers
|
|
99
|
+
* everything -- measured, on a chain broken on purpose, as "5 of 5" while the
|
|
100
|
+
* walk from the real head reached 2. Checking coverage without also checking
|
|
101
|
+
* that the walk *ended* is a second, subtler version of the same trap: a row
|
|
102
|
+
* whose next points back into an already-linked interior row (two
|
|
103
|
+
* predecessors, no row pointing at 0) can visit every row and still never
|
|
104
|
+
* terminate -- the walk stops only because it revisits a row it has already
|
|
105
|
+
* seen, not because it reached the end.
|
|
106
|
+
*/
|
|
107
|
+
export declare function checkChain(rows: {
|
|
108
|
+
id: number;
|
|
109
|
+
next: number;
|
|
110
|
+
}[]): ChainCheck;
|
|
31
111
|
export declare function sameOrder(a: OriginRef[], b: OriginRef[]): boolean;
|
|
32
112
|
export declare function createPlaylist(mdbPath: string, uuid: string, input: {
|
|
33
113
|
title: string;
|
|
@@ -35,3 +115,92 @@ export declare function createPlaylist(mdbPath: string, uuid: string, input: {
|
|
|
35
115
|
}, opts: {
|
|
36
116
|
backupDir: string;
|
|
37
117
|
}): Promise<CreatePlaylistResult | EngineError>;
|
|
118
|
+
/** Where `addTracksToPlaylist`'s `at` can put the new run. */
|
|
119
|
+
export type InsertAt = "end" | "start" | {
|
|
120
|
+
after_position: number;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Adds one or more tracks to an existing playlist, at the start, the end, or
|
|
124
|
+
* after a named position in its current order.
|
|
125
|
+
*
|
|
126
|
+
* Shares its skeleton with createPlaylist: a read-only pre-check first (cheap
|
|
127
|
+
* enough to rule out the common failure modes without ever opening the
|
|
128
|
+
* library for writing), then withWriteTransaction. Where createPlaylist
|
|
129
|
+
* builds a chain from nothing, this extends one that already exists, so it
|
|
130
|
+
* also has to confirm that chain is sound before it touches it -- twice. The
|
|
131
|
+
* pre-check gates it once, both to fail fast (and skip the snapshot) for a
|
|
132
|
+
* playlist that cannot be edited at all, and because validating `at` needs
|
|
133
|
+
* to know how many entries the playlist currently has. The transaction gates
|
|
134
|
+
* it again after BEGIN IMMEDIATE, because that lock is the first moment
|
|
135
|
+
* nothing else can change the chain -- gating on the pre-check's read alone,
|
|
136
|
+
* or reusing the insert position it computed, would be trusting one that
|
|
137
|
+
* could already be stale.
|
|
138
|
+
*/
|
|
139
|
+
export declare function addTracksToPlaylist(mdbPath: string, uuid: string, input: {
|
|
140
|
+
listId: number;
|
|
141
|
+
trackIds: number[];
|
|
142
|
+
at: InsertAt;
|
|
143
|
+
}, opts: {
|
|
144
|
+
backupDir: string;
|
|
145
|
+
}): Promise<EditResult | EngineError>;
|
|
146
|
+
/**
|
|
147
|
+
* Removes one or more tracks from an existing playlist by their current
|
|
148
|
+
* position.
|
|
149
|
+
*
|
|
150
|
+
* `trigger_before_delete_PlaylistEntity` (see gen-library.ts's copy of it,
|
|
151
|
+
* taken verbatim from a real 3.0.2 library) relinks each deleted row's
|
|
152
|
+
* predecessor onto its successor as SQLite processes the delete -- verified
|
|
153
|
+
* for a row removed at the head, the middle and the tail, and, by
|
|
154
|
+
* construction of the trigger itself, for a batch that removes several
|
|
155
|
+
* rows, adjacent or not, in one statement. So this function does no chain
|
|
156
|
+
* maintenance of its own; writing any would just be fighting Engine's own
|
|
157
|
+
* trigger. What it does own is checking that the trigger's job actually
|
|
158
|
+
* landed: its `WHEN OLD.trackId > 0` means a row with trackId <= 0 is
|
|
159
|
+
* deleted *without* relinking, leaving its predecessor pointing at a row
|
|
160
|
+
* that is now gone. No real library measured has such a row, but the
|
|
161
|
+
* post-delete check below is the only thing that would ever notice one --
|
|
162
|
+
* and it checks the surviving order against what was expected, not only
|
|
163
|
+
* that some sound chain is left, because "sound" and "right" are different
|
|
164
|
+
* questions and only the second one is what the caller asked for.
|
|
165
|
+
*
|
|
166
|
+
* Shares createPlaylist/addTracksToPlaylist's skeleton: a read-only
|
|
167
|
+
* pre-check first, then withWriteTransaction.
|
|
168
|
+
*/
|
|
169
|
+
export declare function removeTracksFromPlaylist(mdbPath: string, uuid: string, input: {
|
|
170
|
+
listId: number;
|
|
171
|
+
positions: number[];
|
|
172
|
+
expectTrackIds?: (number | null)[];
|
|
173
|
+
}, opts: {
|
|
174
|
+
backupDir: string;
|
|
175
|
+
}): Promise<EditResult | EngineError>;
|
|
176
|
+
/**
|
|
177
|
+
* Reorders an existing playlist's entries to a caller-given permutation of
|
|
178
|
+
* its current order.
|
|
179
|
+
*
|
|
180
|
+
* Unlike add/remove, this rewrites links only -- no INSERT, no DELETE -- so
|
|
181
|
+
* none of Engine's PlaylistEntity triggers fire and there is no trigger
|
|
182
|
+
* behaviour to trust or verify here, only the links this function writes
|
|
183
|
+
* itself. `order[i]` names the *current* 1-based position of the track that
|
|
184
|
+
* should end up at position `i + 1` (see validatePermutation for why the
|
|
185
|
+
* spec takes a full permutation rather than a move instruction). Only the
|
|
186
|
+
* entries whose successor actually changes get an UPDATE -- measured on a
|
|
187
|
+
* real library, moving an entry from the middle to the front took exactly
|
|
188
|
+
* two updates, and the identity permutation writes no PlaylistEntity row at
|
|
189
|
+
* all. It is still not a no-op: like every other op here it stamps
|
|
190
|
+
* `Playlist.lastEditTime`, and the session's snapshot is copied before the
|
|
191
|
+
* body ever runs, so the identity case costs a timestamp and (once per
|
|
192
|
+
* session) a snapshot. Left that way deliberately -- "did this permutation
|
|
193
|
+
* change anything" can only be answered honestly after BEGIN IMMEDIATE, by
|
|
194
|
+
* which point the snapshot is already taken, and an edit that reports
|
|
195
|
+
* success without touching lastEditTime would be the one op whose result
|
|
196
|
+
* Engine cannot see.
|
|
197
|
+
*
|
|
198
|
+
* Shares createPlaylist/addTracksToPlaylist/removeTracksFromPlaylist's
|
|
199
|
+
* skeleton: a read-only pre-check first, then withWriteTransaction.
|
|
200
|
+
*/
|
|
201
|
+
export declare function reorderPlaylist(mdbPath: string, uuid: string, input: {
|
|
202
|
+
listId: number;
|
|
203
|
+
order: number[];
|
|
204
|
+
}, opts: {
|
|
205
|
+
backupDir: string;
|
|
206
|
+
}): Promise<EditResult | EngineError>;
|