mdmaid-desk 0.1.29 → 0.1.31
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 +60 -8
- package/dist/api-client.d.ts +39 -12
- package/dist/api-client.js +257 -67
- package/dist/api-client.js.map +1 -1
- package/dist/api-types.d.ts +32 -1
- package/dist/api-types.js.map +1 -1
- package/dist/catalog.d.ts +78 -22
- package/dist/catalog.js +710 -88
- package/dist/catalog.js.map +1 -1
- package/dist/cli.js +281 -12
- package/dist/cli.js.map +1 -1
- package/dist/daemon-state.js +6 -3
- package/dist/daemon-state.js.map +1 -1
- package/dist/domain.d.ts +27 -0
- package/dist/domain.js +8 -0
- package/dist/domain.js.map +1 -1
- package/dist/server.js +390 -98
- package/dist/server.js.map +1 -1
- package/dist/sqlite-storage.d.ts +20 -9
- package/dist/sqlite-storage.js +338 -38
- package/dist/sqlite-storage.js.map +1 -1
- package/dist/storage.d.ts +15 -7
- package/dist/tui.d.ts +25 -4
- package/dist/tui.js +512 -125
- package/dist/tui.js.map +1 -1
- package/dist/web-client.d.ts +22 -1
- package/dist/web-client.js +810 -117
- package/dist/web-client.js.map +1 -1
- package/dist/web-styles.d.ts +1 -1
- package/dist/web-styles.js +190 -24
- package/dist/web-styles.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,6 +128,27 @@ node dist/cli.js workspace add /path/to/repository \
|
|
|
128
128
|
--repository-name "Example"
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
A canonical repository/worktree root can belong to only one workspace ID.
|
|
132
|
+
Ticket and task identifiers belong in document/project metadata and must not be
|
|
133
|
+
used as workspace IDs. If an older catalog contains two IDs for one root,
|
|
134
|
+
inspect a dry-run reconciliation before applying it:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
mdmaid-desk workspace reconcile task-derived-id \
|
|
138
|
+
--into stable-repository-id \
|
|
139
|
+
--json
|
|
140
|
+
|
|
141
|
+
mdmaid-desk workspace reconcile task-derived-id \
|
|
142
|
+
--into stable-repository-id \
|
|
143
|
+
--apply
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The operation preserves non-conflicting document and review IDs. Path
|
|
147
|
+
conflicts block the repair. An archived conflicting document may be discarded
|
|
148
|
+
only with `--discard-archived-conflicts`; review history is never discarded.
|
|
149
|
+
The command uses the live daemon when available, so its dry run and mutation
|
|
150
|
+
observe the same catalog authority.
|
|
151
|
+
|
|
131
152
|
Register a document:
|
|
132
153
|
|
|
133
154
|
```bash
|
|
@@ -216,11 +237,11 @@ file does not remove the imported copy.
|
|
|
216
237
|
Every registered-source render re-authorizes the real path and reconciles its
|
|
217
238
|
content hash, revision, reading state, review state, and local-link mappings.
|
|
218
239
|
When a foreground web service or daemon is running, mdmaid.desk also watches
|
|
219
|
-
the parent directories of active registered sources.
|
|
220
|
-
change
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
240
|
+
the parent directories of active registered sources. Every committed catalog
|
|
241
|
+
change publishes one metadata-free invalidation; Web, TUI, and waiting CLI
|
|
242
|
+
clients then reread authoritative state for their selected Space. The
|
|
243
|
+
interfaces label registered documents as `live source` and imports as
|
|
244
|
+
`snapshot`.
|
|
224
245
|
|
|
225
246
|
Without a running service, registration and rendering still work and a manual
|
|
226
247
|
render reconciles current source state. Automatic push refresh is
|
|
@@ -248,6 +269,26 @@ node dist/cli.js list --workspace example
|
|
|
248
269
|
node dist/cli.js list --task PROJECT-123
|
|
249
270
|
```
|
|
250
271
|
|
|
272
|
+
Create persistent Spaces that classify current and future documents from one
|
|
273
|
+
or more repository, repository-namespace, workspace, or tag patterns:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
mdmaid-desk space add work --name "Work" \
|
|
277
|
+
--namespace github.com/example \
|
|
278
|
+
--tag work
|
|
279
|
+
mdmaid-desk space add home --name "Home" --workspace personal
|
|
280
|
+
mdmaid-desk space list
|
|
281
|
+
mdmaid-desk repository list
|
|
282
|
+
mdmaid-desk list --space work
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
An agent creating a Space must ask which patterns belong to it when none were
|
|
286
|
+
provided. Matchers use OR semantics and membership is derived from current
|
|
287
|
+
repository identity and tags, so later matching documents appear automatically.
|
|
288
|
+
Use `space matchers set`, `space rename`, and `space delete` to manage the
|
|
289
|
+
definition. Space-aware commands refuse a healthy older daemon and ask for a
|
|
290
|
+
restart after upgrade instead of bypassing it with direct catalog access.
|
|
291
|
+
|
|
251
292
|
Run the browser workspace as a foreground local service:
|
|
252
293
|
|
|
253
294
|
```bash
|
|
@@ -272,6 +313,10 @@ names are scoped to that token, allowing independent mdmaid.desk daemons on
|
|
|
272
313
|
different ports to remain signed in at the same time. Stop the foreground
|
|
273
314
|
service with `Ctrl-C`.
|
|
274
315
|
|
|
316
|
+
Project, Space, and content type are global filters. Open the Project control
|
|
317
|
+
and type any part of a project name to narrow its choices with a
|
|
318
|
+
case-insensitive substring match, then select the project normally.
|
|
319
|
+
|
|
275
320
|
Use `--port` to select another loopback port for intentional isolated testing.
|
|
276
321
|
An advanced `--public-url` option
|
|
277
322
|
accepts HTTP or HTTPS `.localhost` origins; a direct HTTP origin must use the
|
|
@@ -328,15 +373,17 @@ node dist/cli.js tui
|
|
|
328
373
|
```
|
|
329
374
|
|
|
330
375
|
The TUI reuses the running web daemon when available, so both clients share
|
|
331
|
-
catalog
|
|
376
|
+
catalog invalidations and reading state. Keys are shown in its footer; the main flow
|
|
332
377
|
uses `j`/`k`, `Enter`, `/`, `m`, `u`, `a`, `b`, and `q`.
|
|
333
378
|
The queue starts grouped by project; press `g` to cycle through project, tag,
|
|
334
|
-
and one ordered-list view.
|
|
379
|
+
and one ordered-list view. Press `x` to cycle All and named Spaces.
|
|
335
380
|
Pending review requests add `r` for the Actions view and `y`, `c`, or `x` for
|
|
336
381
|
Approve, Request changes, or Reject. The response composer uses `Enter` for a
|
|
337
382
|
newline, `Ctrl-D` to submit, and `Esc` to cancel.
|
|
338
383
|
|
|
339
|
-
Press `c` from the terminal queue to
|
|
384
|
+
Press `c` from the terminal queue to switch between Docs and Change reviews.
|
|
385
|
+
This content mode is independent from the Actions filter, so “Waiting for you”
|
|
386
|
+
can narrow either mode.
|
|
340
387
|
Producers publish an implementation review and its mandatory decision with:
|
|
341
388
|
|
|
342
389
|
```bash
|
|
@@ -405,6 +452,11 @@ ${XDG_STATE_HOME:-~/.local/state}/mdmaid.desk/
|
|
|
405
452
|
daemon.log
|
|
406
453
|
```
|
|
407
454
|
|
|
455
|
+
The Spaces release upgrades existing catalogs from schema 8 to schema 9.
|
|
456
|
+
Before the first upgraded start, follow the verified backup and downgrade
|
|
457
|
+
procedure in [docs/RELEASING.md](docs/RELEASING.md#schema-9-backup-and-downgrade-recovery).
|
|
458
|
+
In-place downgrade is not supported.
|
|
459
|
+
|
|
408
460
|
## Target Interaction
|
|
409
461
|
|
|
410
462
|
```mermaid
|
package/dist/api-client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ContentScope } from "./domain.js";
|
|
2
|
+
import type { BulkDocumentResult, DocumentAction, DocumentImport, DocumentRegistration, HealthData, PublicDocument, PublicProject, PublicRepository, PublicReviewRequest, PublicSpace, PublicSpaceMatcher, PublicWorkspace, PublicWorkspaceReconciliation, TerminalRenderPreferences, TerminalRender, WebRender, ReviewRequestRegistration, ReviewRequestResponse, ReviewStatus, WorkspaceRegistration, WorkspaceReconciliationRequest } from "./api-types.js";
|
|
2
3
|
import type { MermaidValidationReport } from "./mermaid-validation.js";
|
|
3
4
|
export declare class DeskApiError extends Error {
|
|
4
5
|
readonly status: number;
|
|
@@ -6,8 +7,12 @@ export declare class DeskApiError extends Error {
|
|
|
6
7
|
readonly validation?: MermaidValidationReport | undefined;
|
|
7
8
|
constructor(status: number, code: string, message: string, validation?: MermaidValidationReport | undefined);
|
|
8
9
|
}
|
|
10
|
+
export declare class DaemonHealthCompatibilityError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/** @deprecated Catalog invalidations now arrive as an empty object. */
|
|
9
14
|
export interface CatalogEvent {
|
|
10
|
-
action
|
|
15
|
+
action?: string;
|
|
11
16
|
documentId?: string;
|
|
12
17
|
revision?: number;
|
|
13
18
|
reviewRequestId?: string;
|
|
@@ -21,22 +26,44 @@ export declare class DeskApiClient {
|
|
|
21
26
|
#private;
|
|
22
27
|
constructor(baseUrl: string, token: string);
|
|
23
28
|
health(signal?: AbortSignal): Promise<HealthData>;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
requireCapabilities(...required: string[]): Promise<void>;
|
|
30
|
+
listDocuments(scope?: ContentScope, filters?: {
|
|
31
|
+
archived?: boolean;
|
|
32
|
+
}): Promise<PublicDocument[]>;
|
|
33
|
+
listWorkspaces(scope?: ContentScope): Promise<PublicWorkspace[]>;
|
|
34
|
+
listProjects(scope?: ContentScope): Promise<PublicProject[]>;
|
|
35
|
+
listSpaces(): Promise<PublicSpace[]>;
|
|
36
|
+
getSpace(id: string): Promise<PublicSpace>;
|
|
37
|
+
createSpace(input: PublicSpace): Promise<PublicSpace>;
|
|
38
|
+
renameSpace(id: string, name: string): Promise<PublicSpace>;
|
|
39
|
+
replaceSpaceMatchers(id: string, matchers: PublicSpaceMatcher[]): Promise<PublicSpace>;
|
|
40
|
+
deleteSpace(id: string): Promise<{
|
|
41
|
+
id: string;
|
|
42
|
+
}>;
|
|
43
|
+
listRepositories(): Promise<PublicRepository[]>;
|
|
27
44
|
addWorkspace(input: WorkspaceRegistration): Promise<PublicWorkspace>;
|
|
45
|
+
reconcileWorkspace(sourceWorkspaceId: string, input: WorkspaceReconciliationRequest): Promise<PublicWorkspaceReconciliation>;
|
|
28
46
|
registerDocument(input: DocumentRegistration): Promise<PublicDocument>;
|
|
29
47
|
importDocument(input: DocumentImport): Promise<PublicDocument>;
|
|
30
48
|
listReviewRequests(filters?: {
|
|
31
49
|
documentId?: string;
|
|
32
50
|
status?: ReviewStatus;
|
|
33
|
-
}): Promise<PublicReviewRequest[]>;
|
|
34
|
-
getReviewRequest(id: string): Promise<PublicReviewRequest>;
|
|
35
|
-
createReviewRequest(input: ReviewRequestRegistration): Promise<PublicReviewRequest>;
|
|
36
|
-
respondToReviewRequest(id: string, input: ReviewRequestResponse): Promise<PublicReviewRequest>;
|
|
37
|
-
renderDocument(id: string, target: "terminal", width?: number, preferences?: TerminalRenderPreferences): Promise<TerminalRender>;
|
|
38
|
-
renderDocument(id: string, target: "web", width?: number): Promise<WebRender>;
|
|
39
|
-
act(id: string, action: DocumentAction): Promise<PublicDocument>;
|
|
51
|
+
}, scope?: ContentScope): Promise<PublicReviewRequest[]>;
|
|
52
|
+
getReviewRequest(id: string, scope?: ContentScope): Promise<PublicReviewRequest>;
|
|
53
|
+
createReviewRequest(input: ReviewRequestRegistration, scope?: ContentScope): Promise<PublicReviewRequest>;
|
|
54
|
+
respondToReviewRequest(id: string, input: ReviewRequestResponse, scope?: ContentScope): Promise<PublicReviewRequest>;
|
|
55
|
+
renderDocument(id: string, target: "terminal", width?: number, preferences?: TerminalRenderPreferences, scope?: ContentScope): Promise<TerminalRender>;
|
|
56
|
+
renderDocument(id: string, target: "web", width?: number, preferences?: undefined, scope?: ContentScope): Promise<WebRender>;
|
|
57
|
+
act(id: string, action: DocumentAction, scope?: ContentScope): Promise<PublicDocument>;
|
|
58
|
+
bulkAct(action: "archive" | "restore", ids: string[], scope?: ContentScope): Promise<Extract<BulkDocumentResult, {
|
|
59
|
+
documents: PublicDocument[];
|
|
60
|
+
}>>;
|
|
61
|
+
bulkAct(action: "purge", ids: string[], scope?: ContentScope): Promise<Extract<BulkDocumentResult, {
|
|
62
|
+
action: "purge";
|
|
63
|
+
}>>;
|
|
64
|
+
purgeDocument(id: string, scope?: ContentScope): Promise<string>;
|
|
65
|
+
getDocument(id: string, scope?: ContentScope): Promise<PublicDocument>;
|
|
66
|
+
setDocumentTags(id: string, tags: string[], scope?: ContentScope): Promise<PublicDocument>;
|
|
40
67
|
subscribeCatalog(listener: (event: CatalogEvent) => void, options?: CatalogSubscriptionOptions): Promise<void>;
|
|
41
68
|
}
|
|
42
69
|
export declare function isPublicReviewRequest(value: unknown): value is PublicReviewRequest;
|
package/dist/api-client.js
CHANGED
|
@@ -12,6 +12,12 @@ export class DeskApiError extends Error {
|
|
|
12
12
|
this.name = "DeskApiError";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
export class DaemonHealthCompatibilityError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "DaemonHealthCompatibilityError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
15
21
|
export class DeskApiClient {
|
|
16
22
|
#baseUrl;
|
|
17
23
|
#token;
|
|
@@ -34,32 +40,122 @@ export class DeskApiClient {
|
|
|
34
40
|
authenticated: false,
|
|
35
41
|
...(signal === undefined ? {} : { signal }),
|
|
36
42
|
});
|
|
37
|
-
if (!
|
|
43
|
+
if (!isCoreHealth(value)) {
|
|
38
44
|
throw new Error("Daemon returned an invalid health response");
|
|
39
45
|
}
|
|
40
|
-
|
|
46
|
+
if (value.capabilities !== undefined &&
|
|
47
|
+
(!Array.isArray(value.capabilities) ||
|
|
48
|
+
!value.capabilities.every((capability) => typeof capability === "string"))) {
|
|
49
|
+
throw new DaemonHealthCompatibilityError("The running mdmaid.desk daemon has incompatible capabilities; restart it after upgrading");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
service: "mdmaid.desk",
|
|
53
|
+
status: "ok",
|
|
54
|
+
version: value.version,
|
|
55
|
+
...(value.capabilities === undefined
|
|
56
|
+
? {}
|
|
57
|
+
: { capabilities: [...new Set(value.capabilities)] }),
|
|
58
|
+
};
|
|
41
59
|
}
|
|
42
|
-
async
|
|
43
|
-
const
|
|
60
|
+
async requireCapabilities(...required) {
|
|
61
|
+
const health = await this.health();
|
|
62
|
+
const capabilities = new Set(health.capabilities ?? []);
|
|
63
|
+
const missing = required.filter((capability) => !capabilities.has(capability));
|
|
64
|
+
if (missing.length > 0) {
|
|
65
|
+
throw new DaemonHealthCompatibilityError(`The running mdmaid.desk daemon does not support ${missing.join(", ")}; restart it after upgrading`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async listDocuments(scope = {}, filters = {}) {
|
|
69
|
+
const query = new URLSearchParams();
|
|
70
|
+
if (scope.spaceId !== undefined) {
|
|
71
|
+
assertSpaceId(scope.spaceId);
|
|
72
|
+
query.set("space", scope.spaceId);
|
|
73
|
+
}
|
|
74
|
+
if (filters.archived === true) {
|
|
75
|
+
query.set("archived", "true");
|
|
76
|
+
}
|
|
77
|
+
const suffix = query.size === 0 ? "" : `?${query.toString()}`;
|
|
78
|
+
const value = await this.#request(`/api/v1/documents${suffix}`);
|
|
44
79
|
if (!Array.isArray(value) || !value.every(isPublicDocument)) {
|
|
45
80
|
throw new Error("Daemon returned an invalid document list");
|
|
46
81
|
}
|
|
47
82
|
return value;
|
|
48
83
|
}
|
|
49
|
-
async listWorkspaces() {
|
|
50
|
-
const value = await this.#request("/api/v1/workspaces");
|
|
84
|
+
async listWorkspaces(scope = {}) {
|
|
85
|
+
const value = await this.#request(withScope("/api/v1/workspaces", scope));
|
|
51
86
|
if (!Array.isArray(value) || !value.every(isPublicWorkspace)) {
|
|
52
87
|
throw new Error("Daemon returned an invalid workspace list");
|
|
53
88
|
}
|
|
54
89
|
return value;
|
|
55
90
|
}
|
|
56
|
-
async listProjects() {
|
|
57
|
-
const value = await this.#request("/api/v1/projects");
|
|
91
|
+
async listProjects(scope = {}) {
|
|
92
|
+
const value = await this.#request(withScope("/api/v1/projects", scope));
|
|
58
93
|
if (!Array.isArray(value) || !value.every(isPublicProject)) {
|
|
59
94
|
throw new Error("Daemon returned an invalid project list");
|
|
60
95
|
}
|
|
61
96
|
return value;
|
|
62
97
|
}
|
|
98
|
+
async listSpaces() {
|
|
99
|
+
const value = await this.#request("/api/v1/spaces");
|
|
100
|
+
if (!Array.isArray(value) || !value.every(isPublicSpace)) {
|
|
101
|
+
throw new Error("Daemon returned an invalid Space list");
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
async getSpace(id) {
|
|
106
|
+
assertSpaceId(id);
|
|
107
|
+
const value = await this.#request(`/api/v1/spaces/${encodeURIComponent(id)}`);
|
|
108
|
+
if (!isPublicSpace(value)) {
|
|
109
|
+
throw new Error("Daemon returned an invalid Space");
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
async createSpace(input) {
|
|
114
|
+
const value = await this.#request("/api/v1/spaces", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: input,
|
|
117
|
+
});
|
|
118
|
+
if (!isPublicSpace(value)) {
|
|
119
|
+
throw new Error("Daemon returned an invalid Space");
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
async renameSpace(id, name) {
|
|
124
|
+
assertSpaceId(id);
|
|
125
|
+
const value = await this.#request(`/api/v1/spaces/${encodeURIComponent(id)}`, {
|
|
126
|
+
method: "PATCH",
|
|
127
|
+
body: { name },
|
|
128
|
+
});
|
|
129
|
+
if (!isPublicSpace(value)) {
|
|
130
|
+
throw new Error("Daemon returned an invalid Space");
|
|
131
|
+
}
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
async replaceSpaceMatchers(id, matchers) {
|
|
135
|
+
assertSpaceId(id);
|
|
136
|
+
const value = await this.#request(`/api/v1/spaces/${encodeURIComponent(id)}/matchers`, { method: "PUT", body: { matchers } });
|
|
137
|
+
if (!isPublicSpace(value)) {
|
|
138
|
+
throw new Error("Daemon returned an invalid Space");
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
async deleteSpace(id) {
|
|
143
|
+
assertSpaceId(id);
|
|
144
|
+
const value = await this.#request(`/api/v1/spaces/${encodeURIComponent(id)}`, {
|
|
145
|
+
method: "DELETE",
|
|
146
|
+
});
|
|
147
|
+
if (!isRecord(value) || Object.keys(value).length !== 1 || value.id !== id) {
|
|
148
|
+
throw new Error("Daemon returned an invalid Space deletion response");
|
|
149
|
+
}
|
|
150
|
+
return { id };
|
|
151
|
+
}
|
|
152
|
+
async listRepositories() {
|
|
153
|
+
const value = await this.#request("/api/v1/repositories");
|
|
154
|
+
if (!Array.isArray(value) || !value.every(isPublicRepository)) {
|
|
155
|
+
throw new Error("Daemon returned an invalid repository list");
|
|
156
|
+
}
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
63
159
|
async addWorkspace(input) {
|
|
64
160
|
const value = await this.#request("/api/v1/workspaces", {
|
|
65
161
|
method: "POST",
|
|
@@ -70,6 +166,14 @@ export class DeskApiClient {
|
|
|
70
166
|
}
|
|
71
167
|
return value;
|
|
72
168
|
}
|
|
169
|
+
async reconcileWorkspace(sourceWorkspaceId, input) {
|
|
170
|
+
assertWorkspaceId(sourceWorkspaceId);
|
|
171
|
+
const value = await this.#request(`/api/v1/workspaces/${encodeURIComponent(sourceWorkspaceId)}/reconcile`, { method: "POST", body: input });
|
|
172
|
+
if (!isPublicWorkspaceReconciliation(value)) {
|
|
173
|
+
throw new Error("Daemon returned an invalid workspace reconciliation");
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
73
177
|
async registerDocument(input) {
|
|
74
178
|
const value = await this.#request("/api/v1/documents", {
|
|
75
179
|
method: "POST",
|
|
@@ -90,7 +194,7 @@ export class DeskApiClient {
|
|
|
90
194
|
}
|
|
91
195
|
return value;
|
|
92
196
|
}
|
|
93
|
-
async listReviewRequests(filters = {}) {
|
|
197
|
+
async listReviewRequests(filters = {}, scope = {}) {
|
|
94
198
|
const query = new URLSearchParams();
|
|
95
199
|
if (filters.documentId !== undefined) {
|
|
96
200
|
assertDocumentId(filters.documentId);
|
|
@@ -102,6 +206,7 @@ export class DeskApiClient {
|
|
|
102
206
|
}
|
|
103
207
|
query.set("status", filters.status);
|
|
104
208
|
}
|
|
209
|
+
addScope(query, scope);
|
|
105
210
|
const suffix = query.size === 0 ? "" : `?${query.toString()}`;
|
|
106
211
|
const value = await this.#request(`/api/v1/review-requests${suffix}`);
|
|
107
212
|
if (!Array.isArray(value) || !value.every(isPublicReviewRequest)) {
|
|
@@ -109,16 +214,16 @@ export class DeskApiClient {
|
|
|
109
214
|
}
|
|
110
215
|
return value;
|
|
111
216
|
}
|
|
112
|
-
async getReviewRequest(id) {
|
|
217
|
+
async getReviewRequest(id, scope = {}) {
|
|
113
218
|
assertReviewRequestId(id);
|
|
114
|
-
const value = await this.#request(`/api/v1/review-requests/${encodeURIComponent(id)}
|
|
219
|
+
const value = await this.#request(withScope(`/api/v1/review-requests/${encodeURIComponent(id)}`, scope));
|
|
115
220
|
if (!isPublicReviewRequest(value)) {
|
|
116
221
|
throw new Error("Daemon returned an invalid review request");
|
|
117
222
|
}
|
|
118
223
|
return value;
|
|
119
224
|
}
|
|
120
|
-
async createReviewRequest(input) {
|
|
121
|
-
const value = await this.#request("/api/v1/review-requests", {
|
|
225
|
+
async createReviewRequest(input, scope = {}) {
|
|
226
|
+
const value = await this.#request(withScope("/api/v1/review-requests", scope), {
|
|
122
227
|
method: "POST",
|
|
123
228
|
body: input,
|
|
124
229
|
});
|
|
@@ -127,15 +232,15 @@ export class DeskApiClient {
|
|
|
127
232
|
}
|
|
128
233
|
return value;
|
|
129
234
|
}
|
|
130
|
-
async respondToReviewRequest(id, input) {
|
|
235
|
+
async respondToReviewRequest(id, input, scope = {}) {
|
|
131
236
|
assertReviewRequestId(id);
|
|
132
|
-
const value = await this.#request(`/api/v1/review-requests/${encodeURIComponent(id)}/respond`, { method: "POST", body: input });
|
|
237
|
+
const value = await this.#request(withScope(`/api/v1/review-requests/${encodeURIComponent(id)}/respond`, scope), { method: "POST", body: input });
|
|
133
238
|
if (!isPublicReviewRequest(value)) {
|
|
134
239
|
throw new Error("Daemon returned an invalid review request");
|
|
135
240
|
}
|
|
136
241
|
return value;
|
|
137
242
|
}
|
|
138
|
-
async renderDocument(id, target, width = 100, preferences = {}) {
|
|
243
|
+
async renderDocument(id, target, width = 100, preferences = {}, scope = {}) {
|
|
139
244
|
assertDocumentId(id);
|
|
140
245
|
if (!Number.isSafeInteger(width) || width < 20 || width > 1_000) {
|
|
141
246
|
throw new Error("render width must be an integer between 20 and 1000");
|
|
@@ -150,6 +255,7 @@ export class DeskApiClient {
|
|
|
150
255
|
query.set("unicode", String(preferences.unicode));
|
|
151
256
|
}
|
|
152
257
|
}
|
|
258
|
+
addScope(query, scope);
|
|
153
259
|
const value = await this.#request(`/api/v1/documents/${encodeURIComponent(id)}/render?${query.toString()}`);
|
|
154
260
|
if (target === "terminal" && isTerminalRender(value)) {
|
|
155
261
|
return value;
|
|
@@ -159,9 +265,41 @@ export class DeskApiClient {
|
|
|
159
265
|
}
|
|
160
266
|
throw new Error("Daemon returned an invalid render response");
|
|
161
267
|
}
|
|
162
|
-
async act(id, action) {
|
|
268
|
+
async act(id, action, scope = {}) {
|
|
163
269
|
assertDocumentId(id);
|
|
164
|
-
const value = await this.#request(`/api/v1/documents/${encodeURIComponent(id)}/${action}`, { method: "POST" });
|
|
270
|
+
const value = await this.#request(withScope(`/api/v1/documents/${encodeURIComponent(id)}/${action}`, scope), { method: "POST" });
|
|
271
|
+
if (!isPublicDocument(value)) {
|
|
272
|
+
throw new Error("Daemon returned an invalid document response");
|
|
273
|
+
}
|
|
274
|
+
return value;
|
|
275
|
+
}
|
|
276
|
+
async bulkAct(action, ids, scope = {}) {
|
|
277
|
+
assertDocumentIds(ids);
|
|
278
|
+
const value = await this.#request(withScope("/api/v1/documents/bulk", scope), { method: "POST", body: { action, ids } });
|
|
279
|
+
if (!isBulkDocumentResult(value, action)) {
|
|
280
|
+
throw new Error("Daemon returned an invalid bulk document response");
|
|
281
|
+
}
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
async purgeDocument(id, scope = {}) {
|
|
285
|
+
assertDocumentId(id);
|
|
286
|
+
const value = await this.#request(withScope(`/api/v1/documents/${encodeURIComponent(id)}/purge`, scope), { method: "POST" });
|
|
287
|
+
if (!isRecord(value) || value.purgedId !== id) {
|
|
288
|
+
throw new Error("Daemon returned an invalid purge response");
|
|
289
|
+
}
|
|
290
|
+
return value.purgedId;
|
|
291
|
+
}
|
|
292
|
+
async getDocument(id, scope = {}) {
|
|
293
|
+
assertDocumentId(id);
|
|
294
|
+
const value = await this.#request(withScope(`/api/v1/documents/${encodeURIComponent(id)}`, scope));
|
|
295
|
+
if (!isPublicDocument(value)) {
|
|
296
|
+
throw new Error("Daemon returned an invalid document response");
|
|
297
|
+
}
|
|
298
|
+
return value;
|
|
299
|
+
}
|
|
300
|
+
async setDocumentTags(id, tags, scope = {}) {
|
|
301
|
+
assertDocumentId(id);
|
|
302
|
+
const value = await this.#request(withScope(`/api/v1/documents/${encodeURIComponent(id)}/tags`, scope), { method: "PUT", body: { tags } });
|
|
165
303
|
if (!isPublicDocument(value)) {
|
|
166
304
|
throw new Error("Daemon returned an invalid document response");
|
|
167
305
|
}
|
|
@@ -250,12 +388,39 @@ export class DeskApiClient {
|
|
|
250
388
|
return body.data;
|
|
251
389
|
}
|
|
252
390
|
}
|
|
253
|
-
function
|
|
391
|
+
function isCoreHealth(value) {
|
|
254
392
|
return (isRecord(value) &&
|
|
255
393
|
value.service === "mdmaid.desk" &&
|
|
256
394
|
value.status === "ok" &&
|
|
257
395
|
typeof value.version === "number");
|
|
258
396
|
}
|
|
397
|
+
function isPublicSpaceMatcher(value) {
|
|
398
|
+
return isRecord(value) &&
|
|
399
|
+
Object.keys(value).length === 2 &&
|
|
400
|
+
(value.kind === "repository" ||
|
|
401
|
+
value.kind === "repository-namespace" ||
|
|
402
|
+
value.kind === "tag") &&
|
|
403
|
+
typeof value.value === "string";
|
|
404
|
+
}
|
|
405
|
+
function isPublicSpace(value) {
|
|
406
|
+
return isRecord(value) &&
|
|
407
|
+
Object.keys(value).length === 3 &&
|
|
408
|
+
typeof value.id === "string" &&
|
|
409
|
+
/^[a-z0-9][a-z0-9-]{0,63}$/.test(value.id) &&
|
|
410
|
+
typeof value.name === "string" &&
|
|
411
|
+
Array.isArray(value.matchers) &&
|
|
412
|
+
value.matchers.length > 0 &&
|
|
413
|
+
value.matchers.every(isPublicSpaceMatcher);
|
|
414
|
+
}
|
|
415
|
+
function isPublicRepository(value) {
|
|
416
|
+
return isRecord(value) &&
|
|
417
|
+
Object.keys(value).length === 4 &&
|
|
418
|
+
typeof value.key === "string" &&
|
|
419
|
+
typeof value.name === "string" &&
|
|
420
|
+
Array.isArray(value.workspaceIds) &&
|
|
421
|
+
value.workspaceIds.every((id) => typeof id === "string") &&
|
|
422
|
+
(value.kind === "remote" || value.kind === "local");
|
|
423
|
+
}
|
|
259
424
|
function isPublicWorkspace(value) {
|
|
260
425
|
return (isRecord(value) &&
|
|
261
426
|
typeof value.id === "string" &&
|
|
@@ -263,6 +428,29 @@ function isPublicWorkspace(value) {
|
|
|
263
428
|
typeof value.documentCount === "number" &&
|
|
264
429
|
typeof value.route === "string");
|
|
265
430
|
}
|
|
431
|
+
function isPublicWorkspaceReconciliation(value) {
|
|
432
|
+
return isRecord(value) &&
|
|
433
|
+
Object.keys(value).length === 7 &&
|
|
434
|
+
typeof value.sourceWorkspaceId === "string" &&
|
|
435
|
+
typeof value.targetWorkspaceId === "string" &&
|
|
436
|
+
typeof value.applied === "boolean" &&
|
|
437
|
+
Array.isArray(value.movedDocumentIds) &&
|
|
438
|
+
value.movedDocumentIds.every((id) => typeof id === "string") &&
|
|
439
|
+
Array.isArray(value.discardedDocumentIds) &&
|
|
440
|
+
value.discardedDocumentIds.every((id) => typeof id === "string") &&
|
|
441
|
+
Array.isArray(value.blockingConflicts) &&
|
|
442
|
+
value.blockingConflicts.every(isPublicWorkspaceReconciliationConflict) &&
|
|
443
|
+
typeof value.reviewRequestCount === "number" &&
|
|
444
|
+
Number.isSafeInteger(value.reviewRequestCount) &&
|
|
445
|
+
value.reviewRequestCount >= 0;
|
|
446
|
+
}
|
|
447
|
+
function isPublicWorkspaceReconciliationConflict(value) {
|
|
448
|
+
return isRecord(value) &&
|
|
449
|
+
Object.keys(value).length === 3 &&
|
|
450
|
+
typeof value.sourceDocumentId === "string" &&
|
|
451
|
+
typeof value.targetDocumentId === "string" &&
|
|
452
|
+
(value.reason === "path-conflict" || value.reason === "review-history");
|
|
453
|
+
}
|
|
266
454
|
function isPublicProject(value) {
|
|
267
455
|
return (isRecord(value) &&
|
|
268
456
|
typeof value.id === "string" &&
|
|
@@ -303,6 +491,15 @@ function isPublicDocument(value) {
|
|
|
303
491
|
typeof value.updatedAt === "string" &&
|
|
304
492
|
typeof value.route === "string");
|
|
305
493
|
}
|
|
494
|
+
function isBulkDocumentResult(value, action) {
|
|
495
|
+
if (!isRecord(value) || value.action !== action) {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
return action === "purge"
|
|
499
|
+
? Array.isArray(value.purgedIds) &&
|
|
500
|
+
value.purgedIds.every((id) => typeof id === "string" && isDocumentId(id))
|
|
501
|
+
: Array.isArray(value.documents) && value.documents.every(isPublicDocument);
|
|
502
|
+
}
|
|
306
503
|
export function isPublicReviewRequest(value) {
|
|
307
504
|
if (!isRecord(value)) {
|
|
308
505
|
return false;
|
|
@@ -440,7 +637,7 @@ function dispatchEventBlock(block, listener, onReady) {
|
|
|
440
637
|
onReady?.();
|
|
441
638
|
return;
|
|
442
639
|
}
|
|
443
|
-
if (type !== "catalog") {
|
|
640
|
+
if (type !== "catalog-invalidated") {
|
|
444
641
|
return;
|
|
445
642
|
}
|
|
446
643
|
let value;
|
|
@@ -450,55 +647,10 @@ function dispatchEventBlock(block, listener, onReady) {
|
|
|
450
647
|
catch {
|
|
451
648
|
throw new Error("Daemon returned an invalid catalog event");
|
|
452
649
|
}
|
|
453
|
-
|
|
454
|
-
(value.action === "source-changed" ||
|
|
455
|
-
value.action === "source-missing" ||
|
|
456
|
-
value.action === "source-restored");
|
|
457
|
-
if (!isRecord(value) ||
|
|
458
|
-
!Object.keys(value).every((key) => [
|
|
459
|
-
"action",
|
|
460
|
-
"documentId",
|
|
461
|
-
"revision",
|
|
462
|
-
"reviewRequestId",
|
|
463
|
-
"workspaceId",
|
|
464
|
-
].includes(key)) ||
|
|
465
|
-
typeof value.action !== "string" ||
|
|
466
|
-
(value.documentId !== undefined &&
|
|
467
|
-
(typeof value.documentId !== "string" ||
|
|
468
|
-
!/^doc-[a-f0-9]{20}$/.test(value.documentId))) ||
|
|
469
|
-
(value.workspaceId !== undefined &&
|
|
470
|
-
(typeof value.workspaceId !== "string" ||
|
|
471
|
-
!/^[a-z0-9][a-z0-9-]{0,63}$/.test(value.workspaceId))) ||
|
|
472
|
-
(value.reviewRequestId !== undefined &&
|
|
473
|
-
(typeof value.reviewRequestId !== "string" ||
|
|
474
|
-
!/^review-[a-f0-9]{20}$/.test(value.reviewRequestId))) ||
|
|
475
|
-
(value.revision !== undefined &&
|
|
476
|
-
(typeof value.revision !== "number" ||
|
|
477
|
-
!Number.isSafeInteger(value.revision) ||
|
|
478
|
-
value.revision < 1)) ||
|
|
479
|
-
(liveSourceAction &&
|
|
480
|
-
(value.documentId === undefined || value.revision === undefined)) ||
|
|
481
|
-
(!liveSourceAction && value.revision !== undefined) ||
|
|
482
|
-
(value.documentId === undefined &&
|
|
483
|
-
value.workspaceId === undefined &&
|
|
484
|
-
value.reviewRequestId === undefined)) {
|
|
650
|
+
if (!isRecord(value) || Object.keys(value).length !== 0) {
|
|
485
651
|
throw new Error("Daemon returned an invalid catalog event");
|
|
486
652
|
}
|
|
487
|
-
listener({
|
|
488
|
-
action: value.action,
|
|
489
|
-
...(value.documentId === undefined
|
|
490
|
-
? {}
|
|
491
|
-
: { documentId: value.documentId }),
|
|
492
|
-
...(value.revision === undefined
|
|
493
|
-
? {}
|
|
494
|
-
: { revision: value.revision }),
|
|
495
|
-
...(value.workspaceId === undefined
|
|
496
|
-
? {}
|
|
497
|
-
: { workspaceId: value.workspaceId }),
|
|
498
|
-
...(value.reviewRequestId === undefined
|
|
499
|
-
? {}
|
|
500
|
-
: { reviewRequestId: value.reviewRequestId }),
|
|
501
|
-
});
|
|
653
|
+
listener({});
|
|
502
654
|
}
|
|
503
655
|
function optionalString(value) {
|
|
504
656
|
return value === undefined || typeof value === "string";
|
|
@@ -515,6 +667,18 @@ function nullableInteger(value) {
|
|
|
515
667
|
function isRecord(value) {
|
|
516
668
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
517
669
|
}
|
|
670
|
+
function assertDocumentIds(ids) {
|
|
671
|
+
if (!Array.isArray(ids) ||
|
|
672
|
+
ids.length === 0 ||
|
|
673
|
+
ids.length > 500 ||
|
|
674
|
+
!ids.every((id) => typeof id === "string" && isDocumentId(id)) ||
|
|
675
|
+
new Set(ids).size !== ids.length) {
|
|
676
|
+
throw new Error("document ids must contain between 1 and 500 unique ids");
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function isDocumentId(value) {
|
|
680
|
+
return /^doc-[a-f0-9]{20}$/.test(value);
|
|
681
|
+
}
|
|
518
682
|
function assertDocumentId(id) {
|
|
519
683
|
if (!/^doc-[a-f0-9]{20}$/.test(id)) {
|
|
520
684
|
throw new Error("invalid document id");
|
|
@@ -525,4 +689,30 @@ function assertReviewRequestId(id) {
|
|
|
525
689
|
throw new Error("invalid review request id");
|
|
526
690
|
}
|
|
527
691
|
}
|
|
692
|
+
function assertSpaceId(id) {
|
|
693
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(id)) {
|
|
694
|
+
throw new Error("invalid Space id");
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function assertWorkspaceId(id) {
|
|
698
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(id)) {
|
|
699
|
+
throw new Error("invalid workspace id");
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function addScope(query, scope) {
|
|
703
|
+
if (!isRecord(scope) ||
|
|
704
|
+
!Object.keys(scope).every((key) => key === "spaceId") ||
|
|
705
|
+
(scope.spaceId !== undefined && typeof scope.spaceId !== "string")) {
|
|
706
|
+
throw new Error("invalid content scope");
|
|
707
|
+
}
|
|
708
|
+
if (scope.spaceId !== undefined) {
|
|
709
|
+
assertSpaceId(scope.spaceId);
|
|
710
|
+
query.set("space", scope.spaceId);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function withScope(path, scope) {
|
|
714
|
+
const url = new URL(path, "http://mdmaid.desk.localhost");
|
|
715
|
+
addScope(url.searchParams, scope);
|
|
716
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
717
|
+
}
|
|
528
718
|
//# sourceMappingURL=api-client.js.map
|