@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/src/index.js
ADDED
|
@@ -0,0 +1,1532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plaid-client - JavaScript client for the Plaid annotation API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { transformRequest, transformResponse } from './transforms.js';
|
|
6
|
+
import {
|
|
7
|
+
makeRequest, extractDocumentVersions, parseErrorBody, makeHttpError,
|
|
8
|
+
makeNetworkError, timeoutSignal, DEFAULT_TIMEOUT_MS,
|
|
9
|
+
} from './http.js';
|
|
10
|
+
import { listAll, listPage, iterPages } from './pagination.js';
|
|
11
|
+
import { createSSEConnection } from './sse.js';
|
|
12
|
+
import {
|
|
13
|
+
discoverServices,
|
|
14
|
+
serve,
|
|
15
|
+
requestService,
|
|
16
|
+
} from './services.js';
|
|
17
|
+
|
|
18
|
+
// Helper: build body object, filtering out undefined values
|
|
19
|
+
function bodyOf(obj) {
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
22
|
+
if (value !== undefined) result[key] = value;
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class PlaidClient {
|
|
28
|
+
/**
|
|
29
|
+
* Create a new PlaidClient instance
|
|
30
|
+
* @param {string} baseUrl - The base URL for the API
|
|
31
|
+
* @param {string} token - The authentication token
|
|
32
|
+
* @param {object} [options] - Client options
|
|
33
|
+
* @param {number} [options.timeout=30000] - Per-request timeout in ms (0 or null disables it)
|
|
34
|
+
*/
|
|
35
|
+
constructor(baseUrl, token, options = {}) {
|
|
36
|
+
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
37
|
+
this.token = token;
|
|
38
|
+
this.timeout = options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS;
|
|
39
|
+
this.isBatching = false;
|
|
40
|
+
this.batchOperations = [];
|
|
41
|
+
this.documentVersions = {};
|
|
42
|
+
this.strictModeDocumentId = null;
|
|
43
|
+
|
|
44
|
+
// --- API Bundles ---
|
|
45
|
+
|
|
46
|
+
this.vocabLinks = {
|
|
47
|
+
/**
|
|
48
|
+
* Create a new vocab link between tokens and a vocab item.
|
|
49
|
+
* @param {string} vocabItem - The vocab item to link
|
|
50
|
+
* @param {Array} tokens - The tokens to link
|
|
51
|
+
* @param {any} [metadata] - Metadata for the link. Omit to leave unset; pass null to send JSON null.
|
|
52
|
+
*/
|
|
53
|
+
create: (vocabItem, tokens, metadata) =>
|
|
54
|
+
this._request('POST', '/api/v1/vocab-links', {
|
|
55
|
+
body: bodyOf({ 'vocab-item': vocabItem, tokens, metadata }),
|
|
56
|
+
}),
|
|
57
|
+
/**
|
|
58
|
+
* Replace all metadata for a vocab link. The entire metadata map is replaced - existing metadata keys not included in the request will be removed.
|
|
59
|
+
* @param {string} id - The resource ID
|
|
60
|
+
* @param {any} body - The request body
|
|
61
|
+
*/
|
|
62
|
+
setMetadata: (id, body) =>
|
|
63
|
+
this._request('PUT', `/api/v1/vocab-links/${id}/metadata`, {
|
|
64
|
+
rawBody: body, skipResponseTransform: true,
|
|
65
|
+
}),
|
|
66
|
+
/**
|
|
67
|
+
* Remove all metadata from a vocab link.
|
|
68
|
+
* @param {string} id - The resource ID
|
|
69
|
+
*/
|
|
70
|
+
deleteMetadata: (id) =>
|
|
71
|
+
this._request('DELETE', `/api/v1/vocab-links/${id}/metadata`, {
|
|
72
|
+
skipResponseTransform: true,
|
|
73
|
+
}),
|
|
74
|
+
/**
|
|
75
|
+
* Get a vocab link by ID
|
|
76
|
+
* @param {string} id - The resource ID
|
|
77
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
78
|
+
*/
|
|
79
|
+
get: (id, asOf) =>
|
|
80
|
+
this._request('GET', `/api/v1/vocab-links/${id}`, {
|
|
81
|
+
queryParams: { 'as-of': asOf },
|
|
82
|
+
}),
|
|
83
|
+
/**
|
|
84
|
+
* Delete a vocab link
|
|
85
|
+
* @param {string} id - The resource ID
|
|
86
|
+
*/
|
|
87
|
+
delete: (id) =>
|
|
88
|
+
this._request('DELETE', `/api/v1/vocab-links/${id}`),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
this.vocabLayers = {
|
|
92
|
+
/**
|
|
93
|
+
* Get a vocab layer by ID
|
|
94
|
+
* @param {string} id - The resource ID
|
|
95
|
+
* @param {boolean} [includeItems] - Include vocab items
|
|
96
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
97
|
+
*/
|
|
98
|
+
get: (id, includeItems, asOf) =>
|
|
99
|
+
this._request('GET', `/api/v1/vocab-layers/${id}`, {
|
|
100
|
+
queryParams: { 'include-items': includeItems, 'as-of': asOf },
|
|
101
|
+
}),
|
|
102
|
+
/**
|
|
103
|
+
* Delete a vocab layer.
|
|
104
|
+
* @param {string} id - The resource ID
|
|
105
|
+
*/
|
|
106
|
+
delete: (id) =>
|
|
107
|
+
this._request('DELETE', `/api/v1/vocab-layers/${id}`),
|
|
108
|
+
/**
|
|
109
|
+
* Update a vocab layer's name.
|
|
110
|
+
* @param {string} id - The resource ID
|
|
111
|
+
* @param {string} name - The name
|
|
112
|
+
*/
|
|
113
|
+
update: (id, name) =>
|
|
114
|
+
this._request('PATCH', `/api/v1/vocab-layers/${id}`, {
|
|
115
|
+
body: bodyOf({ name }),
|
|
116
|
+
}),
|
|
117
|
+
/**
|
|
118
|
+
* Set a configuration value for a layer in an editor namespace.
|
|
119
|
+
* @param {string} id - The resource ID
|
|
120
|
+
* @param {string} namespace - The config namespace
|
|
121
|
+
* @param {string} configKey - The config key
|
|
122
|
+
* @param {any} configValue - Configuration value to set
|
|
123
|
+
*/
|
|
124
|
+
setConfig: (id, namespace, configKey, configValue) =>
|
|
125
|
+
this._request('PUT', `/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`, {
|
|
126
|
+
rawBody: configValue, skipResponseTransform: true,
|
|
127
|
+
}),
|
|
128
|
+
/**
|
|
129
|
+
* Remove a configuration value for a layer.
|
|
130
|
+
* @param {string} id - The resource ID
|
|
131
|
+
* @param {string} namespace - The config namespace
|
|
132
|
+
* @param {string} configKey - The config key
|
|
133
|
+
*/
|
|
134
|
+
deleteConfig: (id, namespace, configKey) =>
|
|
135
|
+
this._request('DELETE', `/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`, {
|
|
136
|
+
skipResponseTransform: true,
|
|
137
|
+
}),
|
|
138
|
+
/**
|
|
139
|
+
* List all vocab layers accessible to user. Transparently follows
|
|
140
|
+
* pagination cursors and returns the full flat array.
|
|
141
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
142
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
143
|
+
*/
|
|
144
|
+
list: (asOf) =>
|
|
145
|
+
listAll(this, '/api/v1/vocab-layers', { query: { 'as-of': asOf } }),
|
|
146
|
+
/**
|
|
147
|
+
* Fetch a single page of vocab layers.
|
|
148
|
+
* @param {object} [opts]
|
|
149
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
150
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
151
|
+
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
152
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
153
|
+
*/
|
|
154
|
+
listPage: ({ limit, cursor, asOf } = {}) =>
|
|
155
|
+
listPage(this, '/api/v1/vocab-layers', { limit, cursor, query: { 'as-of': asOf } }),
|
|
156
|
+
/**
|
|
157
|
+
* Async-iterate vocab layers page by page; yields each page's entries array.
|
|
158
|
+
* @param {object} [opts]
|
|
159
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
160
|
+
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
161
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
162
|
+
* @returns {AsyncGenerator<Array>}
|
|
163
|
+
*/
|
|
164
|
+
iterPages: ({ pageSize, asOf } = {}) =>
|
|
165
|
+
iterPages(this, '/api/v1/vocab-layers', { pageSize, query: { 'as-of': asOf } }),
|
|
166
|
+
/**
|
|
167
|
+
* Create a new vocab layer. Note: this also registers the user as a maintainer.
|
|
168
|
+
* @param {string} name - The name
|
|
169
|
+
*/
|
|
170
|
+
create: (name) =>
|
|
171
|
+
this._request('POST', '/api/v1/vocab-layers', {
|
|
172
|
+
body: bodyOf({ name }),
|
|
173
|
+
}),
|
|
174
|
+
/**
|
|
175
|
+
* Assign a user as a maintainer for this vocab layer.
|
|
176
|
+
* @param {string} id - The resource ID
|
|
177
|
+
* @param {string} userId - The user ID
|
|
178
|
+
*/
|
|
179
|
+
addMaintainer: (id, userId) =>
|
|
180
|
+
this._request('POST', `/api/v1/vocab-layers/${id}/maintainers/${userId}`),
|
|
181
|
+
/**
|
|
182
|
+
* Remove a user's maintainer privileges for this vocab layer.
|
|
183
|
+
* @param {string} id - The resource ID
|
|
184
|
+
* @param {string} userId - The user ID
|
|
185
|
+
*/
|
|
186
|
+
removeMaintainer: (id, userId) =>
|
|
187
|
+
this._request('DELETE', `/api/v1/vocab-layers/${id}/maintainers/${userId}`),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
this.relations = {
|
|
191
|
+
/**
|
|
192
|
+
* Replace all metadata for a relation.
|
|
193
|
+
* @param {string} relationId - The relation ID
|
|
194
|
+
* @param {any} body - The request body
|
|
195
|
+
*/
|
|
196
|
+
setMetadata: (relationId, body) =>
|
|
197
|
+
this._request('PUT', `/api/v1/relations/${relationId}/metadata`, {
|
|
198
|
+
rawBody: body, skipResponseTransform: true,
|
|
199
|
+
}),
|
|
200
|
+
/**
|
|
201
|
+
* Remove all metadata from a relation.
|
|
202
|
+
* @param {string} relationId - The relation ID
|
|
203
|
+
*/
|
|
204
|
+
deleteMetadata: (relationId) =>
|
|
205
|
+
this._request('DELETE', `/api/v1/relations/${relationId}/metadata`, {
|
|
206
|
+
skipResponseTransform: true,
|
|
207
|
+
}),
|
|
208
|
+
/**
|
|
209
|
+
* Update the target span of a relation.
|
|
210
|
+
* @param {string} relationId - The relation ID
|
|
211
|
+
* @param {string} spanId - The span ID
|
|
212
|
+
*/
|
|
213
|
+
setTarget: (relationId, spanId) =>
|
|
214
|
+
this._request('PUT', `/api/v1/relations/${relationId}/target`, {
|
|
215
|
+
body: bodyOf({ 'span-id': spanId }),
|
|
216
|
+
}),
|
|
217
|
+
/**
|
|
218
|
+
* Get a relation by ID.
|
|
219
|
+
* @param {string} relationId - The relation ID
|
|
220
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
221
|
+
*/
|
|
222
|
+
get: (relationId, asOf) =>
|
|
223
|
+
this._request('GET', `/api/v1/relations/${relationId}`, {
|
|
224
|
+
queryParams: { 'as-of': asOf },
|
|
225
|
+
}),
|
|
226
|
+
/**
|
|
227
|
+
* Delete a relation.
|
|
228
|
+
* @param {string} relationId - The relation ID
|
|
229
|
+
*/
|
|
230
|
+
delete: (relationId) =>
|
|
231
|
+
this._request('DELETE', `/api/v1/relations/${relationId}`),
|
|
232
|
+
/**
|
|
233
|
+
* Update a relation's value.
|
|
234
|
+
* @param {string} relationId - The relation ID
|
|
235
|
+
* @param {any} value - The value
|
|
236
|
+
*/
|
|
237
|
+
update: (relationId, value) =>
|
|
238
|
+
this._request('PATCH', `/api/v1/relations/${relationId}`, {
|
|
239
|
+
body: bodyOf({ value }),
|
|
240
|
+
}),
|
|
241
|
+
/**
|
|
242
|
+
* Update the source span of a relation.
|
|
243
|
+
* @param {string} relationId - The relation ID
|
|
244
|
+
* @param {string} spanId - The span ID
|
|
245
|
+
*/
|
|
246
|
+
setSource: (relationId, spanId) =>
|
|
247
|
+
this._request('PUT', `/api/v1/relations/${relationId}/source`, {
|
|
248
|
+
body: bodyOf({ 'span-id': spanId }),
|
|
249
|
+
}),
|
|
250
|
+
/**
|
|
251
|
+
* Create a new relation. A relation is a directed edge between two spans
|
|
252
|
+
* with a value, useful for expressing phenomena such as syntactic or
|
|
253
|
+
* semantic relations.
|
|
254
|
+
* @param {string} layerId - The relation layer ID
|
|
255
|
+
* @param {string} sourceId - The source span ID
|
|
256
|
+
* @param {string} targetId - The target span ID
|
|
257
|
+
* @param {any} value - The value
|
|
258
|
+
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
259
|
+
*/
|
|
260
|
+
create: (layerId, sourceId, targetId, value, metadata) =>
|
|
261
|
+
this._request('POST', '/api/v1/relations', {
|
|
262
|
+
body: bodyOf({ 'layer-id': layerId, 'source-id': sourceId, 'target-id': targetId, value, metadata }),
|
|
263
|
+
}),
|
|
264
|
+
/**
|
|
265
|
+
* Create multiple relations in a single operation.
|
|
266
|
+
* @param {Array} body - The request body
|
|
267
|
+
*/
|
|
268
|
+
bulkCreate: (body) =>
|
|
269
|
+
this._request('POST', '/api/v1/relations/bulk', { body }),
|
|
270
|
+
/**
|
|
271
|
+
* Delete multiple relations in a single operation. Provide an array of IDs.
|
|
272
|
+
* @param {Array} body - The request body
|
|
273
|
+
*/
|
|
274
|
+
bulkDelete: (body) =>
|
|
275
|
+
this._request('DELETE', '/api/v1/relations/bulk', { body }),
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
this.spanLayers = {
|
|
279
|
+
/**
|
|
280
|
+
* Set a configuration value for a layer in an editor namespace.
|
|
281
|
+
* @param {string} spanLayerId - The span layer ID
|
|
282
|
+
* @param {string} namespace - The config namespace
|
|
283
|
+
* @param {string} configKey - The config key
|
|
284
|
+
* @param {any} configValue - Configuration value to set
|
|
285
|
+
*/
|
|
286
|
+
setConfig: (spanLayerId, namespace, configKey, configValue) =>
|
|
287
|
+
this._request('PUT', `/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`, {
|
|
288
|
+
rawBody: configValue, skipResponseTransform: true,
|
|
289
|
+
}),
|
|
290
|
+
/**
|
|
291
|
+
* Remove a configuration value for a layer.
|
|
292
|
+
* @param {string} spanLayerId - The span layer ID
|
|
293
|
+
* @param {string} namespace - The config namespace
|
|
294
|
+
* @param {string} configKey - The config key
|
|
295
|
+
*/
|
|
296
|
+
deleteConfig: (spanLayerId, namespace, configKey) =>
|
|
297
|
+
this._request('DELETE', `/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`, {
|
|
298
|
+
skipResponseTransform: true,
|
|
299
|
+
}),
|
|
300
|
+
/**
|
|
301
|
+
* Get a span layer by ID.
|
|
302
|
+
* @param {string} spanLayerId - The span layer ID
|
|
303
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
304
|
+
*/
|
|
305
|
+
get: (spanLayerId, asOf) =>
|
|
306
|
+
this._request('GET', `/api/v1/span-layers/${spanLayerId}`, {
|
|
307
|
+
queryParams: { 'as-of': asOf },
|
|
308
|
+
}),
|
|
309
|
+
/**
|
|
310
|
+
* Delete a span layer.
|
|
311
|
+
* @param {string} spanLayerId - The span layer ID
|
|
312
|
+
*/
|
|
313
|
+
delete: (spanLayerId) =>
|
|
314
|
+
this._request('DELETE', `/api/v1/span-layers/${spanLayerId}`),
|
|
315
|
+
/**
|
|
316
|
+
* Update a span layer's name.
|
|
317
|
+
* @param {string} spanLayerId - The span layer ID
|
|
318
|
+
* @param {string} name - The name
|
|
319
|
+
*/
|
|
320
|
+
update: (spanLayerId, name) =>
|
|
321
|
+
this._request('PATCH', `/api/v1/span-layers/${spanLayerId}`, {
|
|
322
|
+
body: bodyOf({ name }),
|
|
323
|
+
}),
|
|
324
|
+
/**
|
|
325
|
+
* Create a new span layer.
|
|
326
|
+
* @param {string} tokenLayerId - The token layer ID
|
|
327
|
+
* @param {string} name - The name
|
|
328
|
+
*/
|
|
329
|
+
create: (tokenLayerId, name) =>
|
|
330
|
+
this._request('POST', '/api/v1/span-layers', {
|
|
331
|
+
body: bodyOf({ 'token-layer-id': tokenLayerId, name }),
|
|
332
|
+
}),
|
|
333
|
+
/**
|
|
334
|
+
* Shift a span layer's display order.
|
|
335
|
+
* @param {string} spanLayerId - The span layer ID
|
|
336
|
+
* @param {string} direction - The direction ("up" or "down")
|
|
337
|
+
*/
|
|
338
|
+
shift: (spanLayerId, direction) =>
|
|
339
|
+
this._request('POST', `/api/v1/span-layers/${spanLayerId}/shift`, {
|
|
340
|
+
body: bodyOf({ direction }),
|
|
341
|
+
}),
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
this.spans = {
|
|
345
|
+
/**
|
|
346
|
+
* Replace tokens for a span.
|
|
347
|
+
* @param {string} spanId - The span ID
|
|
348
|
+
* @param {Array} tokens - The tokens
|
|
349
|
+
*/
|
|
350
|
+
setTokens: (spanId, tokens) =>
|
|
351
|
+
this._request('PUT', `/api/v1/spans/${spanId}/tokens`, {
|
|
352
|
+
body: bodyOf({ tokens }),
|
|
353
|
+
}),
|
|
354
|
+
/**
|
|
355
|
+
* Create a new span. A span holds a primary atomic value and optional
|
|
356
|
+
* metadata, and must at all times be associated with one or more tokens.
|
|
357
|
+
* @param {string} spanLayerId - The span layer ID
|
|
358
|
+
* @param {Array} tokens - The tokens
|
|
359
|
+
* @param {any} value - The value
|
|
360
|
+
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
361
|
+
*/
|
|
362
|
+
create: (spanLayerId, tokens, value, metadata) =>
|
|
363
|
+
this._request('POST', '/api/v1/spans', {
|
|
364
|
+
body: bodyOf({ 'span-layer-id': spanLayerId, tokens, value, metadata }),
|
|
365
|
+
}),
|
|
366
|
+
/**
|
|
367
|
+
* Get a span by ID.
|
|
368
|
+
* @param {string} spanId - The span ID
|
|
369
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
370
|
+
*/
|
|
371
|
+
get: (spanId, asOf) =>
|
|
372
|
+
this._request('GET', `/api/v1/spans/${spanId}`, {
|
|
373
|
+
queryParams: { 'as-of': asOf },
|
|
374
|
+
}),
|
|
375
|
+
/**
|
|
376
|
+
* Delete a span.
|
|
377
|
+
* @param {string} spanId - The span ID
|
|
378
|
+
*/
|
|
379
|
+
delete: (spanId) =>
|
|
380
|
+
this._request('DELETE', `/api/v1/spans/${spanId}`),
|
|
381
|
+
/**
|
|
382
|
+
* Update a span's value.
|
|
383
|
+
* @param {string} spanId - The span ID
|
|
384
|
+
* @param {any} value - The value
|
|
385
|
+
*/
|
|
386
|
+
update: (spanId, value) =>
|
|
387
|
+
this._request('PATCH', `/api/v1/spans/${spanId}`, {
|
|
388
|
+
body: bodyOf({ value }),
|
|
389
|
+
}),
|
|
390
|
+
/**
|
|
391
|
+
* Create multiple spans in a single operation.
|
|
392
|
+
* @param {Array} body - The request body
|
|
393
|
+
*/
|
|
394
|
+
bulkCreate: (body) =>
|
|
395
|
+
this._request('POST', '/api/v1/spans/bulk', { body }),
|
|
396
|
+
/**
|
|
397
|
+
* Delete multiple spans in a single operation. Provide an array of IDs.
|
|
398
|
+
* @param {Array} body - The request body
|
|
399
|
+
*/
|
|
400
|
+
bulkDelete: (body) =>
|
|
401
|
+
this._request('DELETE', '/api/v1/spans/bulk', { body }),
|
|
402
|
+
/**
|
|
403
|
+
* Replace all metadata for a span.
|
|
404
|
+
* @param {string} spanId - The span ID
|
|
405
|
+
* @param {any} body - The request body
|
|
406
|
+
*/
|
|
407
|
+
setMetadata: (spanId, body) =>
|
|
408
|
+
this._request('PUT', `/api/v1/spans/${spanId}/metadata`, {
|
|
409
|
+
rawBody: body, skipResponseTransform: true,
|
|
410
|
+
}),
|
|
411
|
+
/**
|
|
412
|
+
* Remove all metadata from a span.
|
|
413
|
+
* @param {string} spanId - The span ID
|
|
414
|
+
*/
|
|
415
|
+
deleteMetadata: (spanId) =>
|
|
416
|
+
this._request('DELETE', `/api/v1/spans/${spanId}/metadata`, {
|
|
417
|
+
skipResponseTransform: true,
|
|
418
|
+
}),
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
this.batch = {
|
|
422
|
+
/**
|
|
423
|
+
* Execute multiple API operations atomically. If any operation fails, all
|
|
424
|
+
* changes are rolled back.
|
|
425
|
+
* @param {Array} body - The request body
|
|
426
|
+
*/
|
|
427
|
+
submit: (body) =>
|
|
428
|
+
this._request('POST', '/api/v1/batch', {
|
|
429
|
+
body, noBatch: true,
|
|
430
|
+
}),
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
this.texts = {
|
|
434
|
+
/**
|
|
435
|
+
* Replace all metadata for a text.
|
|
436
|
+
* @param {string} textId - The text ID
|
|
437
|
+
* @param {any} body - The request body
|
|
438
|
+
*/
|
|
439
|
+
setMetadata: (textId, body) =>
|
|
440
|
+
this._request('PUT', `/api/v1/texts/${textId}/metadata`, {
|
|
441
|
+
rawBody: body, skipResponseTransform: true,
|
|
442
|
+
}),
|
|
443
|
+
/**
|
|
444
|
+
* Remove all metadata from a text.
|
|
445
|
+
* @param {string} textId - The text ID
|
|
446
|
+
*/
|
|
447
|
+
deleteMetadata: (textId) =>
|
|
448
|
+
this._request('DELETE', `/api/v1/texts/${textId}/metadata`, {
|
|
449
|
+
skipResponseTransform: true,
|
|
450
|
+
}),
|
|
451
|
+
/**
|
|
452
|
+
* Create a new text in a document's text layer. A text is a container for
|
|
453
|
+
* one long string in `body` for a given layer.
|
|
454
|
+
* @param {string} textLayerId - The text layer ID
|
|
455
|
+
* @param {string} documentId - The document ID
|
|
456
|
+
* @param {string} body - The request body
|
|
457
|
+
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
458
|
+
*/
|
|
459
|
+
create: (textLayerId, documentId, body, metadata) =>
|
|
460
|
+
this._request('POST', '/api/v1/texts', {
|
|
461
|
+
body: bodyOf({ 'text-layer-id': textLayerId, 'document-id': documentId, body, metadata }),
|
|
462
|
+
}),
|
|
463
|
+
/**
|
|
464
|
+
* Get a text.
|
|
465
|
+
* @param {string} textId - The text ID
|
|
466
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
467
|
+
*/
|
|
468
|
+
get: (textId, asOf) =>
|
|
469
|
+
this._request('GET', `/api/v1/texts/${textId}`, {
|
|
470
|
+
queryParams: { 'as-of': asOf },
|
|
471
|
+
}),
|
|
472
|
+
/**
|
|
473
|
+
* Delete a text and all dependent data.
|
|
474
|
+
* @param {string} textId - The text ID
|
|
475
|
+
*/
|
|
476
|
+
delete: (textId) =>
|
|
477
|
+
this._request('DELETE', `/api/v1/texts/${textId}`),
|
|
478
|
+
/**
|
|
479
|
+
* Update a text's body. A diff is computed and token indices are updated
|
|
480
|
+
* so that tokens remain intact. Alternatively, `body` can be a list of
|
|
481
|
+
* edit directives.
|
|
482
|
+
* @param {string} textId - The text ID
|
|
483
|
+
* @param {any} body - The request body
|
|
484
|
+
*/
|
|
485
|
+
update: (textId, body) =>
|
|
486
|
+
this._request('PATCH', `/api/v1/texts/${textId}`, {
|
|
487
|
+
body: bodyOf({ body }),
|
|
488
|
+
}),
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
this.users = {
|
|
492
|
+
/**
|
|
493
|
+
* List all users. Transparently follows pagination cursors and returns
|
|
494
|
+
* the full flat array.
|
|
495
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
496
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
497
|
+
*/
|
|
498
|
+
list: (asOf) =>
|
|
499
|
+
listAll(this, '/api/v1/users', { query: { 'as-of': asOf } }),
|
|
500
|
+
/**
|
|
501
|
+
* Fetch a single page of users.
|
|
502
|
+
* @param {object} [opts]
|
|
503
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
504
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
505
|
+
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
506
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
507
|
+
*/
|
|
508
|
+
listPage: ({ limit, cursor, asOf } = {}) =>
|
|
509
|
+
listPage(this, '/api/v1/users', { limit, cursor, query: { 'as-of': asOf } }),
|
|
510
|
+
/**
|
|
511
|
+
* Async-iterate users page by page; yields each page's entries array.
|
|
512
|
+
* @param {object} [opts]
|
|
513
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
514
|
+
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
515
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
516
|
+
* @returns {AsyncGenerator<Array>}
|
|
517
|
+
*/
|
|
518
|
+
iterPages: ({ pageSize, asOf } = {}) =>
|
|
519
|
+
iterPages(this, '/api/v1/users', { pageSize, query: { 'as-of': asOf } }),
|
|
520
|
+
/**
|
|
521
|
+
* Create a new user
|
|
522
|
+
* @param {string} username - The username
|
|
523
|
+
* @param {string} password - The password
|
|
524
|
+
* @param {boolean} isAdmin - Whether the user is an admin
|
|
525
|
+
*/
|
|
526
|
+
create: (username, password, isAdmin) =>
|
|
527
|
+
this._request('POST', '/api/v1/users', {
|
|
528
|
+
body: bodyOf({ username, password, 'is-admin': isAdmin }),
|
|
529
|
+
}),
|
|
530
|
+
/**
|
|
531
|
+
* Get audit log for a user's actions. Transparently follows pagination
|
|
532
|
+
* cursors and returns the full flat array.
|
|
533
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
534
|
+
* @param {string} userId - The user ID
|
|
535
|
+
* @param {string} [startTime] - Start of time range
|
|
536
|
+
* @param {string} [endTime] - End of time range
|
|
537
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
538
|
+
*/
|
|
539
|
+
audit: (userId, startTime, endTime, asOf) =>
|
|
540
|
+
listAll(this, `/api/v1/users/${userId}/audit`, {
|
|
541
|
+
query: { 'start-time': startTime, 'end-time': endTime, 'as-of': asOf },
|
|
542
|
+
}),
|
|
543
|
+
/**
|
|
544
|
+
* Get a user by ID
|
|
545
|
+
* @param {string} id - The resource ID
|
|
546
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
547
|
+
*/
|
|
548
|
+
get: (id, asOf) =>
|
|
549
|
+
this._request('GET', `/api/v1/users/${id}`, {
|
|
550
|
+
queryParams: { 'as-of': asOf },
|
|
551
|
+
}),
|
|
552
|
+
/**
|
|
553
|
+
* Delete a user
|
|
554
|
+
* @param {string} id - The resource ID
|
|
555
|
+
*/
|
|
556
|
+
delete: (id) =>
|
|
557
|
+
this._request('DELETE', `/api/v1/users/${id}`),
|
|
558
|
+
/**
|
|
559
|
+
* Modify a user. Admins may change the username, password, and admin
|
|
560
|
+
* status of any user. All other users may only modify their own username
|
|
561
|
+
* or password.
|
|
562
|
+
* @param {string} id - The resource ID
|
|
563
|
+
* @param {string} [password] - New password
|
|
564
|
+
* @param {string} [username] - New username
|
|
565
|
+
* @param {boolean} [isAdmin] - New admin status
|
|
566
|
+
*/
|
|
567
|
+
update: (id, password, username, isAdmin) =>
|
|
568
|
+
this._request('PATCH', `/api/v1/users/${id}`, {
|
|
569
|
+
body: bodyOf({ password, username, 'is-admin': isAdmin }),
|
|
570
|
+
}),
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
this.apiTokens = {
|
|
574
|
+
/**
|
|
575
|
+
* List a user's named API tokens. Never includes the signed token
|
|
576
|
+
* string itself — that is only returned once, by create().
|
|
577
|
+
* Transparently follows pagination cursors and returns the full flat array.
|
|
578
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
579
|
+
* @param {string} userId - The user ID who owns the tokens
|
|
580
|
+
*/
|
|
581
|
+
list: (userId) =>
|
|
582
|
+
listAll(this, `/api/v1/users/${userId}/tokens`),
|
|
583
|
+
/**
|
|
584
|
+
* Fetch a single page of a user's named API tokens.
|
|
585
|
+
* @param {string} userId - The user ID who owns the tokens
|
|
586
|
+
* @param {object} [opts]
|
|
587
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
588
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
589
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
590
|
+
*/
|
|
591
|
+
listPage: (userId, { limit, cursor } = {}) =>
|
|
592
|
+
listPage(this, `/api/v1/users/${userId}/tokens`, { limit, cursor }),
|
|
593
|
+
/**
|
|
594
|
+
* Async-iterate a user's named API tokens page by page; yields each
|
|
595
|
+
* page's entries array.
|
|
596
|
+
* @param {string} userId - The user ID who owns the tokens
|
|
597
|
+
* @param {object} [opts]
|
|
598
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
599
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
600
|
+
* @returns {AsyncGenerator<Array>}
|
|
601
|
+
*/
|
|
602
|
+
iterPages: (userId, { pageSize } = {}) =>
|
|
603
|
+
iterPages(this, `/api/v1/users/${userId}/tokens`, { pageSize }),
|
|
604
|
+
/**
|
|
605
|
+
* Mint a named API token for a user. The returned `token` is the signed
|
|
606
|
+
* credential and is shown ONLY here — store it immediately. API tokens
|
|
607
|
+
* do not expire and survive password changes / logout; revoke to kill.
|
|
608
|
+
* @param {string} userId - The user ID who will own the token
|
|
609
|
+
* @param {string} name - A human label, e.g. "Stanza Parser"
|
|
610
|
+
* @returns {Promise<{id: string, name: string, token: string}>}
|
|
611
|
+
*/
|
|
612
|
+
create: (userId, name) =>
|
|
613
|
+
this._request('POST', `/api/v1/users/${userId}/tokens`, {
|
|
614
|
+
body: bodyOf({ name }),
|
|
615
|
+
}),
|
|
616
|
+
/**
|
|
617
|
+
* Revoke a named API token (soft-revoke; idempotent).
|
|
618
|
+
* @param {string} userId - The user ID who owns the token
|
|
619
|
+
* @param {string} tokenId - The token ID to revoke
|
|
620
|
+
*/
|
|
621
|
+
revoke: (userId, tokenId) =>
|
|
622
|
+
this._request('DELETE', `/api/v1/users/${userId}/tokens/${tokenId}`),
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
this.tokenLayers = {
|
|
626
|
+
/**
|
|
627
|
+
* Shift a token layer's display order.
|
|
628
|
+
* @param {string} tokenLayerId - The token layer ID
|
|
629
|
+
* @param {string} direction - The direction ("up" or "down")
|
|
630
|
+
*/
|
|
631
|
+
shift: (tokenLayerId, direction) =>
|
|
632
|
+
this._request('POST', `/api/v1/token-layers/${tokenLayerId}/shift`, {
|
|
633
|
+
body: bodyOf({ direction }),
|
|
634
|
+
}),
|
|
635
|
+
/**
|
|
636
|
+
* Create a new token layer.
|
|
637
|
+
* @param {string} textLayerId - The text layer ID
|
|
638
|
+
* @param {string} name - The name
|
|
639
|
+
* @param {string} [overlapMode] - Per-layer, immutable token invariant: "any" (default), "non-overlapping", or "partitioning". On partitioning layers, single token create/update/delete are rejected; use bulkCreate plus split/merge/shift.
|
|
640
|
+
* @param {string} [parentTokenLayerId] - Optional immutable parent token layer. Tokens in this layer must nest within a parent-layer token; the parent layer must be in the same text layer and be "non-overlapping" or "partitioning" (an "any" parent is rejected). A nested layer may be "any" or "non-overlapping" but not "partitioning" (partitioning is only for root layers), e.g. words (non-overlapping, parent=sentences) within sentences (partitioning).
|
|
641
|
+
*/
|
|
642
|
+
create: (textLayerId, name, overlapMode, parentTokenLayerId) =>
|
|
643
|
+
this._request('POST', '/api/v1/token-layers', {
|
|
644
|
+
body: bodyOf({ 'text-layer-id': textLayerId, name, 'overlap-mode': overlapMode, 'parent-token-layer-id': parentTokenLayerId }),
|
|
645
|
+
}),
|
|
646
|
+
/**
|
|
647
|
+
* Set a configuration value for a layer in an editor namespace.
|
|
648
|
+
* @param {string} tokenLayerId - The token layer ID
|
|
649
|
+
* @param {string} namespace - The config namespace
|
|
650
|
+
* @param {string} configKey - The config key
|
|
651
|
+
* @param {any} configValue - Configuration value to set
|
|
652
|
+
*/
|
|
653
|
+
setConfig: (tokenLayerId, namespace, configKey, configValue) =>
|
|
654
|
+
this._request('PUT', `/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`, {
|
|
655
|
+
rawBody: configValue, skipResponseTransform: true,
|
|
656
|
+
}),
|
|
657
|
+
/**
|
|
658
|
+
* Remove a configuration value for a layer.
|
|
659
|
+
* @param {string} tokenLayerId - The token layer ID
|
|
660
|
+
* @param {string} namespace - The config namespace
|
|
661
|
+
* @param {string} configKey - The config key
|
|
662
|
+
*/
|
|
663
|
+
deleteConfig: (tokenLayerId, namespace, configKey) =>
|
|
664
|
+
this._request('DELETE', `/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`, {
|
|
665
|
+
skipResponseTransform: true,
|
|
666
|
+
}),
|
|
667
|
+
/**
|
|
668
|
+
* Get a token layer by ID.
|
|
669
|
+
* @param {string} tokenLayerId - The token layer ID
|
|
670
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
671
|
+
*/
|
|
672
|
+
get: (tokenLayerId, asOf) =>
|
|
673
|
+
this._request('GET', `/api/v1/token-layers/${tokenLayerId}`, {
|
|
674
|
+
queryParams: { 'as-of': asOf },
|
|
675
|
+
}),
|
|
676
|
+
/**
|
|
677
|
+
* Delete a token layer.
|
|
678
|
+
* @param {string} tokenLayerId - The token layer ID
|
|
679
|
+
*/
|
|
680
|
+
delete: (tokenLayerId) =>
|
|
681
|
+
this._request('DELETE', `/api/v1/token-layers/${tokenLayerId}`),
|
|
682
|
+
/**
|
|
683
|
+
* Update a token layer's name.
|
|
684
|
+
* @param {string} tokenLayerId - The token layer ID
|
|
685
|
+
* @param {string} name - The name
|
|
686
|
+
*/
|
|
687
|
+
update: (tokenLayerId, name) =>
|
|
688
|
+
this._request('PATCH', `/api/v1/token-layers/${tokenLayerId}`, {
|
|
689
|
+
body: bodyOf({ name }),
|
|
690
|
+
}),
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
this.documents = {
|
|
694
|
+
/**
|
|
695
|
+
* Check the lock status of a document.
|
|
696
|
+
* @param {string} documentId - The document ID
|
|
697
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
698
|
+
*/
|
|
699
|
+
checkLock: (documentId, asOf) =>
|
|
700
|
+
this._request('GET', `/api/v1/documents/${documentId}/lock`, {
|
|
701
|
+
queryParams: { 'as-of': asOf },
|
|
702
|
+
}),
|
|
703
|
+
/**
|
|
704
|
+
* Acquire or refresh a document lock
|
|
705
|
+
* @param {string} documentId - The document ID
|
|
706
|
+
*/
|
|
707
|
+
acquireLock: (documentId) =>
|
|
708
|
+
this._request('POST', `/api/v1/documents/${documentId}/lock`),
|
|
709
|
+
/**
|
|
710
|
+
* Release a document lock
|
|
711
|
+
* @param {string} documentId - The document ID
|
|
712
|
+
*/
|
|
713
|
+
releaseLock: (documentId) =>
|
|
714
|
+
this._request('DELETE', `/api/v1/documents/${documentId}/lock`),
|
|
715
|
+
/**
|
|
716
|
+
* Get media file for a document
|
|
717
|
+
* @param {string} documentId - The document ID
|
|
718
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
719
|
+
*/
|
|
720
|
+
getMedia: (documentId, asOf) =>
|
|
721
|
+
this._request('GET', `/api/v1/documents/${documentId}/media`, {
|
|
722
|
+
queryParams: { 'as-of': asOf },
|
|
723
|
+
noBatch: true,
|
|
724
|
+
binaryResponse: true,
|
|
725
|
+
}),
|
|
726
|
+
/**
|
|
727
|
+
* Upload a media file for a document. Uses Apache Tika for content validation.
|
|
728
|
+
* @param {string} documentId - The document ID
|
|
729
|
+
* @param {File} file - The file to upload
|
|
730
|
+
*/
|
|
731
|
+
uploadMedia: (documentId, file) => {
|
|
732
|
+
const fd = new FormData();
|
|
733
|
+
fd.append('file', file);
|
|
734
|
+
return this._request('PUT', `/api/v1/documents/${documentId}/media`, {
|
|
735
|
+
body: fd, formData: true, noBatch: true,
|
|
736
|
+
});
|
|
737
|
+
},
|
|
738
|
+
/**
|
|
739
|
+
* Delete media file for a document
|
|
740
|
+
* @param {string} documentId - The document ID
|
|
741
|
+
*/
|
|
742
|
+
deleteMedia: (documentId) =>
|
|
743
|
+
this._request('DELETE', `/api/v1/documents/${documentId}/media`, {
|
|
744
|
+
noBatch: true,
|
|
745
|
+
}),
|
|
746
|
+
/**
|
|
747
|
+
* Replace all metadata for a document.
|
|
748
|
+
* @param {string} documentId - The document ID
|
|
749
|
+
* @param {any} body - The request body
|
|
750
|
+
*/
|
|
751
|
+
setMetadata: (documentId, body) =>
|
|
752
|
+
this._request('PUT', `/api/v1/documents/${documentId}/metadata`, {
|
|
753
|
+
rawBody: body, skipResponseTransform: true,
|
|
754
|
+
}),
|
|
755
|
+
/**
|
|
756
|
+
* Remove all metadata from a document.
|
|
757
|
+
* @param {string} documentId - The document ID
|
|
758
|
+
*/
|
|
759
|
+
deleteMetadata: (documentId) =>
|
|
760
|
+
this._request('DELETE', `/api/v1/documents/${documentId}/metadata`, {
|
|
761
|
+
skipResponseTransform: true,
|
|
762
|
+
}),
|
|
763
|
+
/**
|
|
764
|
+
* Get audit log for a document. Transparently follows pagination cursors
|
|
765
|
+
* and returns the full flat array.
|
|
766
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
767
|
+
* @param {string} documentId - The document ID
|
|
768
|
+
* @param {string} [startTime] - Start of time range
|
|
769
|
+
* @param {string} [endTime] - End of time range
|
|
770
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
771
|
+
*/
|
|
772
|
+
audit: (documentId, startTime, endTime, asOf) =>
|
|
773
|
+
listAll(this, `/api/v1/documents/${documentId}/audit`, {
|
|
774
|
+
query: { 'start-time': startTime, 'end-time': endTime, 'as-of': asOf },
|
|
775
|
+
}),
|
|
776
|
+
/**
|
|
777
|
+
* Get a document. Set `includeBody` to true to include all data.
|
|
778
|
+
* @param {string} documentId - The document ID
|
|
779
|
+
* @param {boolean} [includeBody] - Include document body data
|
|
780
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
781
|
+
*/
|
|
782
|
+
get: (documentId, includeBody, asOf) =>
|
|
783
|
+
this._request('GET', `/api/v1/documents/${documentId}`, {
|
|
784
|
+
queryParams: { 'include-body': includeBody, 'as-of': asOf },
|
|
785
|
+
}),
|
|
786
|
+
/**
|
|
787
|
+
* Delete a document and all data contained.
|
|
788
|
+
* @param {string} documentId - The document ID
|
|
789
|
+
*/
|
|
790
|
+
delete: (documentId) =>
|
|
791
|
+
this._request('DELETE', `/api/v1/documents/${documentId}`),
|
|
792
|
+
/**
|
|
793
|
+
* Update a document's name.
|
|
794
|
+
* @param {string} documentId - The document ID
|
|
795
|
+
* @param {string} name - The name
|
|
796
|
+
*/
|
|
797
|
+
update: (documentId, name) =>
|
|
798
|
+
this._request('PATCH', `/api/v1/documents/${documentId}`, {
|
|
799
|
+
body: bodyOf({ name }),
|
|
800
|
+
}),
|
|
801
|
+
/**
|
|
802
|
+
* Create a new document in a project.
|
|
803
|
+
* @param {string} projectId - The project ID
|
|
804
|
+
* @param {string} name - The name
|
|
805
|
+
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
806
|
+
*/
|
|
807
|
+
create: (projectId, name, metadata) =>
|
|
808
|
+
this._request('POST', '/api/v1/documents', {
|
|
809
|
+
body: bodyOf({ 'project-id': projectId, name, metadata }),
|
|
810
|
+
}),
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
this.projects = {
|
|
814
|
+
/**
|
|
815
|
+
* Set a user's access level to read and write for this project.
|
|
816
|
+
* @param {string} id - The resource ID
|
|
817
|
+
* @param {string} userId - The user ID
|
|
818
|
+
*/
|
|
819
|
+
addWriter: (id, userId) =>
|
|
820
|
+
this._request('POST', `/api/v1/projects/${id}/writers/${userId}`),
|
|
821
|
+
/**
|
|
822
|
+
* Remove a user's writer privileges for this project.
|
|
823
|
+
* @param {string} id - The resource ID
|
|
824
|
+
* @param {string} userId - The user ID
|
|
825
|
+
*/
|
|
826
|
+
removeWriter: (id, userId) =>
|
|
827
|
+
this._request('DELETE', `/api/v1/projects/${id}/writers/${userId}`),
|
|
828
|
+
/**
|
|
829
|
+
* Set a user's access level to read-only for this project.
|
|
830
|
+
* @param {string} id - The resource ID
|
|
831
|
+
* @param {string} userId - The user ID
|
|
832
|
+
*/
|
|
833
|
+
addReader: (id, userId) =>
|
|
834
|
+
this._request('POST', `/api/v1/projects/${id}/readers/${userId}`),
|
|
835
|
+
/**
|
|
836
|
+
* Remove a user's reader privileges for this project.
|
|
837
|
+
* @param {string} id - The resource ID
|
|
838
|
+
* @param {string} userId - The user ID
|
|
839
|
+
*/
|
|
840
|
+
removeReader: (id, userId) =>
|
|
841
|
+
this._request('DELETE', `/api/v1/projects/${id}/readers/${userId}`),
|
|
842
|
+
/**
|
|
843
|
+
* Set a configuration value for a project in an editor namespace.
|
|
844
|
+
* @param {string} id - The resource ID
|
|
845
|
+
* @param {string} namespace - The config namespace
|
|
846
|
+
* @param {string} configKey - The config key
|
|
847
|
+
* @param {any} configValue - Configuration value to set
|
|
848
|
+
*/
|
|
849
|
+
setConfig: (id, namespace, configKey, configValue) =>
|
|
850
|
+
this._request('PUT', `/api/v1/projects/${id}/config/${namespace}/${configKey}`, {
|
|
851
|
+
rawBody: configValue, skipResponseTransform: true,
|
|
852
|
+
}),
|
|
853
|
+
/**
|
|
854
|
+
* Remove a configuration value for a project.
|
|
855
|
+
* @param {string} id - The resource ID
|
|
856
|
+
* @param {string} namespace - The config namespace
|
|
857
|
+
* @param {string} configKey - The config key
|
|
858
|
+
*/
|
|
859
|
+
deleteConfig: (id, namespace, configKey) =>
|
|
860
|
+
this._request('DELETE', `/api/v1/projects/${id}/config/${namespace}/${configKey}`, {
|
|
861
|
+
skipResponseTransform: true,
|
|
862
|
+
}),
|
|
863
|
+
/**
|
|
864
|
+
* Assign a user as a maintainer for this project.
|
|
865
|
+
* @param {string} id - The resource ID
|
|
866
|
+
* @param {string} userId - The user ID
|
|
867
|
+
*/
|
|
868
|
+
addMaintainer: (id, userId) =>
|
|
869
|
+
this._request('POST', `/api/v1/projects/${id}/maintainers/${userId}`),
|
|
870
|
+
/**
|
|
871
|
+
* Remove a user's maintainer privileges for this project.
|
|
872
|
+
* @param {string} id - The resource ID
|
|
873
|
+
* @param {string} userId - The user ID
|
|
874
|
+
*/
|
|
875
|
+
removeMaintainer: (id, userId) =>
|
|
876
|
+
this._request('DELETE', `/api/v1/projects/${id}/maintainers/${userId}`),
|
|
877
|
+
/**
|
|
878
|
+
* Get audit log for a project. Transparently follows pagination cursors
|
|
879
|
+
* and returns the full flat array.
|
|
880
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
881
|
+
* @param {string} projectId - The project ID
|
|
882
|
+
* @param {string} [startTime] - Start of time range
|
|
883
|
+
* @param {string} [endTime] - End of time range
|
|
884
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
885
|
+
*/
|
|
886
|
+
audit: (projectId, startTime, endTime, asOf) =>
|
|
887
|
+
listAll(this, `/api/v1/projects/${projectId}/audit`, {
|
|
888
|
+
query: { 'start-time': startTime, 'end-time': endTime, 'as-of': asOf },
|
|
889
|
+
}),
|
|
890
|
+
/**
|
|
891
|
+
* Link a vocabulary to a project.
|
|
892
|
+
* @param {string} id - The resource ID
|
|
893
|
+
* @param {string} vocabId - The vocab layer ID
|
|
894
|
+
*/
|
|
895
|
+
linkVocab: (id, vocabId) =>
|
|
896
|
+
this._request('POST', `/api/v1/projects/${id}/vocabs/${vocabId}`),
|
|
897
|
+
/**
|
|
898
|
+
* Unlink a vocabulary from a project.
|
|
899
|
+
* @param {string} id - The resource ID
|
|
900
|
+
* @param {string} vocabId - The vocab layer ID
|
|
901
|
+
*/
|
|
902
|
+
unlinkVocab: (id, vocabId) =>
|
|
903
|
+
this._request('DELETE', `/api/v1/projects/${id}/vocabs/${vocabId}`),
|
|
904
|
+
/**
|
|
905
|
+
* Get a project by ID. To fetch the project's documents, use
|
|
906
|
+
* listDocuments(id) — the include-documents flag has been removed.
|
|
907
|
+
* @param {string} id - The resource ID
|
|
908
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
909
|
+
*/
|
|
910
|
+
get: (id, asOf) =>
|
|
911
|
+
this._request('GET', `/api/v1/projects/${id}`, {
|
|
912
|
+
queryParams: { 'as-of': asOf },
|
|
913
|
+
}),
|
|
914
|
+
/**
|
|
915
|
+
* List all documents in a project. Transparently follows pagination
|
|
916
|
+
* cursors and returns the full flat array.
|
|
917
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
918
|
+
*
|
|
919
|
+
* Note: this endpoint does not support temporal (`as-of`) queries; the
|
|
920
|
+
* server rejects `?as-of=` on the documents-list route with a 400.
|
|
921
|
+
* @param {string} id - The project ID
|
|
922
|
+
*/
|
|
923
|
+
listDocuments: (id) =>
|
|
924
|
+
listAll(this, `/api/v1/projects/${id}/documents`),
|
|
925
|
+
/**
|
|
926
|
+
* Fetch a single page of a project's documents.
|
|
927
|
+
*
|
|
928
|
+
* Note: this endpoint does not support temporal (`as-of`) queries; the
|
|
929
|
+
* server rejects `?as-of=` on the documents-list route with a 400.
|
|
930
|
+
* @param {string} id - The project ID
|
|
931
|
+
* @param {object} [opts]
|
|
932
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
933
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
934
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
935
|
+
*/
|
|
936
|
+
listDocumentsPage: (id, { limit, cursor } = {}) =>
|
|
937
|
+
listPage(this, `/api/v1/projects/${id}/documents`, { limit, cursor }),
|
|
938
|
+
/**
|
|
939
|
+
* Async-iterate a project's documents page by page; yields each page's
|
|
940
|
+
* entries array.
|
|
941
|
+
*
|
|
942
|
+
* Note: this endpoint does not support temporal (`as-of`) queries; the
|
|
943
|
+
* server rejects `?as-of=` on the documents-list route with a 400.
|
|
944
|
+
* @param {string} id - The project ID
|
|
945
|
+
* @param {object} [opts]
|
|
946
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
947
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
948
|
+
* @returns {AsyncGenerator<Array>}
|
|
949
|
+
*/
|
|
950
|
+
iterDocuments: (id, { pageSize } = {}) =>
|
|
951
|
+
iterPages(this, `/api/v1/projects/${id}/documents`, { pageSize }),
|
|
952
|
+
/**
|
|
953
|
+
* Delete a project.
|
|
954
|
+
* @param {string} id - The resource ID
|
|
955
|
+
*/
|
|
956
|
+
delete: (id) =>
|
|
957
|
+
this._request('DELETE', `/api/v1/projects/${id}`),
|
|
958
|
+
/**
|
|
959
|
+
* Update a project's name.
|
|
960
|
+
* @param {string} id - The resource ID
|
|
961
|
+
* @param {string} name - The name
|
|
962
|
+
*/
|
|
963
|
+
update: (id, name) =>
|
|
964
|
+
this._request('PATCH', `/api/v1/projects/${id}`, {
|
|
965
|
+
body: bodyOf({ name }),
|
|
966
|
+
}),
|
|
967
|
+
/**
|
|
968
|
+
* List all projects accessible to user. Transparently follows pagination
|
|
969
|
+
* cursors and returns the full flat array.
|
|
970
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
971
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
972
|
+
*/
|
|
973
|
+
list: (asOf) =>
|
|
974
|
+
listAll(this, '/api/v1/projects', { query: { 'as-of': asOf } }),
|
|
975
|
+
/**
|
|
976
|
+
* Fetch a single page of projects.
|
|
977
|
+
* @param {object} [opts]
|
|
978
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
979
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
980
|
+
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
981
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
982
|
+
*/
|
|
983
|
+
listPage: ({ limit, cursor, asOf } = {}) =>
|
|
984
|
+
listPage(this, '/api/v1/projects', { limit, cursor, query: { 'as-of': asOf } }),
|
|
985
|
+
/**
|
|
986
|
+
* Async-iterate projects page by page; yields each page's entries array.
|
|
987
|
+
* @param {object} [opts]
|
|
988
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
989
|
+
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
990
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
991
|
+
* @returns {AsyncGenerator<Array>}
|
|
992
|
+
*/
|
|
993
|
+
iterPages: ({ pageSize, asOf } = {}) =>
|
|
994
|
+
iterPages(this, '/api/v1/projects', { pageSize, query: { 'as-of': asOf } }),
|
|
995
|
+
/**
|
|
996
|
+
* Create a new project. Note: this also registers the user as a maintainer.
|
|
997
|
+
* @param {string} name - The name
|
|
998
|
+
*/
|
|
999
|
+
create: (name) =>
|
|
1000
|
+
this._request('POST', '/api/v1/projects', {
|
|
1001
|
+
body: bodyOf({ name }),
|
|
1002
|
+
}),
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
this.textLayers = {
|
|
1006
|
+
/**
|
|
1007
|
+
* Set a configuration value for a layer in an editor namespace.
|
|
1008
|
+
* @param {string} textLayerId - The text layer ID
|
|
1009
|
+
* @param {string} namespace - The config namespace
|
|
1010
|
+
* @param {string} configKey - The config key
|
|
1011
|
+
* @param {any} configValue - Configuration value to set
|
|
1012
|
+
*/
|
|
1013
|
+
setConfig: (textLayerId, namespace, configKey, configValue) =>
|
|
1014
|
+
this._request('PUT', `/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`, {
|
|
1015
|
+
rawBody: configValue, skipResponseTransform: true,
|
|
1016
|
+
}),
|
|
1017
|
+
/**
|
|
1018
|
+
* Remove a configuration value for a layer.
|
|
1019
|
+
* @param {string} textLayerId - The text layer ID
|
|
1020
|
+
* @param {string} namespace - The config namespace
|
|
1021
|
+
* @param {string} configKey - The config key
|
|
1022
|
+
*/
|
|
1023
|
+
deleteConfig: (textLayerId, namespace, configKey) =>
|
|
1024
|
+
this._request('DELETE', `/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`, {
|
|
1025
|
+
skipResponseTransform: true,
|
|
1026
|
+
}),
|
|
1027
|
+
/**
|
|
1028
|
+
* Get a text layer by ID.
|
|
1029
|
+
* @param {string} textLayerId - The text layer ID
|
|
1030
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
1031
|
+
*/
|
|
1032
|
+
get: (textLayerId, asOf) =>
|
|
1033
|
+
this._request('GET', `/api/v1/text-layers/${textLayerId}`, {
|
|
1034
|
+
queryParams: { 'as-of': asOf },
|
|
1035
|
+
}),
|
|
1036
|
+
/**
|
|
1037
|
+
* Delete a text layer.
|
|
1038
|
+
* @param {string} textLayerId - The text layer ID
|
|
1039
|
+
*/
|
|
1040
|
+
delete: (textLayerId) =>
|
|
1041
|
+
this._request('DELETE', `/api/v1/text-layers/${textLayerId}`),
|
|
1042
|
+
/**
|
|
1043
|
+
* Update a text layer's name.
|
|
1044
|
+
* @param {string} textLayerId - The text layer ID
|
|
1045
|
+
* @param {string} name - The name
|
|
1046
|
+
*/
|
|
1047
|
+
update: (textLayerId, name) =>
|
|
1048
|
+
this._request('PATCH', `/api/v1/text-layers/${textLayerId}`, {
|
|
1049
|
+
body: bodyOf({ name }),
|
|
1050
|
+
}),
|
|
1051
|
+
/**
|
|
1052
|
+
* Shift a text layer's display order within the project.
|
|
1053
|
+
* @param {string} textLayerId - The text layer ID
|
|
1054
|
+
* @param {string} direction - The direction ("up" or "down")
|
|
1055
|
+
*/
|
|
1056
|
+
shift: (textLayerId, direction) =>
|
|
1057
|
+
this._request('POST', `/api/v1/text-layers/${textLayerId}/shift`, {
|
|
1058
|
+
body: bodyOf({ direction }),
|
|
1059
|
+
}),
|
|
1060
|
+
/**
|
|
1061
|
+
* Create a new text layer for a project.
|
|
1062
|
+
* @param {string} projectId - The project ID
|
|
1063
|
+
* @param {string} name - The name
|
|
1064
|
+
*/
|
|
1065
|
+
create: (projectId, name) =>
|
|
1066
|
+
this._request('POST', '/api/v1/text-layers', {
|
|
1067
|
+
body: bodyOf({ 'project-id': projectId, name }),
|
|
1068
|
+
}),
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
this.vocabItems = {
|
|
1072
|
+
/**
|
|
1073
|
+
* Replace all metadata for a vocab item.
|
|
1074
|
+
* @param {string} id - The resource ID
|
|
1075
|
+
* @param {any} body - The request body
|
|
1076
|
+
*/
|
|
1077
|
+
setMetadata: (id, body) =>
|
|
1078
|
+
this._request('PUT', `/api/v1/vocab-items/${id}/metadata`, {
|
|
1079
|
+
rawBody: body, skipResponseTransform: true,
|
|
1080
|
+
}),
|
|
1081
|
+
/**
|
|
1082
|
+
* Remove all metadata from a vocab item.
|
|
1083
|
+
* @param {string} id - The resource ID
|
|
1084
|
+
*/
|
|
1085
|
+
deleteMetadata: (id) =>
|
|
1086
|
+
this._request('DELETE', `/api/v1/vocab-items/${id}/metadata`, {
|
|
1087
|
+
skipResponseTransform: true,
|
|
1088
|
+
}),
|
|
1089
|
+
/**
|
|
1090
|
+
* Create a new vocab item
|
|
1091
|
+
* @param {string} vocabLayerId - The vocab layer ID
|
|
1092
|
+
* @param {string} form - The vocab item form
|
|
1093
|
+
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
1094
|
+
*/
|
|
1095
|
+
create: (vocabLayerId, form, metadata) =>
|
|
1096
|
+
this._request('POST', '/api/v1/vocab-items', {
|
|
1097
|
+
body: bodyOf({ 'vocab-layer-id': vocabLayerId, form, metadata }),
|
|
1098
|
+
}),
|
|
1099
|
+
/**
|
|
1100
|
+
* Get a vocab item by ID
|
|
1101
|
+
* @param {string} id - The resource ID
|
|
1102
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
1103
|
+
*/
|
|
1104
|
+
get: (id, asOf) =>
|
|
1105
|
+
this._request('GET', `/api/v1/vocab-items/${id}`, {
|
|
1106
|
+
queryParams: { 'as-of': asOf },
|
|
1107
|
+
}),
|
|
1108
|
+
/**
|
|
1109
|
+
* Delete a vocab item
|
|
1110
|
+
* @param {string} id - The resource ID
|
|
1111
|
+
*/
|
|
1112
|
+
delete: (id) =>
|
|
1113
|
+
this._request('DELETE', `/api/v1/vocab-items/${id}`),
|
|
1114
|
+
/**
|
|
1115
|
+
* Update a vocab item's form
|
|
1116
|
+
* @param {string} id - The resource ID
|
|
1117
|
+
* @param {string} form - The vocab item form
|
|
1118
|
+
*/
|
|
1119
|
+
update: (id, form) =>
|
|
1120
|
+
this._request('PATCH', `/api/v1/vocab-items/${id}`, {
|
|
1121
|
+
body: bodyOf({ form }),
|
|
1122
|
+
}),
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
this.relationLayers = {
|
|
1126
|
+
/**
|
|
1127
|
+
* Shift a relation layer's display order.
|
|
1128
|
+
* @param {string} relationLayerId - The relation layer ID
|
|
1129
|
+
* @param {string} direction - The direction ("up" or "down")
|
|
1130
|
+
*/
|
|
1131
|
+
shift: (relationLayerId, direction) =>
|
|
1132
|
+
this._request('POST', `/api/v1/relation-layers/${relationLayerId}/shift`, {
|
|
1133
|
+
body: bodyOf({ direction }),
|
|
1134
|
+
}),
|
|
1135
|
+
/**
|
|
1136
|
+
* Create a new relation layer.
|
|
1137
|
+
* @param {string} spanLayerId - The span layer ID
|
|
1138
|
+
* @param {string} name - The name
|
|
1139
|
+
*/
|
|
1140
|
+
create: (spanLayerId, name) =>
|
|
1141
|
+
this._request('POST', '/api/v1/relation-layers', {
|
|
1142
|
+
body: bodyOf({ 'span-layer-id': spanLayerId, name }),
|
|
1143
|
+
}),
|
|
1144
|
+
/**
|
|
1145
|
+
* Set a configuration value for a layer in an editor namespace.
|
|
1146
|
+
* @param {string} relationLayerId - The relation layer ID
|
|
1147
|
+
* @param {string} namespace - The config namespace
|
|
1148
|
+
* @param {string} configKey - The config key
|
|
1149
|
+
* @param {any} configValue - Configuration value to set
|
|
1150
|
+
*/
|
|
1151
|
+
setConfig: (relationLayerId, namespace, configKey, configValue) =>
|
|
1152
|
+
this._request('PUT', `/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`, {
|
|
1153
|
+
rawBody: configValue, skipResponseTransform: true,
|
|
1154
|
+
}),
|
|
1155
|
+
/**
|
|
1156
|
+
* Remove a configuration value for a layer.
|
|
1157
|
+
* @param {string} relationLayerId - The relation layer ID
|
|
1158
|
+
* @param {string} namespace - The config namespace
|
|
1159
|
+
* @param {string} configKey - The config key
|
|
1160
|
+
*/
|
|
1161
|
+
deleteConfig: (relationLayerId, namespace, configKey) =>
|
|
1162
|
+
this._request('DELETE', `/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`, {
|
|
1163
|
+
skipResponseTransform: true,
|
|
1164
|
+
}),
|
|
1165
|
+
/**
|
|
1166
|
+
* Get a relation layer by ID.
|
|
1167
|
+
* @param {string} relationLayerId - The relation layer ID
|
|
1168
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
1169
|
+
*/
|
|
1170
|
+
get: (relationLayerId, asOf) =>
|
|
1171
|
+
this._request('GET', `/api/v1/relation-layers/${relationLayerId}`, {
|
|
1172
|
+
queryParams: { 'as-of': asOf },
|
|
1173
|
+
}),
|
|
1174
|
+
/**
|
|
1175
|
+
* Delete a relation layer.
|
|
1176
|
+
* @param {string} relationLayerId - The relation layer ID
|
|
1177
|
+
*/
|
|
1178
|
+
delete: (relationLayerId) =>
|
|
1179
|
+
this._request('DELETE', `/api/v1/relation-layers/${relationLayerId}`),
|
|
1180
|
+
/**
|
|
1181
|
+
* Update a relation layer's name.
|
|
1182
|
+
* @param {string} relationLayerId - The relation layer ID
|
|
1183
|
+
* @param {string} name - The name
|
|
1184
|
+
*/
|
|
1185
|
+
update: (relationLayerId, name) =>
|
|
1186
|
+
this._request('PATCH', `/api/v1/relation-layers/${relationLayerId}`, {
|
|
1187
|
+
body: bodyOf({ name }),
|
|
1188
|
+
}),
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
this.tokens = {
|
|
1192
|
+
/**
|
|
1193
|
+
* Create a new token in a token layer. Tokens define text substrings
|
|
1194
|
+
* using begin and end offsets. Tokens may be zero-width and may overlap.
|
|
1195
|
+
* For tokens sharing the same begin, precedence controls the linear
|
|
1196
|
+
* ordering.
|
|
1197
|
+
* @param {string} tokenLayerId - The token layer ID
|
|
1198
|
+
* @param {string} text - The text ID
|
|
1199
|
+
* @param {number} begin - Start offset (inclusive)
|
|
1200
|
+
* @param {number} end - End offset (exclusive)
|
|
1201
|
+
* @param {number} [precedence] - Ordering precedence
|
|
1202
|
+
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
1203
|
+
*/
|
|
1204
|
+
create: (tokenLayerId, text, begin, end, precedence, metadata) =>
|
|
1205
|
+
this._request('POST', '/api/v1/tokens', {
|
|
1206
|
+
body: bodyOf({ 'token-layer-id': tokenLayerId, text, begin, end, precedence, metadata }),
|
|
1207
|
+
}),
|
|
1208
|
+
/**
|
|
1209
|
+
* Get a token.
|
|
1210
|
+
* @param {string} tokenId - The token ID
|
|
1211
|
+
* @param {string} [asOf] - Temporal query timestamp
|
|
1212
|
+
*/
|
|
1213
|
+
get: (tokenId, asOf) =>
|
|
1214
|
+
this._request('GET', `/api/v1/tokens/${tokenId}`, {
|
|
1215
|
+
queryParams: { 'as-of': asOf },
|
|
1216
|
+
}),
|
|
1217
|
+
/**
|
|
1218
|
+
* Delete a token and remove it from any spans. If this causes a span to
|
|
1219
|
+
* have no remaining tokens, the span will also be deleted.
|
|
1220
|
+
* @param {string} tokenId - The token ID
|
|
1221
|
+
*/
|
|
1222
|
+
delete: (tokenId) =>
|
|
1223
|
+
this._request('DELETE', `/api/v1/tokens/${tokenId}`),
|
|
1224
|
+
/**
|
|
1225
|
+
* Update a token.
|
|
1226
|
+
* @param {string} tokenId - The token ID
|
|
1227
|
+
* @param {number} [begin] - New start offset
|
|
1228
|
+
* @param {number} [end] - New end offset
|
|
1229
|
+
* @param {?number} [precedence] - Ordering precedence. Omit (undefined)
|
|
1230
|
+
* to leave unchanged; pass a number to set; pass null explicitly to
|
|
1231
|
+
* CLEAR it (revert to no explicit ordering). bodyOf keeps null but
|
|
1232
|
+
* drops undefined, so the three cases map correctly to the server.
|
|
1233
|
+
*/
|
|
1234
|
+
update: (tokenId, begin, end, precedence) =>
|
|
1235
|
+
this._request('PATCH', `/api/v1/tokens/${tokenId}`, {
|
|
1236
|
+
body: bodyOf({ begin, end, precedence }),
|
|
1237
|
+
}),
|
|
1238
|
+
/**
|
|
1239
|
+
* Create multiple tokens in a single operation.
|
|
1240
|
+
* @param {Array} body - The request body
|
|
1241
|
+
*/
|
|
1242
|
+
bulkCreate: (body) =>
|
|
1243
|
+
this._request('POST', '/api/v1/tokens/bulk', { body }),
|
|
1244
|
+
/**
|
|
1245
|
+
* Delete multiple tokens in a single operation. Provide an array of IDs.
|
|
1246
|
+
* @param {Array} body - The request body
|
|
1247
|
+
*/
|
|
1248
|
+
bulkDelete: (body) =>
|
|
1249
|
+
this._request('DELETE', '/api/v1/tokens/bulk', { body }),
|
|
1250
|
+
/**
|
|
1251
|
+
* Split a token at a character offset. The original token becomes the left half
|
|
1252
|
+
* (keeps its ID, spans, vocab-links); the new right token's ID is returned.
|
|
1253
|
+
* @param {string} tokenId - The token ID
|
|
1254
|
+
* @param {number} position - Offset to split at (strictly between begin and end)
|
|
1255
|
+
*/
|
|
1256
|
+
split: (tokenId, position) =>
|
|
1257
|
+
this._request('POST', `/api/v1/tokens/${tokenId}/split`, {
|
|
1258
|
+
body: bodyOf({ position }),
|
|
1259
|
+
}),
|
|
1260
|
+
/**
|
|
1261
|
+
* Merge two tokens. The left token (smaller begin) survives with the combined
|
|
1262
|
+
* extent; the right is deleted and its spans/vocab-links are reparented to the left.
|
|
1263
|
+
* On partitioning layers the tokens must be adjacent; on non-overlapping layers the
|
|
1264
|
+
* merged extent must not engulf a third token.
|
|
1265
|
+
* @param {string} tokenId - The anchor token ID
|
|
1266
|
+
* @param {string} otherTokenId - The other token to merge in
|
|
1267
|
+
*/
|
|
1268
|
+
merge: (tokenId, otherTokenId) =>
|
|
1269
|
+
this._request('POST', `/api/v1/tokens/${tokenId}/merge`, {
|
|
1270
|
+
body: bodyOf({ 'other-token-id': otherTokenId }),
|
|
1271
|
+
}),
|
|
1272
|
+
/**
|
|
1273
|
+
* Shift a token's boundary. On partitioning layers the adjacent token is
|
|
1274
|
+
* auto-adjusted to preserve the partition; on non-overlapping layers a shift that
|
|
1275
|
+
* would create an overlap is rejected.
|
|
1276
|
+
* @param {string} tokenId - The token ID
|
|
1277
|
+
* @param {number} [begin] - New start offset
|
|
1278
|
+
* @param {number} [end] - New end offset
|
|
1279
|
+
*/
|
|
1280
|
+
shift: (tokenId, begin, end) =>
|
|
1281
|
+
this._request('POST', `/api/v1/tokens/${tokenId}/shift`, {
|
|
1282
|
+
body: bodyOf({ begin, end }),
|
|
1283
|
+
}),
|
|
1284
|
+
/**
|
|
1285
|
+
* Replace all metadata for a token.
|
|
1286
|
+
* @param {string} tokenId - The token ID
|
|
1287
|
+
* @param {any} body - The request body
|
|
1288
|
+
*/
|
|
1289
|
+
setMetadata: (tokenId, body) =>
|
|
1290
|
+
this._request('PUT', `/api/v1/tokens/${tokenId}/metadata`, {
|
|
1291
|
+
rawBody: body, skipResponseTransform: true,
|
|
1292
|
+
}),
|
|
1293
|
+
/**
|
|
1294
|
+
* Remove all metadata from a token.
|
|
1295
|
+
* @param {string} tokenId - The token ID
|
|
1296
|
+
*/
|
|
1297
|
+
deleteMetadata: (tokenId) =>
|
|
1298
|
+
this._request('DELETE', `/api/v1/tokens/${tokenId}/metadata`, {
|
|
1299
|
+
skipResponseTransform: true,
|
|
1300
|
+
}),
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
this.messages = {
|
|
1304
|
+
/**
|
|
1305
|
+
* Open a Server-Sent Events stream for a project.
|
|
1306
|
+
* @param {string} projectId - The UUID of the project to listen to
|
|
1307
|
+
* @param {function} onEvent - Callback function that receives (eventType, data). If it returns true, listening will stop.
|
|
1308
|
+
* @param {string} [path] - Stream path under baseUrl (defaults to the project /listen bus; service channels pass their own).
|
|
1309
|
+
* @returns {Object} SSE connection object with .close() and .getStats() methods
|
|
1310
|
+
*/
|
|
1311
|
+
listen: (projectId, onEvent, path) =>
|
|
1312
|
+
createSSEConnection(this, projectId, onEvent, path),
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Send a message to project listeners
|
|
1316
|
+
* @param {string} projectId - The UUID of the project to send to
|
|
1317
|
+
* @param {any} data - The message data to send
|
|
1318
|
+
* @returns {Promise<any>} Response from the send operation
|
|
1319
|
+
*/
|
|
1320
|
+
sendMessage: (projectId, data) =>
|
|
1321
|
+
this._request('POST', `/api/v1/projects/${projectId}/message`, {
|
|
1322
|
+
body: { body: data },
|
|
1323
|
+
}),
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Discover the services currently connected to a project (synchronous GET).
|
|
1327
|
+
* @param {string} projectId - The UUID of the project to query
|
|
1328
|
+
* @param {number} [timeout] - Ignored; kept for back-compat
|
|
1329
|
+
* @returns {Promise<Array>} Array of discovered service information
|
|
1330
|
+
*/
|
|
1331
|
+
discoverServices: (projectId, timeout) =>
|
|
1332
|
+
discoverServices(this, projectId, timeout),
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Register as a service and handle incoming work requests.
|
|
1336
|
+
* @param {string} projectId - The UUID of the project to serve
|
|
1337
|
+
* @param {Object} serviceInfo - Service information {serviceId, serviceName, description}
|
|
1338
|
+
* @param {function} onServiceRequest - Callback (data, responseHelper)
|
|
1339
|
+
* @param {Object} [extras] - Optional additional service metadata
|
|
1340
|
+
* @returns {Object} Service registration object with .stop() method
|
|
1341
|
+
*/
|
|
1342
|
+
serve: (projectId, serviceInfo, onServiceRequest, extras) =>
|
|
1343
|
+
serve(this, projectId, serviceInfo, onServiceRequest, extras),
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* Request a service to perform work and await its result.
|
|
1347
|
+
* @param {string} projectId - The UUID of the project
|
|
1348
|
+
* @param {string} serviceId - The ID of the service to request
|
|
1349
|
+
* @param {any} data - The request data
|
|
1350
|
+
* @param {number} [timeout] - Timeout in milliseconds (default: 10000)
|
|
1351
|
+
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
1352
|
+
* @returns {Promise<any>} Service response
|
|
1353
|
+
*/
|
|
1354
|
+
requestService: (projectId, serviceId, data, timeout, onProgress) =>
|
|
1355
|
+
requestService(this, projectId, serviceId, data, timeout, onProgress),
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* Run a query over every project you can read.
|
|
1360
|
+
*
|
|
1361
|
+
* `body` is the query AST. Its keys follow the usual client convention
|
|
1362
|
+
* (camelCase, e.g. `scope.projectIds`) and are converted to the wire
|
|
1363
|
+
* format automatically; clause heads and variables are plain strings you
|
|
1364
|
+
* write literally (e.g. `'span'`, `'?s1'`, `'vocab-link'`). Example:
|
|
1365
|
+
*
|
|
1366
|
+
* await client.query({
|
|
1367
|
+
* find: ['?s1', '?s2'],
|
|
1368
|
+
* where: [
|
|
1369
|
+
* ['span', '?s1', { layer: 'pos', value: 'NOUN' }],
|
|
1370
|
+
* ['span', '?s2', { layer: 'pos', value: 'VERB' }],
|
|
1371
|
+
* ['covers', '?s1', '?t1'], ['covers', '?s2', '?t2'],
|
|
1372
|
+
* ['precedes', '?t1', '?t2'],
|
|
1373
|
+
* ],
|
|
1374
|
+
* return: 'entities', // 'ids' (default) | 'entities' | 'count'
|
|
1375
|
+
* limit: 100,
|
|
1376
|
+
* });
|
|
1377
|
+
*
|
|
1378
|
+
* @param {Object} body - The query AST ({find, where, scope?, limit?, return?}).
|
|
1379
|
+
* @returns {Promise<Object>} For 'ids'/'entities': {columns, results, count, truncated}.
|
|
1380
|
+
* For 'count': {return: 'count', count}. Entity cells are full entity objects
|
|
1381
|
+
* (same shape as the GET endpoints).
|
|
1382
|
+
*/
|
|
1383
|
+
this.query = (body) =>
|
|
1384
|
+
this._request('POST', '/api/v1/query', { body });
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// --- Core methods ---
|
|
1388
|
+
|
|
1389
|
+
async _request(method, path, options = {}) {
|
|
1390
|
+
return makeRequest(this, method, path, options);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Enter strict mode for a specific document, requiring document version
|
|
1395
|
+
* headers so that conflicting concurrent writes are rejected.
|
|
1396
|
+
* @param {string} documentId - The ID of the document to track versions for
|
|
1397
|
+
*/
|
|
1398
|
+
enterStrictMode(documentId) {
|
|
1399
|
+
this.strictModeDocumentId = documentId;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/** Exit strict mode and stop tracking document versions for writes. */
|
|
1403
|
+
exitStrictMode() {
|
|
1404
|
+
this.strictModeDocumentId = null;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/** Begin a batch of operations. Subsequent API calls will be queued. */
|
|
1408
|
+
beginBatch() {
|
|
1409
|
+
this.isBatching = true;
|
|
1410
|
+
this.batchOperations = [];
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Submit all queued batch operations as a single batch request, executed
|
|
1415
|
+
* atomically. If any operation fails, all changes are rolled back.
|
|
1416
|
+
* @returns {Promise<Array>} Array of results corresponding to each operation
|
|
1417
|
+
*/
|
|
1418
|
+
async submitBatch() {
|
|
1419
|
+
if (!this.isBatching) {
|
|
1420
|
+
throw new Error('No active batch. Call beginBatch() first.');
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (this.batchOperations.length === 0) {
|
|
1424
|
+
this.isBatching = false;
|
|
1425
|
+
return [];
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
try {
|
|
1429
|
+
let url = `${this.baseUrl}/api/v1/batch`;
|
|
1430
|
+
const body = this.batchOperations.map(op => ({
|
|
1431
|
+
path: op.path,
|
|
1432
|
+
method: op.method.toUpperCase(),
|
|
1433
|
+
...(op.body && { body: op.body }),
|
|
1434
|
+
}));
|
|
1435
|
+
|
|
1436
|
+
const fetchOptions = {
|
|
1437
|
+
method: 'POST',
|
|
1438
|
+
headers: {
|
|
1439
|
+
'Authorization': `Bearer ${this.token}`,
|
|
1440
|
+
'Content-Type': 'application/json',
|
|
1441
|
+
},
|
|
1442
|
+
body: JSON.stringify(body),
|
|
1443
|
+
};
|
|
1444
|
+
const signal = timeoutSignal(this.timeout);
|
|
1445
|
+
if (signal) fetchOptions.signal = signal;
|
|
1446
|
+
|
|
1447
|
+
try {
|
|
1448
|
+
const response = await fetch(url, fetchOptions);
|
|
1449
|
+
if (!response.ok) {
|
|
1450
|
+
throw makeHttpError(response, await parseErrorBody(response), url, 'POST');
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const results = await response.json();
|
|
1454
|
+
|
|
1455
|
+
// Extract document versions from each batch response
|
|
1456
|
+
for (const result of results) {
|
|
1457
|
+
if (result.headers && result.headers['X-Document-Versions']) {
|
|
1458
|
+
try {
|
|
1459
|
+
const versionsMap = JSON.parse(result.headers['X-Document-Versions']);
|
|
1460
|
+
if (typeof versionsMap === 'object' && versionsMap !== null) {
|
|
1461
|
+
// Clone once per response, then merge — not once per entry.
|
|
1462
|
+
this.documentVersions = { ...this.documentVersions, ...versionsMap };
|
|
1463
|
+
}
|
|
1464
|
+
} catch (e) {
|
|
1465
|
+
console.warn('Failed to parse document versions header from batch response:', e);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
return results.map(result => transformResponse(result));
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
if (error.status) throw error;
|
|
1473
|
+
throw makeNetworkError(error, url, 'POST');
|
|
1474
|
+
}
|
|
1475
|
+
} finally {
|
|
1476
|
+
this.isBatching = false;
|
|
1477
|
+
this.batchOperations = [];
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/** Abort the current batch without executing any operations. */
|
|
1482
|
+
abortBatch() {
|
|
1483
|
+
this.isBatching = false;
|
|
1484
|
+
this.batchOperations = [];
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/**
|
|
1488
|
+
* Check if currently in batch mode.
|
|
1489
|
+
* @returns {boolean}
|
|
1490
|
+
*/
|
|
1491
|
+
isBatchMode() {
|
|
1492
|
+
return this.isBatching;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
/**
|
|
1496
|
+
* Authenticate and return a new client instance with token. This is the
|
|
1497
|
+
* single auth entry point — there is no `client.login` resource.
|
|
1498
|
+
* @param {string} baseUrl - The base URL for the API
|
|
1499
|
+
* @param {string} userId - User ID for authentication
|
|
1500
|
+
* @param {string} password - Password for authentication
|
|
1501
|
+
* @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
|
|
1502
|
+
* @returns {Promise<PlaidClient>} - Authenticated client instance
|
|
1503
|
+
*/
|
|
1504
|
+
static async login(baseUrl, userId, password, options = {}) {
|
|
1505
|
+
baseUrl = baseUrl.replace(/\/$/, '');
|
|
1506
|
+
const url = `${baseUrl}/api/v1/login`;
|
|
1507
|
+
try {
|
|
1508
|
+
const fetchOptions = {
|
|
1509
|
+
method: 'POST',
|
|
1510
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1511
|
+
body: JSON.stringify({ 'user-id': userId, password }),
|
|
1512
|
+
};
|
|
1513
|
+
const signal = timeoutSignal(options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS);
|
|
1514
|
+
if (signal) fetchOptions.signal = signal;
|
|
1515
|
+
|
|
1516
|
+
const response = await fetch(url, fetchOptions);
|
|
1517
|
+
if (!response.ok) {
|
|
1518
|
+
throw makeHttpError(response, await parseErrorBody(response), url, 'POST');
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
const data = await response.json();
|
|
1522
|
+
const token = data.token || '';
|
|
1523
|
+
return new PlaidClient(baseUrl, token, options);
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
if (error.status) throw error;
|
|
1526
|
+
throw makeNetworkError(error, url, 'POST');
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
export default PlaidClient;
|
|
1532
|
+
export { PlaidClient };
|