@larc-iu/plaid-client 0.0.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/index.d.ts +270 -0
- package/package.json +26 -0
- package/src/http.js +212 -0
- package/src/index.js +1532 -0
- package/src/pagination.js +142 -0
- package/src/services.js +220 -0
- package/src/sse.js +147 -0
- package/src/transforms.js +70 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/** A single page of a cursor-paginated collection. */
|
|
2
|
+
interface Page<T = any> {
|
|
3
|
+
entries: T[];
|
|
4
|
+
nextCursor: string | null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ServiceInfo {
|
|
8
|
+
serviceId: string;
|
|
9
|
+
serviceName: string;
|
|
10
|
+
description: string;
|
|
11
|
+
extras?: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface DiscoveredService {
|
|
15
|
+
serviceId: string;
|
|
16
|
+
serviceName: string;
|
|
17
|
+
description: string;
|
|
18
|
+
extras: any;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ServiceRegistration {
|
|
22
|
+
stop(): void;
|
|
23
|
+
isRunning(): boolean;
|
|
24
|
+
serviceInfo: ServiceInfo & { extras: any };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ResponseHelper {
|
|
28
|
+
progress(percent: number, message: string): void;
|
|
29
|
+
complete(data: any): void;
|
|
30
|
+
error(error: string | Error): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface SSEConnection {
|
|
34
|
+
close(): void;
|
|
35
|
+
getStats(): any;
|
|
36
|
+
readyState: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface VocabLinksBundle {
|
|
40
|
+
create(vocabItem: string, tokens: any[], metadata?: any): Promise<any>;
|
|
41
|
+
setMetadata(id: string, body: any): Promise<any>;
|
|
42
|
+
deleteMetadata(id: string): Promise<any>;
|
|
43
|
+
get(id: string, asOf?: string): Promise<any>;
|
|
44
|
+
delete(id: string): Promise<any>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface VocabLayersBundle {
|
|
48
|
+
get(id: string, includeItems?: boolean, asOf?: string): Promise<any>;
|
|
49
|
+
delete(id: string): Promise<any>;
|
|
50
|
+
update(id: string, name: string): Promise<any>;
|
|
51
|
+
setConfig(id: string, namespace: string, configKey: string, configValue: any): Promise<any>;
|
|
52
|
+
deleteConfig(id: string, namespace: string, configKey: string): Promise<any>;
|
|
53
|
+
list(asOf?: string): Promise<any[]>;
|
|
54
|
+
listPage(opts?: { limit?: number; cursor?: string; asOf?: string }): Promise<Page>;
|
|
55
|
+
iterPages(opts?: { pageSize?: number; asOf?: string }): AsyncGenerator<any[]>;
|
|
56
|
+
create(name: string): Promise<any>;
|
|
57
|
+
addMaintainer(id: string, userId: string): Promise<any>;
|
|
58
|
+
removeMaintainer(id: string, userId: string): Promise<any>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface RelationsBundle {
|
|
62
|
+
setMetadata(relationId: string, body: any): Promise<any>;
|
|
63
|
+
deleteMetadata(relationId: string): Promise<any>;
|
|
64
|
+
setTarget(relationId: string, spanId: string): Promise<any>;
|
|
65
|
+
get(relationId: string, asOf?: string): Promise<any>;
|
|
66
|
+
delete(relationId: string): Promise<any>;
|
|
67
|
+
update(relationId: string, value: any): Promise<any>;
|
|
68
|
+
setSource(relationId: string, spanId: string): Promise<any>;
|
|
69
|
+
create(layerId: string, sourceId: string, targetId: string, value: any, metadata?: any): Promise<any>;
|
|
70
|
+
bulkCreate(body: any[]): Promise<any>;
|
|
71
|
+
bulkDelete(body: any[]): Promise<any>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface SpanLayersBundle {
|
|
75
|
+
setConfig(spanLayerId: string, namespace: string, configKey: string, configValue: any): Promise<any>;
|
|
76
|
+
deleteConfig(spanLayerId: string, namespace: string, configKey: string): Promise<any>;
|
|
77
|
+
get(spanLayerId: string, asOf?: string): Promise<any>;
|
|
78
|
+
delete(spanLayerId: string): Promise<any>;
|
|
79
|
+
update(spanLayerId: string, name: string): Promise<any>;
|
|
80
|
+
create(tokenLayerId: string, name: string): Promise<any>;
|
|
81
|
+
shift(spanLayerId: string, direction: string): Promise<any>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface SpansBundle {
|
|
85
|
+
setTokens(spanId: string, tokens: any[]): Promise<any>;
|
|
86
|
+
create(spanLayerId: string, tokens: any[], value: any, metadata?: any): Promise<any>;
|
|
87
|
+
get(spanId: string, asOf?: string): Promise<any>;
|
|
88
|
+
delete(spanId: string): Promise<any>;
|
|
89
|
+
update(spanId: string, value: any): Promise<any>;
|
|
90
|
+
bulkCreate(body: any[]): Promise<any>;
|
|
91
|
+
bulkDelete(body: any[]): Promise<any>;
|
|
92
|
+
setMetadata(spanId: string, body: any): Promise<any>;
|
|
93
|
+
deleteMetadata(spanId: string): Promise<any>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface BatchBundle {
|
|
97
|
+
submit(body: any[]): Promise<any>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface TextsBundle {
|
|
101
|
+
setMetadata(textId: string, body: any): Promise<any>;
|
|
102
|
+
deleteMetadata(textId: string): Promise<any>;
|
|
103
|
+
create(textLayerId: string, documentId: string, body: string, metadata?: any): Promise<any>;
|
|
104
|
+
get(textId: string, asOf?: string): Promise<any>;
|
|
105
|
+
delete(textId: string): Promise<any>;
|
|
106
|
+
update(textId: string, body: any): Promise<any>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface UsersBundle {
|
|
110
|
+
list(asOf?: string): Promise<any[]>;
|
|
111
|
+
listPage(opts?: { limit?: number; cursor?: string; asOf?: string }): Promise<Page>;
|
|
112
|
+
iterPages(opts?: { pageSize?: number; asOf?: string }): AsyncGenerator<any[]>;
|
|
113
|
+
create(username: string, password: string, isAdmin: boolean): Promise<any>;
|
|
114
|
+
audit(userId: string, startTime?: string, endTime?: string, asOf?: string): Promise<any[]>;
|
|
115
|
+
get(id: string, asOf?: string): Promise<any>;
|
|
116
|
+
delete(id: string): Promise<any>;
|
|
117
|
+
update(id: string, password?: string, username?: string, isAdmin?: boolean): Promise<any>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface ApiTokensBundle {
|
|
121
|
+
list(userId: string): Promise<any[]>;
|
|
122
|
+
listPage(userId: string, opts?: { limit?: number; cursor?: string }): Promise<Page>;
|
|
123
|
+
iterPages(userId: string, opts?: { pageSize?: number }): AsyncGenerator<any[]>;
|
|
124
|
+
create(userId: string, name: string): Promise<{ id: string; name: string; token: string }>;
|
|
125
|
+
revoke(userId: string, tokenId: string): Promise<any>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface TokenLayersBundle {
|
|
129
|
+
shift(tokenLayerId: string, direction: string): Promise<any>;
|
|
130
|
+
create(textLayerId: string, name: string, overlapMode?: string, parentTokenLayerId?: string): Promise<any>;
|
|
131
|
+
setConfig(tokenLayerId: string, namespace: string, configKey: string, configValue: any): Promise<any>;
|
|
132
|
+
deleteConfig(tokenLayerId: string, namespace: string, configKey: string): Promise<any>;
|
|
133
|
+
get(tokenLayerId: string, asOf?: string): Promise<any>;
|
|
134
|
+
delete(tokenLayerId: string): Promise<any>;
|
|
135
|
+
update(tokenLayerId: string, name: string): Promise<any>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface DocumentsBundle {
|
|
139
|
+
checkLock(documentId: string, asOf?: string): Promise<any>;
|
|
140
|
+
acquireLock(documentId: string): Promise<any>;
|
|
141
|
+
releaseLock(documentId: string): Promise<any>;
|
|
142
|
+
getMedia(documentId: string, asOf?: string): Promise<ArrayBuffer>;
|
|
143
|
+
uploadMedia(documentId: string, file: File): Promise<any>;
|
|
144
|
+
deleteMedia(documentId: string): Promise<any>;
|
|
145
|
+
setMetadata(documentId: string, body: any): Promise<any>;
|
|
146
|
+
deleteMetadata(documentId: string): Promise<any>;
|
|
147
|
+
audit(documentId: string, startTime?: string, endTime?: string, asOf?: string): Promise<any[]>;
|
|
148
|
+
get(documentId: string, includeBody?: boolean, asOf?: string): Promise<any>;
|
|
149
|
+
delete(documentId: string): Promise<any>;
|
|
150
|
+
update(documentId: string, name: string): Promise<any>;
|
|
151
|
+
create(projectId: string, name: string, metadata?: any): Promise<any>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface MessagesBundle {
|
|
155
|
+
sendMessage(projectId: string, data: any): Promise<any>;
|
|
156
|
+
listen(projectId: string, onEvent: (eventType: string, data: any) => void | boolean, path?: string): SSEConnection;
|
|
157
|
+
/** Discover the services currently connected to a project. `timeout` is ignored (kept for back-compat). */
|
|
158
|
+
discoverServices(projectId: string, timeout?: number): Promise<DiscoveredService[]>;
|
|
159
|
+
serve(projectId: string, serviceInfo: ServiceInfo, onServiceRequest: (data: any, responseHelper: ResponseHelper) => void, extras?: any): ServiceRegistration;
|
|
160
|
+
/** Submit work to a service; streams progress to `onProgress`, resolves with the result. */
|
|
161
|
+
requestService(projectId: string, serviceId: string, data: any, timeout?: number, onProgress?: (progress: any) => void): Promise<any>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface ProjectsBundle {
|
|
165
|
+
addWriter(id: string, userId: string): Promise<any>;
|
|
166
|
+
removeWriter(id: string, userId: string): Promise<any>;
|
|
167
|
+
addReader(id: string, userId: string): Promise<any>;
|
|
168
|
+
removeReader(id: string, userId: string): Promise<any>;
|
|
169
|
+
setConfig(id: string, namespace: string, configKey: string, configValue: any): Promise<any>;
|
|
170
|
+
deleteConfig(id: string, namespace: string, configKey: string): Promise<any>;
|
|
171
|
+
addMaintainer(id: string, userId: string): Promise<any>;
|
|
172
|
+
removeMaintainer(id: string, userId: string): Promise<any>;
|
|
173
|
+
audit(projectId: string, startTime?: string, endTime?: string, asOf?: string): Promise<any[]>;
|
|
174
|
+
linkVocab(id: string, vocabId: string): Promise<any>;
|
|
175
|
+
unlinkVocab(id: string, vocabId: string): Promise<any>;
|
|
176
|
+
get(id: string, asOf?: string): Promise<any>;
|
|
177
|
+
listDocuments(id: string): Promise<any[]>;
|
|
178
|
+
listDocumentsPage(id: string, opts?: { limit?: number; cursor?: string }): Promise<Page>;
|
|
179
|
+
iterDocuments(id: string, opts?: { pageSize?: number }): AsyncGenerator<any[]>;
|
|
180
|
+
delete(id: string): Promise<any>;
|
|
181
|
+
update(id: string, name: string): Promise<any>;
|
|
182
|
+
list(asOf?: string): Promise<any[]>;
|
|
183
|
+
listPage(opts?: { limit?: number; cursor?: string; asOf?: string }): Promise<Page>;
|
|
184
|
+
iterPages(opts?: { pageSize?: number; asOf?: string }): AsyncGenerator<any[]>;
|
|
185
|
+
create(name: string): Promise<any>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
interface TextLayersBundle {
|
|
189
|
+
setConfig(textLayerId: string, namespace: string, configKey: string, configValue: any): Promise<any>;
|
|
190
|
+
deleteConfig(textLayerId: string, namespace: string, configKey: string): Promise<any>;
|
|
191
|
+
get(textLayerId: string, asOf?: string): Promise<any>;
|
|
192
|
+
delete(textLayerId: string): Promise<any>;
|
|
193
|
+
update(textLayerId: string, name: string): Promise<any>;
|
|
194
|
+
shift(textLayerId: string, direction: string): Promise<any>;
|
|
195
|
+
create(projectId: string, name: string): Promise<any>;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface VocabItemsBundle {
|
|
199
|
+
setMetadata(id: string, body: any): Promise<any>;
|
|
200
|
+
deleteMetadata(id: string): Promise<any>;
|
|
201
|
+
create(vocabLayerId: string, form: string, metadata?: any): Promise<any>;
|
|
202
|
+
get(id: string, asOf?: string): Promise<any>;
|
|
203
|
+
delete(id: string): Promise<any>;
|
|
204
|
+
update(id: string, form: string): Promise<any>;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface RelationLayersBundle {
|
|
208
|
+
shift(relationLayerId: string, direction: string): Promise<any>;
|
|
209
|
+
create(spanLayerId: string, name: string): Promise<any>;
|
|
210
|
+
setConfig(relationLayerId: string, namespace: string, configKey: string, configValue: any): Promise<any>;
|
|
211
|
+
deleteConfig(relationLayerId: string, namespace: string, configKey: string): Promise<any>;
|
|
212
|
+
get(relationLayerId: string, asOf?: string): Promise<any>;
|
|
213
|
+
delete(relationLayerId: string): Promise<any>;
|
|
214
|
+
update(relationLayerId: string, name: string): Promise<any>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface TokensBundle {
|
|
218
|
+
create(tokenLayerId: string, text: string, begin: number, end: number, precedence?: number | null, metadata?: any): Promise<any>;
|
|
219
|
+
get(tokenId: string, asOf?: string): Promise<any>;
|
|
220
|
+
delete(tokenId: string): Promise<any>;
|
|
221
|
+
update(tokenId: string, begin?: number, end?: number, precedence?: number | null): Promise<any>;
|
|
222
|
+
bulkCreate(body: any[]): Promise<any>;
|
|
223
|
+
bulkDelete(body: any[]): Promise<any>;
|
|
224
|
+
split(tokenId: string, position: number): Promise<any>;
|
|
225
|
+
merge(tokenId: string, otherTokenId: string): Promise<any>;
|
|
226
|
+
shift(tokenId: string, begin?: number, end?: number): Promise<any>;
|
|
227
|
+
setMetadata(tokenId: string, body: any): Promise<any>;
|
|
228
|
+
deleteMetadata(tokenId: string): Promise<any>;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface PlaidClientOptions {
|
|
232
|
+
/** Per-request timeout in ms (default 30000; 0 or null disables it). */
|
|
233
|
+
timeout?: number | null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export declare class PlaidClient {
|
|
237
|
+
constructor(baseUrl: string, token: string, options?: PlaidClientOptions);
|
|
238
|
+
static login(baseUrl: string, userId: string, password: string, options?: PlaidClientOptions): Promise<PlaidClient>;
|
|
239
|
+
timeout: number | null;
|
|
240
|
+
|
|
241
|
+
// Batch control methods
|
|
242
|
+
beginBatch(): void;
|
|
243
|
+
submitBatch(): Promise<any[]>;
|
|
244
|
+
abortBatch(): void;
|
|
245
|
+
isBatchMode(): boolean;
|
|
246
|
+
|
|
247
|
+
// Strict mode methods
|
|
248
|
+
enterStrictMode(documentId: string): void;
|
|
249
|
+
exitStrictMode(): void;
|
|
250
|
+
|
|
251
|
+
vocabLinks: VocabLinksBundle;
|
|
252
|
+
vocabLayers: VocabLayersBundle;
|
|
253
|
+
relations: RelationsBundle;
|
|
254
|
+
spanLayers: SpanLayersBundle;
|
|
255
|
+
spans: SpansBundle;
|
|
256
|
+
batch: BatchBundle;
|
|
257
|
+
texts: TextsBundle;
|
|
258
|
+
users: UsersBundle;
|
|
259
|
+
apiTokens: ApiTokensBundle;
|
|
260
|
+
tokenLayers: TokenLayersBundle;
|
|
261
|
+
documents: DocumentsBundle;
|
|
262
|
+
messages: MessagesBundle;
|
|
263
|
+
projects: ProjectsBundle;
|
|
264
|
+
textLayers: TextLayersBundle;
|
|
265
|
+
vocabItems: VocabItemsBundle;
|
|
266
|
+
relationLayers: RelationLayersBundle;
|
|
267
|
+
tokens: TokensBundle;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export default PlaidClient;
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@larc-iu/plaid-client",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "JavaScript client for the Plaid annotation API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/larc-iu/plaid.git",
|
|
9
|
+
"directory": "plaid-client-js"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/larc-iu/plaid#readme",
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"main": "src/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"src/",
|
|
24
|
+
"index.d.ts"
|
|
25
|
+
]
|
|
26
|
+
}
|
package/src/http.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { transformRequest, transformResponse } from './transforms.js';
|
|
2
|
+
|
|
3
|
+
// Default per-request timeout (ms). Applied to every request unless the client
|
|
4
|
+
// is constructed with a different `timeout` (0 / null disables it). Note: this
|
|
5
|
+
// also bounds media up/downloads — bump it (or disable) for very large files.
|
|
6
|
+
export const DEFAULT_TIMEOUT_MS = 30000;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract and update document versions from response headers and body.
|
|
10
|
+
*/
|
|
11
|
+
export function extractDocumentVersions(client, responseHeaders, responseBody = null) {
|
|
12
|
+
const docVersionsHeader = responseHeaders.get('X-Document-Versions');
|
|
13
|
+
if (docVersionsHeader) {
|
|
14
|
+
try {
|
|
15
|
+
const versionsMap = JSON.parse(docVersionsHeader);
|
|
16
|
+
if (typeof versionsMap === 'object' && versionsMap !== null) {
|
|
17
|
+
// Clone once, then assign — cloning inside the loop is O(n²) and pointless.
|
|
18
|
+
client.documentVersions = { ...client.documentVersions, ...versionsMap };
|
|
19
|
+
}
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.warn('Failed to parse document versions header:', e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (responseBody && typeof responseBody === 'object') {
|
|
26
|
+
if (responseBody['document/id'] && responseBody['document/version']) {
|
|
27
|
+
client.documentVersions = { ...client.documentVersions };
|
|
28
|
+
client.documentVersions[responseBody['document/id']] = responseBody['document/version'];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read a failed response's body as parsed JSON, falling back to text.
|
|
35
|
+
*/
|
|
36
|
+
export async function parseErrorBody(response) {
|
|
37
|
+
try {
|
|
38
|
+
return await response.json();
|
|
39
|
+
} catch (_) {
|
|
40
|
+
return { message: await response.text().catch(() => 'Unable to read error response') };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create an enriched error from a failed HTTP response.
|
|
46
|
+
*/
|
|
47
|
+
export function makeHttpError(response, errorData, url, method) {
|
|
48
|
+
const serverMessage = errorData?.error || errorData?.message || response.statusText || 'Unknown error';
|
|
49
|
+
const error = new Error(`HTTP ${response.status} ${serverMessage} at ${url}`);
|
|
50
|
+
error.status = response.status;
|
|
51
|
+
error.statusText = response.statusText;
|
|
52
|
+
error.url = url;
|
|
53
|
+
error.method = method;
|
|
54
|
+
error.responseData = errorData;
|
|
55
|
+
return error;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a network error (status 0). Timeout aborts get a clearer message.
|
|
60
|
+
*/
|
|
61
|
+
export function makeNetworkError(originalError, url, method) {
|
|
62
|
+
const timedOut = originalError?.name === 'TimeoutError' || originalError?.name === 'AbortError';
|
|
63
|
+
const message = timedOut
|
|
64
|
+
? `Request timed out at ${url}`
|
|
65
|
+
: `Network error: ${originalError.message} at ${url}`;
|
|
66
|
+
const error = new Error(message);
|
|
67
|
+
error.status = 0;
|
|
68
|
+
error.url = url;
|
|
69
|
+
error.method = method;
|
|
70
|
+
error.originalError = originalError;
|
|
71
|
+
return error;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build a fetch AbortSignal that fires after `timeout` ms, or undefined when
|
|
76
|
+
* timeouts are disabled / unsupported.
|
|
77
|
+
*/
|
|
78
|
+
export function timeoutSignal(timeout) {
|
|
79
|
+
if (timeout && timeout > 0 && typeof AbortSignal !== 'undefined' && AbortSignal.timeout) {
|
|
80
|
+
return AbortSignal.timeout(timeout);
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Generic request method handling all fetch logic.
|
|
87
|
+
*
|
|
88
|
+
* Options:
|
|
89
|
+
* body - Object body, run through transformRequest
|
|
90
|
+
* rawBody - Body value passed directly (no transform). Mutually exclusive with body.
|
|
91
|
+
* formData - If true, body is FormData; skip Content-Type header
|
|
92
|
+
* queryParams - Object of query param key/values to append
|
|
93
|
+
* noBatch - If true, throw when in batch mode
|
|
94
|
+
* skipResponseTransform - Return raw parsed JSON (no transformResponse)
|
|
95
|
+
* noAuth - Skip Authorization header
|
|
96
|
+
* binaryResponse - Return arrayBuffer instead of JSON/text
|
|
97
|
+
*/
|
|
98
|
+
export async function makeRequest(client, method, path, options = {}) {
|
|
99
|
+
const {
|
|
100
|
+
body,
|
|
101
|
+
rawBody,
|
|
102
|
+
formData,
|
|
103
|
+
queryParams,
|
|
104
|
+
noBatch,
|
|
105
|
+
skipResponseTransform,
|
|
106
|
+
noAuth,
|
|
107
|
+
binaryResponse,
|
|
108
|
+
} = options;
|
|
109
|
+
|
|
110
|
+
// Build URL
|
|
111
|
+
let url = `${client.baseUrl}${path}`;
|
|
112
|
+
|
|
113
|
+
// Append query params
|
|
114
|
+
if (queryParams) {
|
|
115
|
+
const params = new URLSearchParams();
|
|
116
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
117
|
+
if (value !== undefined && value !== null) {
|
|
118
|
+
// URLSearchParams stringifies booleans to lowercase 'true'/'false',
|
|
119
|
+
// which the server's malli coercion requires (the Python client does
|
|
120
|
+
// this conversion explicitly).
|
|
121
|
+
params.append(key, value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const qs = params.toString();
|
|
125
|
+
if (qs) url += `?${qs}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Prepare request body
|
|
129
|
+
let requestBody;
|
|
130
|
+
if (formData) {
|
|
131
|
+
requestBody = body; // FormData passed directly
|
|
132
|
+
} else if (rawBody !== undefined) {
|
|
133
|
+
requestBody = rawBody;
|
|
134
|
+
} else if (body !== undefined) {
|
|
135
|
+
requestBody = transformRequest(body);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Strict mode: append document-version for non-GET requests
|
|
139
|
+
if (client.strictModeDocumentId && method !== 'GET') {
|
|
140
|
+
const docId = client.strictModeDocumentId;
|
|
141
|
+
if (client.documentVersions[docId]) {
|
|
142
|
+
const docVersion = client.documentVersions[docId];
|
|
143
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
144
|
+
url += `${separator}document-version=${encodeURIComponent(docVersion)}`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Batch mode
|
|
149
|
+
if (client.isBatching) {
|
|
150
|
+
if (noBatch) {
|
|
151
|
+
throw new Error(`This endpoint cannot be used in batch mode: ${path}`);
|
|
152
|
+
}
|
|
153
|
+
const operation = {
|
|
154
|
+
path: url.replace(client.baseUrl, ''),
|
|
155
|
+
method: method.toUpperCase(),
|
|
156
|
+
};
|
|
157
|
+
if (requestBody !== undefined) {
|
|
158
|
+
operation.body = requestBody;
|
|
159
|
+
}
|
|
160
|
+
client.batchOperations.push(operation);
|
|
161
|
+
return { batched: true };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Build fetch options
|
|
165
|
+
const headers = {};
|
|
166
|
+
if (!noAuth) {
|
|
167
|
+
headers['Authorization'] = `Bearer ${client.token}`;
|
|
168
|
+
}
|
|
169
|
+
if (!formData) {
|
|
170
|
+
headers['Content-Type'] = 'application/json';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const fetchOptions = { method, headers };
|
|
174
|
+
if (requestBody !== undefined) {
|
|
175
|
+
fetchOptions.body = formData ? requestBody : JSON.stringify(requestBody);
|
|
176
|
+
}
|
|
177
|
+
const signal = timeoutSignal(client.timeout);
|
|
178
|
+
if (signal) fetchOptions.signal = signal;
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const response = await fetch(url, fetchOptions);
|
|
182
|
+
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
throw makeHttpError(response, await parseErrorBody(response), url, method);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Binary response (getMedia)
|
|
188
|
+
if (binaryResponse) {
|
|
189
|
+
extractDocumentVersions(client, response.headers);
|
|
190
|
+
return await response.arrayBuffer();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// JSON or text response
|
|
194
|
+
const contentType = response.headers.get('content-type');
|
|
195
|
+
if (contentType && contentType.includes('application/json')) {
|
|
196
|
+
const data = await response.json();
|
|
197
|
+
extractDocumentVersions(client, response.headers, data);
|
|
198
|
+
if (skipResponseTransform) {
|
|
199
|
+
return data;
|
|
200
|
+
}
|
|
201
|
+
return transformResponse(data);
|
|
202
|
+
} else {
|
|
203
|
+
extractDocumentVersions(client, response.headers);
|
|
204
|
+
return await response.text();
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error.status !== undefined) {
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
throw makeNetworkError(error, url, method);
|
|
211
|
+
}
|
|
212
|
+
}
|