@withpica/mcp-sdk 3.23.1 → 3.25.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/CHANGELOG.md +75 -0
- package/dist/index.d.ts +695 -68
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +604 -127
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight PICA SDK client for MCP server
|
|
5
|
+
* Uses the PICA API directly without external dependencies
|
|
6
|
+
*/
|
|
7
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
8
|
// --- Request infrastructure ---
|
|
4
9
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
10
|
const LONG_TIMEOUT_MS = 120_000;
|
|
@@ -32,6 +37,98 @@ export class ApiError extends Error {
|
|
|
32
37
|
this.name = "ApiError";
|
|
33
38
|
}
|
|
34
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* ADR-333 D7 — acting for a client catalogue, one call at a time.
|
|
42
|
+
*
|
|
43
|
+
* An organisation's assistant names the client catalogue on each tool call
|
|
44
|
+
* (the optional `catalogue` argument). The MCP server runs that call inside
|
|
45
|
+
* `withCatalogue(id, …)`, and every request the call makes carries
|
|
46
|
+
* `x-pica-catalogue: <id>`. The app resolves it through the relationship on
|
|
47
|
+
* every request (PR 1.3) and answers with `x-pica-catalogue-resolved: <id>`.
|
|
48
|
+
*
|
|
49
|
+
* A per-call context, not a field on the client: the stdio server shares one
|
|
50
|
+
* client across concurrent tool calls, so a field would leak one call's
|
|
51
|
+
* catalogue into another's requests.
|
|
52
|
+
*
|
|
53
|
+
* Fails closed: a 2xx answer without a matching echo means the route did not
|
|
54
|
+
* act for that catalogue (it may have used the caller's own), so the call
|
|
55
|
+
* throws `CatalogueNotHonouredError` instead of reporting success. This is the
|
|
56
|
+
* client's check only; stopping such a route from writing at all is the app's
|
|
57
|
+
* job (PR 1.3 refuses a catalogue header it did not resolve).
|
|
58
|
+
*/
|
|
59
|
+
export const CATALOGUE_HEADER = "x-pica-catalogue";
|
|
60
|
+
export const CATALOGUE_RESOLVED_HEADER = "x-pica-catalogue-resolved";
|
|
61
|
+
const catalogueContext = new AsyncLocalStorage();
|
|
62
|
+
/** Run `fn` with every SDK request in it acting for `catalogueId`. */
|
|
63
|
+
export function withCatalogue(catalogueId, fn) {
|
|
64
|
+
return catalogueId
|
|
65
|
+
? runInCatalogue(catalogueId, fn).then((r) => r.value)
|
|
66
|
+
: fn();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* `withCatalogue`, also returning the call's report. The report is the one
|
|
70
|
+
* the call's requests wrote to; it is complete once `fn` settles.
|
|
71
|
+
*/
|
|
72
|
+
export async function runInCatalogue(catalogueId, fn) {
|
|
73
|
+
const report = {
|
|
74
|
+
catalogueId,
|
|
75
|
+
answered: 0,
|
|
76
|
+
refused: [],
|
|
77
|
+
};
|
|
78
|
+
const value = await catalogueContext.run(report, fn);
|
|
79
|
+
return { value, report };
|
|
80
|
+
}
|
|
81
|
+
/** The catalogue the current call acts for, if any. */
|
|
82
|
+
export function currentCatalogue() {
|
|
83
|
+
return catalogueContext.getStore()?.catalogueId;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Run `fn` in the current client catalogue (its requests still name it), but
|
|
87
|
+
* leave its requests off the current call's report: for a check PICA makes
|
|
88
|
+
* for itself (the billing gate reading the client's plan), which must not
|
|
89
|
+
* read as the tool's own work having been answered (the MCP server judges a
|
|
90
|
+
* call "every part refused" by that report).
|
|
91
|
+
*/
|
|
92
|
+
export function asideFromReport(fn) {
|
|
93
|
+
const current = catalogueContext.getStore();
|
|
94
|
+
if (!current)
|
|
95
|
+
return fn();
|
|
96
|
+
return catalogueContext.run({ catalogueId: current.catalogueId, answered: 0, refused: [] }, fn);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Record a relationship refusal on the current call's report (no-op outside
|
|
100
|
+
* one). Every client-catalogue refusal, from the proxy or a route, answers
|
|
101
|
+
* `{ success: false, error: { code, message, details: { reason_code } } }`
|
|
102
|
+
* (catalogueRefusalBody, lib/auth/relationship-refused-error.ts; pinned by
|
|
103
|
+
* lib/auth/__tests__/catalogue-refusal-shape.test.ts).
|
|
104
|
+
*/
|
|
105
|
+
function recordCatalogueRefusal(url, body) {
|
|
106
|
+
const report = catalogueContext.getStore();
|
|
107
|
+
if (!report)
|
|
108
|
+
return;
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(body);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (parsed?.error?.code !== "RELATIONSHIP_REFUSED")
|
|
117
|
+
return;
|
|
118
|
+
const reason = parsed.error.details?.reason_code;
|
|
119
|
+
report.refused.push({
|
|
120
|
+
path: new URL(url).pathname,
|
|
121
|
+
reason: typeof reason === "string" ? reason : null,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
export class CatalogueNotHonouredError extends ApiError {
|
|
125
|
+
catalogueId;
|
|
126
|
+
constructor(catalogueId, path) {
|
|
127
|
+
super(`This action can't be done for a client's catalogue yet, so nothing was done for catalogue ${catalogueId} (${path}).`, 0, false);
|
|
128
|
+
this.catalogueId = catalogueId;
|
|
129
|
+
this.name = "CatalogueNotHonouredError";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
35
132
|
/**
|
|
36
133
|
* Thrown when the API responds 404 — the entity at the given id either
|
|
37
134
|
* doesn't exist or doesn't belong to the caller's org. Distinguished from
|
|
@@ -54,6 +151,14 @@ function getTimeoutForPath(path) {
|
|
|
54
151
|
? LONG_TIMEOUT_MS
|
|
55
152
|
: DEFAULT_TIMEOUT_MS;
|
|
56
153
|
}
|
|
154
|
+
/** The `offset` query parameter of a request path (0 when absent). */
|
|
155
|
+
function offsetOf(path) {
|
|
156
|
+
const q = path.indexOf("?");
|
|
157
|
+
if (q === -1)
|
|
158
|
+
return 0;
|
|
159
|
+
const n = Number(new URLSearchParams(path.slice(q + 1)).get("offset"));
|
|
160
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
161
|
+
}
|
|
57
162
|
function sleep(ms) {
|
|
58
163
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
164
|
}
|
|
@@ -98,9 +203,30 @@ class BaseResource {
|
|
|
98
203
|
const timingOn = typeof process !== "undefined" && process.env?.PICA_SDK_TIMING === "1";
|
|
99
204
|
const t0 = timingOn ? Date.now() : 0;
|
|
100
205
|
let statusForTiming = 0;
|
|
206
|
+
const catalogueId = currentCatalogue();
|
|
207
|
+
const headers = catalogueId
|
|
208
|
+
? {
|
|
209
|
+
...init.headers,
|
|
210
|
+
[CATALOGUE_HEADER]: catalogueId,
|
|
211
|
+
}
|
|
212
|
+
: init.headers;
|
|
101
213
|
try {
|
|
102
|
-
const res = await fetch(url, {
|
|
214
|
+
const res = await fetch(url, {
|
|
215
|
+
...init,
|
|
216
|
+
headers,
|
|
217
|
+
signal: controller.signal,
|
|
218
|
+
});
|
|
103
219
|
statusForTiming = res.status;
|
|
220
|
+
if (catalogueId &&
|
|
221
|
+
res.ok &&
|
|
222
|
+
res.headers.get(CATALOGUE_RESOLVED_HEADER) !== catalogueId) {
|
|
223
|
+
throw new CatalogueNotHonouredError(catalogueId, new URL(url).pathname);
|
|
224
|
+
}
|
|
225
|
+
if (catalogueId && res.ok) {
|
|
226
|
+
const report = catalogueContext.getStore();
|
|
227
|
+
if (report)
|
|
228
|
+
report.answered += 1;
|
|
229
|
+
}
|
|
104
230
|
return res;
|
|
105
231
|
}
|
|
106
232
|
catch (err) {
|
|
@@ -134,6 +260,7 @@ class BaseResource {
|
|
|
134
260
|
if (response.ok)
|
|
135
261
|
return response;
|
|
136
262
|
const errorText = await response.text();
|
|
263
|
+
recordCatalogueRefusal(url, errorText);
|
|
137
264
|
// Auth errors — fail fast, never retry
|
|
138
265
|
if (response.status === 401 || response.status === 403) {
|
|
139
266
|
throw new ApiError(`Authentication failed: ${response.status} ${errorText}`, response.status, false);
|
|
@@ -312,8 +439,18 @@ class BaseResource {
|
|
|
312
439
|
// `total`/`hasMore` (e.g. GET /admin/recordings/search, ADR unified
|
|
313
440
|
// catalog search). Check nested first, then flat, then fall back to
|
|
314
441
|
// `count` / items.length so untouched callers keep their prior behaviour.
|
|
315
|
-
|
|
316
|
-
|
|
442
|
+
// An explicit `pagination.total: null` means the server does not know
|
|
443
|
+
// the total — keep it null rather than falling through to the page
|
|
444
|
+
// length, which would read as "that's all of them".
|
|
445
|
+
const total = json.pagination && json.pagination.total === null
|
|
446
|
+
? null
|
|
447
|
+
: (json.pagination?.total ?? json.total ?? json.count ?? items.length);
|
|
448
|
+
// With no server-side hasMore, the answer is offset + this page < total.
|
|
449
|
+
// `total > items.length` alone ignored the offset, so every LAST page of
|
|
450
|
+
// a multi-page result said "more" (row-cap T3).
|
|
451
|
+
const hasMore = json.pagination?.hasMore ??
|
|
452
|
+
json.hasMore ??
|
|
453
|
+
(total !== null && offsetOf(path) + items.length < total);
|
|
317
454
|
// Carry the server's zero-result guidance through — silently dropping it
|
|
318
455
|
// here left agents staring at a bare empty list (ops_issue 689a8e9a).
|
|
319
456
|
const recovery_hint = typeof json.recovery_hint === "string" ? json.recovery_hint : undefined;
|
|
@@ -406,6 +543,15 @@ class WorksResource extends BaseResource {
|
|
|
406
543
|
const qs = qp.toString();
|
|
407
544
|
return this.requestPaginated("GET", `/admin/works${qs ? `?${qs}` : ""}`);
|
|
408
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* The agreements linked to ONE work (GET /admin/works/:id/agreements).
|
|
548
|
+
* Inspect used `agreements.list()`, which returned the org's first page of
|
|
549
|
+
* agreements under a work's heading (row-cap T3).
|
|
550
|
+
*/
|
|
551
|
+
async getAgreements(workId) {
|
|
552
|
+
const res = await this.request("GET", `/admin/works/${workId}/agreements`);
|
|
553
|
+
return Array.isArray(res) ? res : [];
|
|
554
|
+
}
|
|
409
555
|
async get(id) {
|
|
410
556
|
return this.request("GET", `/admin/works/${id}`);
|
|
411
557
|
}
|
|
@@ -517,6 +663,22 @@ class WorksResource extends BaseResource {
|
|
|
517
663
|
async addWriters(workId, names) {
|
|
518
664
|
return this.request("POST", `/admin/works/${workId}/writers`, { names });
|
|
519
665
|
}
|
|
666
|
+
/** A song's directory topics: approved, proposed, moods awaiting review, settings. */
|
|
667
|
+
async getThemes(workId) {
|
|
668
|
+
return this.request("GET", `/admin/works/${encodeURIComponent(workId)}/themes`);
|
|
669
|
+
}
|
|
670
|
+
/** Set the song's approved topics directly, and/or its switch and lyric visibility. */
|
|
671
|
+
async setThemes(workId, body) {
|
|
672
|
+
return this.request("PUT", `/admin/works/${encodeURIComponent(workId)}/themes`, body);
|
|
673
|
+
}
|
|
674
|
+
/** Propose topics (and recording moods) for the owner to review. Nothing goes public. */
|
|
675
|
+
async proposeThemes(workId, body) {
|
|
676
|
+
return this.request("POST", `/admin/works/${encodeURIComponent(workId)}/themes/proposals`, body);
|
|
677
|
+
}
|
|
678
|
+
/** The owner's review of proposed topics and moods. */
|
|
679
|
+
async reviewThemes(workId, body) {
|
|
680
|
+
return this.request("PATCH", `/admin/works/${encodeURIComponent(workId)}/themes/proposals`, body);
|
|
681
|
+
}
|
|
520
682
|
}
|
|
521
683
|
class PeopleResource extends BaseResource {
|
|
522
684
|
async list(params) {
|
|
@@ -628,6 +790,23 @@ class PeopleResource extends BaseResource {
|
|
|
628
790
|
return this.request("GET", `/admin/people/triangulate?${qp.toString()}`);
|
|
629
791
|
}
|
|
630
792
|
}
|
|
793
|
+
function licenseEnquiryListPath(params) {
|
|
794
|
+
const queryParams = new URLSearchParams();
|
|
795
|
+
if (params?.work_id)
|
|
796
|
+
queryParams.set("work_id", params.work_id);
|
|
797
|
+
if (params?.status)
|
|
798
|
+
queryParams.set("status", params.status);
|
|
799
|
+
if (params?.project_type)
|
|
800
|
+
queryParams.set("project_type", params.project_type);
|
|
801
|
+
if (params?.territory)
|
|
802
|
+
queryParams.set("territory", params.territory);
|
|
803
|
+
if (params?.limit !== undefined)
|
|
804
|
+
queryParams.set("limit", String(params.limit));
|
|
805
|
+
if (params?.offset !== undefined)
|
|
806
|
+
queryParams.set("offset", String(params.offset));
|
|
807
|
+
const query = queryParams.toString();
|
|
808
|
+
return `/admin/license-enquiries${query ? `?${query}` : ""}`;
|
|
809
|
+
}
|
|
631
810
|
class LicensingResource extends BaseResource {
|
|
632
811
|
/**
|
|
633
812
|
* Search for sync-licensable tracks by audio characteristics
|
|
@@ -673,33 +852,26 @@ class LicensingResource extends BaseResource {
|
|
|
673
852
|
return this.request("POST", "/admin/license-enquiries", enquiry);
|
|
674
853
|
}
|
|
675
854
|
/**
|
|
676
|
-
* List license enquiries with optional filters
|
|
855
|
+
* List license enquiries with optional filters (one page, the bare array;
|
|
856
|
+
* see listEnquiriesPage for the real total).
|
|
677
857
|
*/
|
|
678
858
|
async listEnquiries(params) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
queryParams.set("project_type", params.project_type);
|
|
686
|
-
if (params?.territory)
|
|
687
|
-
queryParams.set("territory", params.territory);
|
|
688
|
-
if (params?.limit !== undefined)
|
|
689
|
-
queryParams.set("limit", String(params.limit));
|
|
690
|
-
if (params?.offset !== undefined)
|
|
691
|
-
queryParams.set("offset", String(params.offset));
|
|
692
|
-
const query = queryParams.toString();
|
|
693
|
-
const path = `/admin/license-enquiries${query ? `?${query}` : ""}`;
|
|
694
|
-
// The admin route returns successResponse({ data: [...], count }); after
|
|
695
|
-
// request() unwraps the outer { success, data } envelope the payload is the
|
|
696
|
-
// { data, count } object, not the array. Tolerate both shapes (mirrors
|
|
697
|
-
// listDiscoveriesRecent + the other list resources) so the caller never
|
|
698
|
-
// calls `.map` on a non-array — the "t.map is not a function" crash that
|
|
699
|
-
// made pica_list_license_enquiries fail on every call shape.
|
|
700
|
-
const res = (await this.request("GET", path));
|
|
859
|
+
// Older servers nested the list as `{ data: { data: [...], count } }`, so
|
|
860
|
+
// after request() unwraps the outer envelope the payload was an object,
|
|
861
|
+
// not the array. Tolerate both shapes so the caller never calls `.map` on
|
|
862
|
+
// a non-array — the "t.map is not a function" crash that made
|
|
863
|
+
// pica_list_license_enquiries fail on every call shape.
|
|
864
|
+
const res = (await this.request("GET", licenseEnquiryListPath(params)));
|
|
701
865
|
return Array.isArray(res) ? res : (res?.data ?? []);
|
|
702
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* One page of license enquiries WITH the real total and has_more
|
|
869
|
+
* (row-cap T3): `listEnquiries()` returns the bare array, so callers
|
|
870
|
+
* reported the page length as the total.
|
|
871
|
+
*/
|
|
872
|
+
async listEnquiriesPage(params) {
|
|
873
|
+
return this.requestPaginated("GET", licenseEnquiryListPath(params));
|
|
874
|
+
}
|
|
703
875
|
/**
|
|
704
876
|
* Get a specific license enquiry by ID
|
|
705
877
|
*/
|
|
@@ -722,34 +894,40 @@ class LicensingResource extends BaseResource {
|
|
|
722
894
|
return this.request("PATCH", `/admin/license-enquiries/${id}`, { counterOffer, notes });
|
|
723
895
|
}
|
|
724
896
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
897
|
+
function bookingListPath(params) {
|
|
898
|
+
const qp = new URLSearchParams();
|
|
899
|
+
if (params?.status)
|
|
900
|
+
qp.set("status", params.status);
|
|
901
|
+
if (params?.priority)
|
|
902
|
+
qp.set("priority", params.priority);
|
|
903
|
+
if (params?.query)
|
|
904
|
+
qp.set("query", params.query);
|
|
905
|
+
if (params?.enquiry_type)
|
|
906
|
+
qp.set("enquiry_type", params.enquiry_type);
|
|
907
|
+
if (params?.follow_up_required !== undefined)
|
|
908
|
+
qp.set("follow_up_required", String(params.follow_up_required));
|
|
909
|
+
if (params?.limit !== undefined)
|
|
910
|
+
qp.set("limit", String(params.limit));
|
|
911
|
+
if (params?.offset !== undefined)
|
|
912
|
+
qp.set("offset", String(params.offset));
|
|
913
|
+
const qs = qp.toString();
|
|
914
|
+
return `/admin/bookings${qs ? `?${qs}` : ""}`;
|
|
915
|
+
}
|
|
730
916
|
class BookingsResource extends BaseResource {
|
|
731
917
|
/**
|
|
732
|
-
*
|
|
733
|
-
*
|
|
918
|
+
* One page of booking enquiries (the bare array; see listPage for the real
|
|
919
|
+
* total). `request()` unwraps the route's `{ data, count }` envelope.
|
|
734
920
|
*/
|
|
735
921
|
async list(params) {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
if (params?.follow_up_required !== undefined)
|
|
746
|
-
qp.set("follow_up_required", String(params.follow_up_required));
|
|
747
|
-
if (params?.limit !== undefined)
|
|
748
|
-
qp.set("limit", String(params.limit));
|
|
749
|
-
if (params?.offset !== undefined)
|
|
750
|
-
qp.set("offset", String(params.offset));
|
|
751
|
-
const qs = qp.toString();
|
|
752
|
-
return this.request("GET", `/admin/bookings${qs ? `?${qs}` : ""}`);
|
|
922
|
+
return this.request("GET", bookingListPath(params));
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* One page of booking enquiries WITH the real total and has_more
|
|
926
|
+
* (row-cap T3): `list()` returns the bare array, so callers reported the
|
|
927
|
+
* page length as the total.
|
|
928
|
+
*/
|
|
929
|
+
async listPage(params) {
|
|
930
|
+
return this.requestPaginated("GET", bookingListPath(params));
|
|
753
931
|
}
|
|
754
932
|
async get(id) {
|
|
755
933
|
return this.request("GET", `/admin/bookings/${encodeURIComponent(id)}`);
|
|
@@ -1084,39 +1262,51 @@ class MultimediaResource extends BaseResource {
|
|
|
1084
1262
|
});
|
|
1085
1263
|
}
|
|
1086
1264
|
}
|
|
1265
|
+
function agreementListPath(params) {
|
|
1266
|
+
const queryParams = new URLSearchParams();
|
|
1267
|
+
if (params?.query)
|
|
1268
|
+
queryParams.set("query", params.query);
|
|
1269
|
+
if (params?.agreement_type)
|
|
1270
|
+
queryParams.set("agreement_type", params.agreement_type);
|
|
1271
|
+
if (params?.status)
|
|
1272
|
+
queryParams.set("status", params.status);
|
|
1273
|
+
if (params?.party_name)
|
|
1274
|
+
queryParams.set("party_name", params.party_name);
|
|
1275
|
+
if (params?.includeWorkCounts)
|
|
1276
|
+
queryParams.set("includeWorkCounts", "true");
|
|
1277
|
+
if (params?.awaiting_signature)
|
|
1278
|
+
queryParams.set("awaiting_signature", "true");
|
|
1279
|
+
if (params?.start_date_from)
|
|
1280
|
+
queryParams.set("start_date_from", params.start_date_from);
|
|
1281
|
+
if (params?.start_date_to)
|
|
1282
|
+
queryParams.set("start_date_to", params.start_date_to);
|
|
1283
|
+
if (params?.end_date_from)
|
|
1284
|
+
queryParams.set("end_date_from", params.end_date_from);
|
|
1285
|
+
if (params?.end_date_to)
|
|
1286
|
+
queryParams.set("end_date_to", params.end_date_to);
|
|
1287
|
+
if (params?.expiring_within_days !== undefined)
|
|
1288
|
+
queryParams.set("expiring_within_days", String(params.expiring_within_days));
|
|
1289
|
+
if (params?.has_end_date !== undefined)
|
|
1290
|
+
queryParams.set("has_end_date", String(params.has_end_date));
|
|
1291
|
+
if (params?.limit !== undefined)
|
|
1292
|
+
queryParams.set("limit", String(params.limit));
|
|
1293
|
+
if (params?.offset !== undefined)
|
|
1294
|
+
queryParams.set("offset", String(params.offset));
|
|
1295
|
+
const query = queryParams.toString();
|
|
1296
|
+
return `/admin/agreements${query ? `?${query}` : ""}`;
|
|
1297
|
+
}
|
|
1087
1298
|
class AgreementsResource extends BaseResource {
|
|
1299
|
+
/** One page of agreements (the bare array; see listPage for the real total). */
|
|
1088
1300
|
async list(params) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
if (params?.includeWorkCounts)
|
|
1099
|
-
queryParams.set("includeWorkCounts", "true");
|
|
1100
|
-
if (params?.awaiting_signature)
|
|
1101
|
-
queryParams.set("awaiting_signature", "true");
|
|
1102
|
-
if (params?.start_date_from)
|
|
1103
|
-
queryParams.set("start_date_from", params.start_date_from);
|
|
1104
|
-
if (params?.start_date_to)
|
|
1105
|
-
queryParams.set("start_date_to", params.start_date_to);
|
|
1106
|
-
if (params?.end_date_from)
|
|
1107
|
-
queryParams.set("end_date_from", params.end_date_from);
|
|
1108
|
-
if (params?.end_date_to)
|
|
1109
|
-
queryParams.set("end_date_to", params.end_date_to);
|
|
1110
|
-
if (params?.expiring_within_days !== undefined)
|
|
1111
|
-
queryParams.set("expiring_within_days", String(params.expiring_within_days));
|
|
1112
|
-
if (params?.has_end_date !== undefined)
|
|
1113
|
-
queryParams.set("has_end_date", String(params.has_end_date));
|
|
1114
|
-
if (params?.limit !== undefined)
|
|
1115
|
-
queryParams.set("limit", String(params.limit));
|
|
1116
|
-
if (params?.offset !== undefined)
|
|
1117
|
-
queryParams.set("offset", String(params.offset));
|
|
1118
|
-
const query = queryParams.toString();
|
|
1119
|
-
return this.request("GET", `/admin/agreements${query ? `?${query}` : ""}`);
|
|
1301
|
+
return this.request("GET", agreementListPath(params));
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* One page of agreements WITH the real total and has_more (row-cap T3):
|
|
1305
|
+
* `list()` returns the bare array, so callers reported the page length
|
|
1306
|
+
* as the total.
|
|
1307
|
+
*/
|
|
1308
|
+
async listPage(params) {
|
|
1309
|
+
return this.requestPaginated("GET", agreementListPath(params));
|
|
1120
1310
|
}
|
|
1121
1311
|
async get(id) {
|
|
1122
1312
|
return this.request("GET", `/admin/agreements/${id}`);
|
|
@@ -1189,10 +1379,28 @@ class AgreementsResource extends BaseResource {
|
|
|
1189
1379
|
return this.request("POST", `/admin/agreements/${id}/send-for-signature`, { expiresInDays: params?.expires_in_days });
|
|
1190
1380
|
}
|
|
1191
1381
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1382
|
+
/** Sync fit: GET /admin/sync-fit, the same read as /inspect/sync. */
|
|
1383
|
+
class SyncFitResource extends BaseResource {
|
|
1384
|
+
async query(params) {
|
|
1385
|
+
const q = new URLSearchParams();
|
|
1386
|
+
if (params?.work_id)
|
|
1387
|
+
q.set("work_id", params.work_id);
|
|
1388
|
+
if (params?.screen_project_id)
|
|
1389
|
+
q.set("screen_project_id", params.screen_project_id);
|
|
1390
|
+
if (params?.song_facts)
|
|
1391
|
+
q.set("song_facts", "true");
|
|
1392
|
+
if (params?.limit)
|
|
1393
|
+
q.set("limit", String(params.limit));
|
|
1394
|
+
if (params?.offset)
|
|
1395
|
+
q.set("offset", String(params.offset));
|
|
1396
|
+
const qs = q.toString();
|
|
1397
|
+
const res = await this.request("GET", `/admin/sync-fit${qs ? `?${qs}` : ""}`);
|
|
1398
|
+
if (res && typeof res === "object" && "data" in res && res.data) {
|
|
1399
|
+
return res.data;
|
|
1400
|
+
}
|
|
1401
|
+
return res;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1196
1404
|
class SyncPlacementsResource extends BaseResource {
|
|
1197
1405
|
async list(params) {
|
|
1198
1406
|
const queryParams = new URLSearchParams();
|
|
@@ -1735,6 +1943,16 @@ class EnrichmentResource extends BaseResource {
|
|
|
1735
1943
|
async bulkRejectProposals(input) {
|
|
1736
1944
|
return this.request("POST", "/admin/enrichment-proposals/bulk-reject", input);
|
|
1737
1945
|
}
|
|
1946
|
+
/**
|
|
1947
|
+
* What bulkRejectProposals would reject, without rejecting: the route
|
|
1948
|
+
* resolves the same pending set the real call would (row-cap T3).
|
|
1949
|
+
*/
|
|
1950
|
+
async previewBulkRejectProposals(input) {
|
|
1951
|
+
return this.request("POST", "/admin/enrichment-proposals/bulk-reject", {
|
|
1952
|
+
...input,
|
|
1953
|
+
dry_run: true,
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1738
1956
|
/**
|
|
1739
1957
|
* Apply many pending proposals in one call — the accept side of the
|
|
1740
1958
|
* /inspect/found queue.
|
|
@@ -2676,6 +2894,24 @@ class DuplicatesResource extends BaseResource {
|
|
|
2676
2894
|
loser_ids: loserIds,
|
|
2677
2895
|
});
|
|
2678
2896
|
}
|
|
2897
|
+
/**
|
|
2898
|
+
* "They're different": mark every pair among `ids` (2–25 recordings or
|
|
2899
|
+
* releases of this catalogue) as not duplicates, so findDuplicates stops
|
|
2900
|
+
* offering them. Idempotent. Resolves {entity_type, ids, pairs, changed}.
|
|
2901
|
+
*/
|
|
2902
|
+
async markDifferent(entityType, ids) {
|
|
2903
|
+
return this.request("POST", "/admin/catalog/duplicates/exclusions", {
|
|
2904
|
+
entity_type: entityType,
|
|
2905
|
+
ids,
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
/** Take back markDifferent for every pair among `ids`. */
|
|
2909
|
+
async unmarkDifferent(entityType, ids) {
|
|
2910
|
+
return this.request("DELETE", "/admin/catalog/duplicates/exclusions", {
|
|
2911
|
+
entity_type: entityType,
|
|
2912
|
+
ids,
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2679
2915
|
}
|
|
2680
2916
|
// --- Entity Context Resource (ADR-116 Phase 3) ---
|
|
2681
2917
|
class EntityContextResource extends BaseResource {
|
|
@@ -2943,6 +3179,40 @@ class CollaboratorsResource extends BaseResource {
|
|
|
2943
3179
|
const qs = qp.toString();
|
|
2944
3180
|
return this.request("GET", `/admin/collaborators/invites${qs ? `?${qs}` : ""}`);
|
|
2945
3181
|
}
|
|
3182
|
+
/**
|
|
3183
|
+
* One page of the invites this org sent WITH the real total and has_more
|
|
3184
|
+
* (row-cap T3). `listInvites()` is typed as an array but the route nests
|
|
3185
|
+
* its rows as `data: { data, count }`, so it never carried a usable total.
|
|
3186
|
+
*/
|
|
3187
|
+
async listInvitesPage(params) {
|
|
3188
|
+
const qp = new URLSearchParams();
|
|
3189
|
+
if (params?.status)
|
|
3190
|
+
qp.set("status", params.status);
|
|
3191
|
+
if (params?.workId)
|
|
3192
|
+
qp.set("workId", params.workId);
|
|
3193
|
+
if (params?.limit !== undefined)
|
|
3194
|
+
qp.set("limit", String(params.limit));
|
|
3195
|
+
if (params?.offset)
|
|
3196
|
+
qp.set("offset", String(params.offset));
|
|
3197
|
+
const qs = qp.toString();
|
|
3198
|
+
return this.requestNestedPage(`/admin/collaborators/invites${qs ? `?${qs}` : ""}`, params?.offset ?? 0);
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Reads a collaborator list route, whose rows sit at `data.data` (kept
|
|
3202
|
+
* for published SDKs) with the total in the top-level `pagination`.
|
|
3203
|
+
*/
|
|
3204
|
+
async requestNestedPage(path, offset) {
|
|
3205
|
+
const json = await this.requestWithEnvelope("GET", path);
|
|
3206
|
+
const inner = json.data;
|
|
3207
|
+
const rows = Array.isArray(inner) ? inner : (inner?.data ?? []);
|
|
3208
|
+
const pagination = json.pagination;
|
|
3209
|
+
const total = pagination?.total ?? rows.length;
|
|
3210
|
+
return {
|
|
3211
|
+
data: rows,
|
|
3212
|
+
total,
|
|
3213
|
+
hasMore: pagination?.hasMore ?? offset + rows.length < total,
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
2946
3216
|
async resendInvite(inviteId) {
|
|
2947
3217
|
return this.request("POST", `/admin/collaborators/invites/${inviteId}/resend`);
|
|
2948
3218
|
}
|
|
@@ -2961,6 +3231,21 @@ class CollaboratorsResource extends BaseResource {
|
|
|
2961
3231
|
const res = await this.request("GET", `/admin/collaborators/incoming${qs ? `?${qs}` : ""}`);
|
|
2962
3232
|
return res.data;
|
|
2963
3233
|
}
|
|
3234
|
+
/**
|
|
3235
|
+
* One page of the invites addressed to the caller WITH the real total and
|
|
3236
|
+
* has_more (row-cap T3: `listIncoming()` returns only the rows).
|
|
3237
|
+
*/
|
|
3238
|
+
async listIncomingPage(params) {
|
|
3239
|
+
const qp = new URLSearchParams();
|
|
3240
|
+
if (params?.status)
|
|
3241
|
+
qp.set("status", params.status);
|
|
3242
|
+
if (params?.limit !== undefined)
|
|
3243
|
+
qp.set("limit", String(params.limit));
|
|
3244
|
+
if (params?.offset)
|
|
3245
|
+
qp.set("offset", String(params.offset));
|
|
3246
|
+
const qs = qp.toString();
|
|
3247
|
+
return this.requestNestedPage(`/admin/collaborators/incoming${qs ? `?${qs}` : ""}`, params?.offset ?? 0);
|
|
3248
|
+
}
|
|
2964
3249
|
/**
|
|
2965
3250
|
* ADR-157 warm path — accept an invite addressed to the authenticated
|
|
2966
3251
|
* user, and flip its status to confirmed. The credit write is
|
|
@@ -3192,6 +3477,10 @@ class DirectoryResource extends BaseResource {
|
|
|
3192
3477
|
async updateSettings(updates) {
|
|
3193
3478
|
return this.request("PATCH", "/admin/directory/settings", updates);
|
|
3194
3479
|
}
|
|
3480
|
+
/** Listed songs with lyrics and topics on, but no approved topic yet. */
|
|
3481
|
+
async untaggedThemes() {
|
|
3482
|
+
return this.request("GET", "/admin/directory/themes/untagged");
|
|
3483
|
+
}
|
|
3195
3484
|
}
|
|
3196
3485
|
class ProjectsResource extends BaseResource {
|
|
3197
3486
|
async list(params) {
|
|
@@ -3438,6 +3727,21 @@ class RecordingSplitsResource extends BaseResource {
|
|
|
3438
3727
|
}
|
|
3439
3728
|
}
|
|
3440
3729
|
class PublishersResource extends BaseResource {
|
|
3730
|
+
/**
|
|
3731
|
+
* One page of the publisher registry with an honest has_more. The total
|
|
3732
|
+
* is null: the route does not count the platform-wide registry.
|
|
3733
|
+
*/
|
|
3734
|
+
async listPage(params) {
|
|
3735
|
+
const queryParams = new URLSearchParams();
|
|
3736
|
+
if (params?.query)
|
|
3737
|
+
queryParams.set("query", params.query);
|
|
3738
|
+
if (params?.limit)
|
|
3739
|
+
queryParams.set("limit", String(params.limit));
|
|
3740
|
+
if (params?.offset)
|
|
3741
|
+
queryParams.set("offset", String(params.offset));
|
|
3742
|
+
const qs = queryParams.toString();
|
|
3743
|
+
return this.requestPaginated("GET", `/admin/publishers${qs ? `?${qs}` : ""}`);
|
|
3744
|
+
}
|
|
3441
3745
|
async list(params) {
|
|
3442
3746
|
const queryParams = new URLSearchParams();
|
|
3443
3747
|
if (params?.query)
|
|
@@ -3452,6 +3756,25 @@ class PublishersResource extends BaseResource {
|
|
|
3452
3756
|
}
|
|
3453
3757
|
}
|
|
3454
3758
|
class LabelsResource extends BaseResource {
|
|
3759
|
+
/**
|
|
3760
|
+
* One page of label/distributor organisations with an honest has_more.
|
|
3761
|
+
* The total is null: the route does not count platform-wide rows.
|
|
3762
|
+
*/
|
|
3763
|
+
async listPage(params) {
|
|
3764
|
+
const queryParams = new URLSearchParams();
|
|
3765
|
+
if (params?.query)
|
|
3766
|
+
queryParams.set("query", params.query);
|
|
3767
|
+
if (params?.limit)
|
|
3768
|
+
queryParams.set("limit", String(params.limit));
|
|
3769
|
+
if (params?.offset)
|
|
3770
|
+
queryParams.set("offset", String(params.offset));
|
|
3771
|
+
if (params?.org_type)
|
|
3772
|
+
queryParams.set("org_type", params.org_type);
|
|
3773
|
+
if (params?.ids?.length)
|
|
3774
|
+
queryParams.set("ids", params.ids.join(","));
|
|
3775
|
+
const qs = queryParams.toString();
|
|
3776
|
+
return this.requestPaginated("GET", `/admin/labels${qs ? `?${qs}` : ""}`);
|
|
3777
|
+
}
|
|
3455
3778
|
async list(params) {
|
|
3456
3779
|
const queryParams = new URLSearchParams();
|
|
3457
3780
|
if (params?.query)
|
|
@@ -3480,14 +3803,20 @@ class LabelsResource extends BaseResource {
|
|
|
3480
3803
|
return (res ?? null);
|
|
3481
3804
|
}
|
|
3482
3805
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3806
|
+
function partyClaimListPath(params) {
|
|
3807
|
+
const queryParams = new URLSearchParams();
|
|
3808
|
+
if (params?.target_organisation_id) {
|
|
3809
|
+
queryParams.set("target_organisation_id", params.target_organisation_id);
|
|
3810
|
+
}
|
|
3811
|
+
if (params?.status)
|
|
3812
|
+
queryParams.set("status", params.status);
|
|
3813
|
+
if (params?.limit)
|
|
3814
|
+
queryParams.set("limit", String(params.limit));
|
|
3815
|
+
if (params?.offset)
|
|
3816
|
+
queryParams.set("offset", String(params.offset));
|
|
3817
|
+
const qs = queryParams.toString();
|
|
3818
|
+
return `/admin/party-claims${qs ? `?${qs}` : ""}`;
|
|
3819
|
+
}
|
|
3491
3820
|
class PartyClaimsResource extends BaseResource {
|
|
3492
3821
|
async invite(params) {
|
|
3493
3822
|
const res = await this.request("POST", "/admin/party-claims", params);
|
|
@@ -3499,20 +3828,19 @@ class PartyClaimsResource extends BaseResource {
|
|
|
3499
3828
|
return (res ?? null);
|
|
3500
3829
|
}
|
|
3501
3830
|
async list(params) {
|
|
3502
|
-
const
|
|
3503
|
-
if (params?.target_organisation_id) {
|
|
3504
|
-
queryParams.set("target_organisation_id", params.target_organisation_id);
|
|
3505
|
-
}
|
|
3506
|
-
if (params?.status)
|
|
3507
|
-
queryParams.set("status", params.status);
|
|
3508
|
-
if (params?.limit)
|
|
3509
|
-
queryParams.set("limit", String(params.limit));
|
|
3510
|
-
const qs = queryParams.toString();
|
|
3511
|
-
const res = await this.request("GET", `/admin/party-claims${qs ? `?${qs}` : ""}`);
|
|
3831
|
+
const res = await this.request("GET", partyClaimListPath(params));
|
|
3512
3832
|
if (Array.isArray(res))
|
|
3513
3833
|
return res;
|
|
3514
3834
|
return (res?.data ?? []);
|
|
3515
3835
|
}
|
|
3836
|
+
/**
|
|
3837
|
+
* One page of sent claim invites WITH the real total and has_more
|
|
3838
|
+
* (row-cap T3): `list()` returns the bare array, so callers reported the
|
|
3839
|
+
* page length as the total.
|
|
3840
|
+
*/
|
|
3841
|
+
async listPage(params) {
|
|
3842
|
+
return this.requestPaginated("GET", partyClaimListPath(params));
|
|
3843
|
+
}
|
|
3516
3844
|
}
|
|
3517
3845
|
/**
|
|
3518
3846
|
* Claim your profile: invite people to claim their profile (a preview
|
|
@@ -3544,25 +3872,38 @@ class ProfileClaimsResource extends BaseResource {
|
|
|
3544
3872
|
};
|
|
3545
3873
|
}
|
|
3546
3874
|
}
|
|
3875
|
+
function releaseListPath(params) {
|
|
3876
|
+
const parts = [];
|
|
3877
|
+
if (params?.limit != null)
|
|
3878
|
+
parts.push(`limit=${params.limit}`);
|
|
3879
|
+
if (params?.offset != null)
|
|
3880
|
+
parts.push(`offset=${params.offset}`);
|
|
3881
|
+
if (params?.query)
|
|
3882
|
+
parts.push(`query=${encodeURIComponent(params.query)}`);
|
|
3883
|
+
if (params?.upc)
|
|
3884
|
+
parts.push(`upc=${encodeURIComponent(params.upc)}`);
|
|
3885
|
+
if (params?.spotify_album_id)
|
|
3886
|
+
parts.push(`spotify_album_id=${encodeURIComponent(params.spotify_album_id)}`);
|
|
3887
|
+
if (params?.release_date_from)
|
|
3888
|
+
parts.push(`release_date_from=${encodeURIComponent(params.release_date_from)}`);
|
|
3889
|
+
if (params?.release_date_to)
|
|
3890
|
+
parts.push(`release_date_to=${encodeURIComponent(params.release_date_to)}`);
|
|
3891
|
+
const qs = parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
3892
|
+
return `/admin/releases${qs}`;
|
|
3893
|
+
}
|
|
3547
3894
|
class ReleasesResource extends BaseResource {
|
|
3895
|
+
/** One page of releases (the bare array; see listPage for the real total). */
|
|
3548
3896
|
async list(params) {
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
parts.push(`spotify_album_id=${encodeURIComponent(params.spotify_album_id)}`);
|
|
3560
|
-
if (params?.release_date_from)
|
|
3561
|
-
parts.push(`release_date_from=${encodeURIComponent(params.release_date_from)}`);
|
|
3562
|
-
if (params?.release_date_to)
|
|
3563
|
-
parts.push(`release_date_to=${encodeURIComponent(params.release_date_to)}`);
|
|
3564
|
-
const qs = parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
3565
|
-
return this.request("GET", `/admin/releases${qs}`);
|
|
3897
|
+
return this.request("GET", releaseListPath(params));
|
|
3898
|
+
}
|
|
3899
|
+
/**
|
|
3900
|
+
* One page of releases WITH the real total and has_more (row-cap T3):
|
|
3901
|
+
* `list()` returns the bare array, so callers guessed has_more from "the
|
|
3902
|
+
* page came back full". `total` is null when the server cannot count (a
|
|
3903
|
+
* title search past its match cap); has_more is the server's own answer.
|
|
3904
|
+
*/
|
|
3905
|
+
async listPage(params) {
|
|
3906
|
+
return this.requestPaginated("GET", releaseListPath(params));
|
|
3566
3907
|
}
|
|
3567
3908
|
async get(id) {
|
|
3568
3909
|
return this.request("GET", `/admin/releases/${id}`);
|
|
@@ -3681,15 +4022,27 @@ class ReleasesResource extends BaseResource {
|
|
|
3681
4022
|
return this.request("POST", "/admin/releases/create-with-tracks", data);
|
|
3682
4023
|
}
|
|
3683
4024
|
}
|
|
4025
|
+
function sessionListPath(params) {
|
|
4026
|
+
const query = new URLSearchParams();
|
|
4027
|
+
if (params?.limit)
|
|
4028
|
+
query.set("limit", String(params.limit));
|
|
4029
|
+
if (params?.offset)
|
|
4030
|
+
query.set("offset", String(params.offset));
|
|
4031
|
+
const qs = query.toString();
|
|
4032
|
+
return `/admin/sessions${qs ? `?${qs}` : ""}`;
|
|
4033
|
+
}
|
|
3684
4034
|
class SessionsResource extends BaseResource {
|
|
4035
|
+
/** One page of upcoming sessions (see listPage for the real total). */
|
|
3685
4036
|
async list(params) {
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
4037
|
+
return this.request("GET", sessionListPath(params));
|
|
4038
|
+
}
|
|
4039
|
+
/**
|
|
4040
|
+
* One page of the next 7 days' sessions WITH the real total and has_more
|
|
4041
|
+
* (row-cap T3): `list()` returns the bare array, so callers could only
|
|
4042
|
+
* guess has_more from "the page came back full".
|
|
4043
|
+
*/
|
|
4044
|
+
async listPage(params) {
|
|
4045
|
+
return this.requestPaginated("GET", sessionListPath(params));
|
|
3693
4046
|
}
|
|
3694
4047
|
async get(id) {
|
|
3695
4048
|
return this.request("GET", `/admin/sessions/${id}`);
|
|
@@ -3824,6 +4177,113 @@ class RightsResource extends BaseResource {
|
|
|
3824
4177
|
return this.request("GET", "/admin/rights-statements/measures");
|
|
3825
4178
|
}
|
|
3826
4179
|
}
|
|
4180
|
+
function pageQuery(params) {
|
|
4181
|
+
const qp = new URLSearchParams();
|
|
4182
|
+
for (const [key, value] of Object.entries(params)) {
|
|
4183
|
+
if (value === undefined)
|
|
4184
|
+
continue;
|
|
4185
|
+
if (Array.isArray(value))
|
|
4186
|
+
value.forEach((v) => qp.append(key, v));
|
|
4187
|
+
else
|
|
4188
|
+
qp.set(key, String(value));
|
|
4189
|
+
}
|
|
4190
|
+
const qs = qp.toString();
|
|
4191
|
+
return qs ? `?${qs}` : "";
|
|
4192
|
+
}
|
|
4193
|
+
/**
|
|
4194
|
+
* Catalogues an organisation builds for a client, who accepts to own them
|
|
4195
|
+
* (ADR-333 D5). Builder side only: the client answers on screen, signed in.
|
|
4196
|
+
*/
|
|
4197
|
+
class HandoversResource extends BaseResource {
|
|
4198
|
+
/**
|
|
4199
|
+
* POST /admin/handovers — create a catalogue for a client
|
|
4200
|
+
* (catalogue_name), or a new hand-over on one this organisation built whose
|
|
4201
|
+
* last one closed (catalogue_org_id). Nothing is sent.
|
|
4202
|
+
*/
|
|
4203
|
+
async create(body) {
|
|
4204
|
+
return this.request("POST", "/admin/handovers", body);
|
|
4205
|
+
}
|
|
4206
|
+
/** POST /admin/handovers/{id}/send { expected_version? } — email the client. */
|
|
4207
|
+
async send(id, body) {
|
|
4208
|
+
return this.request("POST", `/admin/handovers/${encodeURIComponent(id)}/send`, body);
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
/**
|
|
4212
|
+
* /admin/relationships/** — both sides of a relationship. Refusals arrive as
|
|
4213
|
+
* `{ success: false, error: { code, message, details? } }` (read them with
|
|
4214
|
+
* the MCP package's parseApiErrorBody); a step whose terms changed since they
|
|
4215
|
+
* were read is a 409 `status_changed` carrying `details.relationship`.
|
|
4216
|
+
*/
|
|
4217
|
+
class RelationshipsResource extends BaseResource {
|
|
4218
|
+
/** GET /admin/relationships?side=owner|organisation&status=&cursor=&limit= */
|
|
4219
|
+
async list(params) {
|
|
4220
|
+
return this.request("GET", `/admin/relationships${pageQuery({
|
|
4221
|
+
side: params.side,
|
|
4222
|
+
status: params.statuses,
|
|
4223
|
+
cursor: params.cursor,
|
|
4224
|
+
limit: params.limit,
|
|
4225
|
+
})}`);
|
|
4226
|
+
}
|
|
4227
|
+
/** GET /admin/relationships/{id} */
|
|
4228
|
+
async get(id) {
|
|
4229
|
+
return this.request("GET", `/admin/relationships/${encodeURIComponent(id)}`);
|
|
4230
|
+
}
|
|
4231
|
+
/**
|
|
4232
|
+
* POST /admin/relationships { as, email, type, scope? } — invite someone by
|
|
4233
|
+
* email. as="owner" asks someone at an organisation to look after this
|
|
4234
|
+
* catalogue; as="organisation" asks a client to let this organisation look
|
|
4235
|
+
* after theirs. The reply is the same whether or not the address is on PICA.
|
|
4236
|
+
*/
|
|
4237
|
+
async invite(body) {
|
|
4238
|
+
return this.request("POST", "/admin/relationships", body);
|
|
4239
|
+
}
|
|
4240
|
+
/** PATCH /admin/relationships/{id} { action: accept | decline | edit, expected_version, … } */
|
|
4241
|
+
async answer(id, body) {
|
|
4242
|
+
return this.request("PATCH", `/admin/relationships/${encodeURIComponent(id)}`, body);
|
|
4243
|
+
}
|
|
4244
|
+
/** DELETE /admin/relationships/{id} { expected_version, reason? } — the owner ends it. */
|
|
4245
|
+
async revoke(id, body) {
|
|
4246
|
+
return this.request("DELETE", `/admin/relationships/${encodeURIComponent(id)}`, body);
|
|
4247
|
+
}
|
|
4248
|
+
/** POST /admin/relationships/{id}/step-down { expected_version, reason? } — the organisation ends its side. */
|
|
4249
|
+
async stepDown(id, body) {
|
|
4250
|
+
return this.request("POST", `/admin/relationships/${encodeURIComponent(id)}/step-down`, body);
|
|
4251
|
+
}
|
|
4252
|
+
/** GET /admin/relationships/{id}/activity — one relationship's history, either party. */
|
|
4253
|
+
async events(id, params = {}) {
|
|
4254
|
+
return this.request("GET", `/admin/relationships/${encodeURIComponent(id)}/activity${pageQuery(params)}`);
|
|
4255
|
+
}
|
|
4256
|
+
/**
|
|
4257
|
+
* GET /admin/relationships/activity?side=&org= — side=owner: what
|
|
4258
|
+
* organisations did in this catalogue (org = one organisation);
|
|
4259
|
+
* side=organisation: this organisation's own actions (org = one catalogue).
|
|
4260
|
+
*/
|
|
4261
|
+
async activity(params) {
|
|
4262
|
+
return this.request("GET", `/admin/relationships/activity${pageQuery(params)}`);
|
|
4263
|
+
}
|
|
4264
|
+
/**
|
|
4265
|
+
* PATCH /admin/relationships/invites/{id} { action: "accept", organisation_id, type?, scope? }
|
|
4266
|
+
* — answer yes to an invitation for one of the person's organisations.
|
|
4267
|
+
*/
|
|
4268
|
+
async acceptInvite(inviteId, body) {
|
|
4269
|
+
return this.request("PATCH", `/admin/relationships/invites/${encodeURIComponent(inviteId)}`, { action: "accept", ...body });
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
/**
|
|
4273
|
+
* /admin/portfolio — the client catalogues an organisation looks after. A
|
|
4274
|
+
* page may hold fewer rows than `limit` and still not be the last: follow
|
|
4275
|
+
* `nextCursor` until it is null.
|
|
4276
|
+
*/
|
|
4277
|
+
class PortfolioResource extends BaseResource {
|
|
4278
|
+
/** GET /admin/portfolio?cursor=&limit= (limit ≤ 25) */
|
|
4279
|
+
async list(params = {}) {
|
|
4280
|
+
return this.request("GET", `/admin/portfolio${pageQuery(params)}`);
|
|
4281
|
+
}
|
|
4282
|
+
/** GET /admin/portfolio/briefing?cursor=&limit= — the list plus what needs attention. */
|
|
4283
|
+
async briefing(params = {}) {
|
|
4284
|
+
return this.request("GET", `/admin/portfolio/briefing${pageQuery(params)}`);
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
3827
4287
|
class RoyaltiesResource extends BaseResource {
|
|
3828
4288
|
async payments(params) {
|
|
3829
4289
|
const qp = new URLSearchParams();
|
|
@@ -4044,6 +4504,11 @@ class CustodyResource extends BaseResource {
|
|
|
4044
4504
|
async respond(body) {
|
|
4045
4505
|
return this.request("POST", "/admin/custody/respond", body);
|
|
4046
4506
|
}
|
|
4507
|
+
/**
|
|
4508
|
+
* Custody claims, one page per direction. Since row-cap T3 the result also
|
|
4509
|
+
* carries `totals: { incoming, outgoing }` (null for a direction not asked
|
|
4510
|
+
* for), `has_more: { incoming, outgoing }`, `limit` and `offset`.
|
|
4511
|
+
*/
|
|
4047
4512
|
async list(params) {
|
|
4048
4513
|
const qp = new URLSearchParams();
|
|
4049
4514
|
if (params?.direction)
|
|
@@ -4052,6 +4517,8 @@ class CustodyResource extends BaseResource {
|
|
|
4052
4517
|
qp.set("status", params.status);
|
|
4053
4518
|
if (params?.limit !== undefined)
|
|
4054
4519
|
qp.set("limit", String(params.limit));
|
|
4520
|
+
if (params?.offset)
|
|
4521
|
+
qp.set("offset", String(params.offset));
|
|
4055
4522
|
const qs = qp.toString();
|
|
4056
4523
|
return this.request("GET", `/admin/custody/list${qs ? `?${qs}` : ""}`);
|
|
4057
4524
|
}
|
|
@@ -4270,6 +4737,7 @@ export class PicaClient {
|
|
|
4270
4737
|
agreements;
|
|
4271
4738
|
// ADR-222 — sync placements (W3, paired with W4 REST routes).
|
|
4272
4739
|
syncPlacements;
|
|
4740
|
+
syncFit;
|
|
4273
4741
|
workspace;
|
|
4274
4742
|
memory;
|
|
4275
4743
|
audit;
|
|
@@ -4318,6 +4786,11 @@ export class PicaClient {
|
|
|
4318
4786
|
producerAgreements;
|
|
4319
4787
|
workForHire;
|
|
4320
4788
|
royalties;
|
|
4789
|
+
/** ADR-333 — who looks after whose catalogue, from either side. */
|
|
4790
|
+
relationships;
|
|
4791
|
+
handovers;
|
|
4792
|
+
/** ADR-333 — the client catalogues an organisation looks after. */
|
|
4793
|
+
portfolio;
|
|
4321
4794
|
/** ADR-321 Phase 2 — programmable rights (propose / status / inspect). */
|
|
4322
4795
|
rights;
|
|
4323
4796
|
statements;
|
|
@@ -4382,6 +4855,7 @@ export class PicaClient {
|
|
|
4382
4855
|
this.multimedia = new MultimediaResource(baseUrl, config.apiKey, debug);
|
|
4383
4856
|
this.agreements = new AgreementsResource(baseUrl, config.apiKey, debug);
|
|
4384
4857
|
this.syncPlacements = new SyncPlacementsResource(baseUrl, config.apiKey, debug);
|
|
4858
|
+
this.syncFit = new SyncFitResource(baseUrl, config.apiKey, debug);
|
|
4385
4859
|
this.workspace = new WorkspaceResource(baseUrl, config.apiKey, debug);
|
|
4386
4860
|
this.memory = new MemoryResource(baseUrl, config.apiKey, debug);
|
|
4387
4861
|
this.audit = new AuditResource(baseUrl, config.apiKey, debug);
|
|
@@ -4428,6 +4902,9 @@ export class PicaClient {
|
|
|
4428
4902
|
this.producerAgreements = new ProducerAgreementsResource(baseUrl, config.apiKey, debug);
|
|
4429
4903
|
this.workForHire = new WorkForHireResource(baseUrl, config.apiKey, debug);
|
|
4430
4904
|
this.royalties = new RoyaltiesResource(baseUrl, config.apiKey, debug);
|
|
4905
|
+
this.relationships = new RelationshipsResource(baseUrl, config.apiKey, debug);
|
|
4906
|
+
this.handovers = new HandoversResource(baseUrl, config.apiKey, debug);
|
|
4907
|
+
this.portfolio = new PortfolioResource(baseUrl, config.apiKey, debug);
|
|
4431
4908
|
this.rights = new RightsResource(baseUrl, config.apiKey, debug);
|
|
4432
4909
|
this.statements = new StatementsResource(baseUrl, config.apiKey, debug);
|
|
4433
4910
|
this.selections = new SelectionsResource(baseUrl, config.apiKey, debug);
|