@larc-iu/plaid-client 0.2.0-alpha.0 → 0.2.0-alpha.11
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 +662 -105
- package/package.json +1 -1
- package/src/http.js +266 -38
- package/src/index.js +1435 -357
- package/src/provenance.js +274 -26
- package/src/serviceSchema.js +50 -30
- package/src/services.js +212 -37
- package/src/sse.js +51 -1
package/src/index.js
CHANGED
|
@@ -2,19 +2,46 @@
|
|
|
2
2
|
* plaid-client - JavaScript client for the Plaid annotation API
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { transformRequest, transformResponse } from
|
|
5
|
+
import { transformRequest, transformResponse } from "./transforms.js";
|
|
6
6
|
import {
|
|
7
|
-
makeRequest,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
makeRequest,
|
|
8
|
+
extractDocumentVersions,
|
|
9
|
+
parseErrorBody,
|
|
10
|
+
makeHttpError,
|
|
11
|
+
makeNetworkError,
|
|
12
|
+
timeoutSignal,
|
|
13
|
+
retryWhileBusy,
|
|
14
|
+
DEFAULT_TIMEOUT_MS,
|
|
15
|
+
DEFAULT_BATCH_TIMEOUT_MS,
|
|
16
|
+
} from "./http.js";
|
|
17
|
+
import { listAll, listPage, iterPages } from "./pagination.js";
|
|
18
|
+
import { createSSEConnection } from "./sse.js";
|
|
12
19
|
import {
|
|
13
20
|
discoverServices,
|
|
14
21
|
discardService,
|
|
15
22
|
serve,
|
|
16
23
|
requestService,
|
|
17
|
-
|
|
24
|
+
attachServiceRequest,
|
|
25
|
+
cancelServiceRequest,
|
|
26
|
+
} from "./services.js";
|
|
27
|
+
|
|
28
|
+
// Helper: normalize a document-read `layers` filter to the wire's
|
|
29
|
+
// comma-separated form. Accepts an array of layer ids or a ready-made string;
|
|
30
|
+
// anything empty becomes undefined so no `?layers=` is sent at all.
|
|
31
|
+
function layersParam(layers) {
|
|
32
|
+
if (layers === undefined || layers === null) return undefined;
|
|
33
|
+
const joined = Array.isArray(layers) ? layers.join(",") : String(layers);
|
|
34
|
+
return joined.length > 0 ? joined : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper: normalize an audit `opTypes` filter to the wire's comma-separated
|
|
38
|
+
// form. Accepts an array of op types or a ready-made string; anything empty
|
|
39
|
+
// becomes undefined so no `?op-types=` is sent at all.
|
|
40
|
+
function opTypesParam(opTypes) {
|
|
41
|
+
if (opTypes === undefined || opTypes === null) return undefined;
|
|
42
|
+
const joined = Array.isArray(opTypes) ? opTypes.join(",") : String(opTypes);
|
|
43
|
+
return joined.length > 0 ? joined : undefined;
|
|
44
|
+
}
|
|
18
45
|
|
|
19
46
|
// Helper: build body object, filtering out undefined values
|
|
20
47
|
function bodyOf(obj) {
|
|
@@ -25,6 +52,45 @@ function bodyOf(obj) {
|
|
|
25
52
|
return result;
|
|
26
53
|
}
|
|
27
54
|
|
|
55
|
+
// POST to an endpoint that takes no Authorization header (login, invite lookup
|
|
56
|
+
// and redemption). Deliberately does not go through PlaidClient._request: these
|
|
57
|
+
// are called before any client exists, which is the whole point.
|
|
58
|
+
async function anonymousPost(baseUrl, path, body, options = {}) {
|
|
59
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
60
|
+
const url = `${base}${path}`;
|
|
61
|
+
try {
|
|
62
|
+
const fetchOptions = {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify(body),
|
|
66
|
+
};
|
|
67
|
+
const signal = timeoutSignal(
|
|
68
|
+
options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS,
|
|
69
|
+
);
|
|
70
|
+
if (signal) fetchOptions.signal = signal;
|
|
71
|
+
|
|
72
|
+
const response = await fetch(url, fetchOptions);
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw makeHttpError(
|
|
75
|
+
response,
|
|
76
|
+
await parseErrorBody(response),
|
|
77
|
+
url,
|
|
78
|
+
"POST",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return transformResponse(await response.json());
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error.status) throw error;
|
|
84
|
+
throw makeNetworkError(error, url, "POST");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The server's cap on operations per batch request (plaid.rest-api.v1.batch).
|
|
90
|
+
* A batch queued past it is submitted as consecutive requests.
|
|
91
|
+
*/
|
|
92
|
+
const MAX_BATCH_OPS = 1000;
|
|
93
|
+
|
|
28
94
|
class PlaidClient {
|
|
29
95
|
/**
|
|
30
96
|
* Create a new PlaidClient instance
|
|
@@ -32,18 +98,33 @@ class PlaidClient {
|
|
|
32
98
|
* @param {string} token - The authentication token
|
|
33
99
|
* @param {object} [options] - Client options
|
|
34
100
|
* @param {number} [options.timeout=30000] - Per-request timeout in ms (0 or null disables it)
|
|
101
|
+
* @param {number} [options.batchTimeout=180000] - Timeout for batch submissions in ms (0 or null disables it)
|
|
35
102
|
*/
|
|
36
103
|
constructor(baseUrl, token, options = {}) {
|
|
37
|
-
this.baseUrl = baseUrl.replace(/\/$/,
|
|
104
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
38
105
|
this.token = token;
|
|
39
|
-
this.timeout =
|
|
106
|
+
this.timeout =
|
|
107
|
+
options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS;
|
|
108
|
+
// Batch submissions get a longer budget than a single request. Aborting a
|
|
109
|
+
// batch does NOT stop the server: it keeps running the transaction and
|
|
110
|
+
// holding the single SQLite write lock, so a client that gives up early
|
|
111
|
+
// just adds a retry on top of a write that is still in progress. Falls
|
|
112
|
+
// back to an explicit `timeout` when one was given.
|
|
113
|
+
this.batchTimeout =
|
|
114
|
+
options.batchTimeout !== undefined
|
|
115
|
+
? options.batchTimeout
|
|
116
|
+
: options.timeout !== undefined
|
|
117
|
+
? options.timeout
|
|
118
|
+
: DEFAULT_BATCH_TIMEOUT_MS;
|
|
40
119
|
this.isBatching = false;
|
|
41
120
|
this.batchOperations = [];
|
|
42
121
|
this.documentVersions = {};
|
|
43
122
|
this.strictModeDocumentId = null;
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
123
|
+
// The open logical operation (audit-log group), or null. While set, every
|
|
124
|
+
// write is stamped with `?group-id=` (+ `group-message`) so the audit log
|
|
125
|
+
// folds them into ONE expandable entry. See beginOperation / withOperation.
|
|
126
|
+
// Shape: { id, message, depth, written, refined }.
|
|
127
|
+
this.operationGroup = null;
|
|
47
128
|
// Optional callback fired once (per client) when any request returns HTTP
|
|
48
129
|
// 401 — i.e. the token is missing/expired/invalid. Apps use it to discard
|
|
49
130
|
// the stored token and route back to login. See makeRequest in http.js.
|
|
@@ -60,8 +141,9 @@ class PlaidClient {
|
|
|
60
141
|
* @param {any} [metadata] - Metadata for the link. Omit to leave unset; pass null to send JSON null.
|
|
61
142
|
*/
|
|
62
143
|
create: (vocabItem, tokens, metadata, auditMessage) =>
|
|
63
|
-
this._request(
|
|
64
|
-
|
|
144
|
+
this._request("POST", "/api/v1/vocab-links", {
|
|
145
|
+
auditMessage,
|
|
146
|
+
body: bodyOf({ "vocab-item": vocabItem, tokens, metadata }),
|
|
65
147
|
}),
|
|
66
148
|
/**
|
|
67
149
|
* Create multiple vocab links in a single operation. Entries may
|
|
@@ -71,28 +153,37 @@ class PlaidClient {
|
|
|
71
153
|
* @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
|
|
72
154
|
*/
|
|
73
155
|
bulkCreate: (body, auditMessage) =>
|
|
74
|
-
this._request(
|
|
156
|
+
this._request("POST", "/api/v1/vocab-links/bulk", {
|
|
157
|
+
auditMessage,
|
|
158
|
+
body,
|
|
159
|
+
}),
|
|
75
160
|
/**
|
|
76
161
|
* Delete multiple vocab links in a single operation. Provide an array of IDs.
|
|
77
162
|
* @param {string[]} body - The vocab link IDs to delete
|
|
78
163
|
*/
|
|
79
164
|
bulkDelete: (body, auditMessage) =>
|
|
80
|
-
this._request(
|
|
165
|
+
this._request("DELETE", "/api/v1/vocab-links/bulk", {
|
|
166
|
+
auditMessage,
|
|
167
|
+
body,
|
|
168
|
+
}),
|
|
81
169
|
/**
|
|
82
170
|
* Replace all metadata for a vocab link. The entire metadata map is replaced - existing metadata keys not included in the request will be removed.
|
|
83
171
|
* @param {string} id - The resource ID
|
|
84
172
|
* @param {any} body - The request body
|
|
85
173
|
*/
|
|
86
174
|
setMetadata: (id, body, auditMessage) =>
|
|
87
|
-
this._request(
|
|
88
|
-
|
|
175
|
+
this._request("PUT", `/api/v1/vocab-links/${id}/metadata`, {
|
|
176
|
+
auditMessage,
|
|
177
|
+
rawBody: body,
|
|
178
|
+
skipResponseTransform: true,
|
|
89
179
|
}),
|
|
90
180
|
/**
|
|
91
181
|
* Remove all metadata from a vocab link.
|
|
92
182
|
* @param {string} id - The resource ID
|
|
93
183
|
*/
|
|
94
184
|
deleteMetadata: (id, auditMessage) =>
|
|
95
|
-
this._request(
|
|
185
|
+
this._request("DELETE", `/api/v1/vocab-links/${id}/metadata`, {
|
|
186
|
+
auditMessage,
|
|
96
187
|
skipResponseTransform: true,
|
|
97
188
|
}),
|
|
98
189
|
/**
|
|
@@ -101,8 +192,10 @@ class PlaidClient {
|
|
|
101
192
|
* @param {any} body - The metadata patch
|
|
102
193
|
*/
|
|
103
194
|
patchMetadata: (id, body, auditMessage) =>
|
|
104
|
-
this._request(
|
|
105
|
-
|
|
195
|
+
this._request("PATCH", `/api/v1/vocab-links/${id}/metadata`, {
|
|
196
|
+
auditMessage,
|
|
197
|
+
rawBody: body,
|
|
198
|
+
skipResponseTransform: true,
|
|
106
199
|
}),
|
|
107
200
|
/**
|
|
108
201
|
* Get a vocab link by ID
|
|
@@ -110,15 +203,15 @@ class PlaidClient {
|
|
|
110
203
|
* @param {string} [asOf] - Temporal query timestamp
|
|
111
204
|
*/
|
|
112
205
|
get: (id, asOf) =>
|
|
113
|
-
this._request(
|
|
114
|
-
queryParams: {
|
|
206
|
+
this._request("GET", `/api/v1/vocab-links/${id}`, {
|
|
207
|
+
queryParams: { "as-of": asOf },
|
|
115
208
|
}),
|
|
116
209
|
/**
|
|
117
210
|
* Delete a vocab link
|
|
118
211
|
* @param {string} id - The resource ID
|
|
119
212
|
*/
|
|
120
213
|
delete: (id, auditMessage) =>
|
|
121
|
-
this._request(
|
|
214
|
+
this._request("DELETE", `/api/v1/vocab-links/${id}`, { auditMessage }),
|
|
122
215
|
};
|
|
123
216
|
|
|
124
217
|
this.vocabLayers = {
|
|
@@ -129,22 +222,23 @@ class PlaidClient {
|
|
|
129
222
|
* @param {string} [asOf] - Temporal query timestamp
|
|
130
223
|
*/
|
|
131
224
|
get: (id, includeItems, asOf) =>
|
|
132
|
-
this._request(
|
|
133
|
-
queryParams: {
|
|
225
|
+
this._request("GET", `/api/v1/vocab-layers/${id}`, {
|
|
226
|
+
queryParams: { "include-items": includeItems, "as-of": asOf },
|
|
134
227
|
}),
|
|
135
228
|
/**
|
|
136
229
|
* Delete a vocab layer.
|
|
137
230
|
* @param {string} id - The resource ID
|
|
138
231
|
*/
|
|
139
232
|
delete: (id, auditMessage) =>
|
|
140
|
-
this._request(
|
|
233
|
+
this._request("DELETE", `/api/v1/vocab-layers/${id}`, { auditMessage }),
|
|
141
234
|
/**
|
|
142
235
|
* Update a vocab layer's name.
|
|
143
236
|
* @param {string} id - The resource ID
|
|
144
237
|
* @param {string} name - The name
|
|
145
238
|
*/
|
|
146
239
|
update: (id, name, auditMessage) =>
|
|
147
|
-
this._request(
|
|
240
|
+
this._request("PATCH", `/api/v1/vocab-layers/${id}`, {
|
|
241
|
+
auditMessage,
|
|
148
242
|
body: bodyOf({ name }),
|
|
149
243
|
}),
|
|
150
244
|
/**
|
|
@@ -155,9 +249,11 @@ class PlaidClient {
|
|
|
155
249
|
* @param {any} configValue - Configuration value to set
|
|
156
250
|
*/
|
|
157
251
|
setConfig: (id, namespace, configKey, configValue, auditMessage) =>
|
|
158
|
-
this._request(
|
|
159
|
-
|
|
160
|
-
|
|
252
|
+
this._request(
|
|
253
|
+
"PUT",
|
|
254
|
+
`/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`,
|
|
255
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
256
|
+
),
|
|
161
257
|
/**
|
|
162
258
|
* Remove a configuration value for a layer.
|
|
163
259
|
* @param {string} id - The resource ID
|
|
@@ -165,9 +261,11 @@ class PlaidClient {
|
|
|
165
261
|
* @param {string} configKey - The config key
|
|
166
262
|
*/
|
|
167
263
|
deleteConfig: (id, namespace, configKey, auditMessage) =>
|
|
168
|
-
this._request(
|
|
169
|
-
|
|
170
|
-
|
|
264
|
+
this._request(
|
|
265
|
+
"DELETE",
|
|
266
|
+
`/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`,
|
|
267
|
+
{ auditMessage, skipResponseTransform: true },
|
|
268
|
+
),
|
|
171
269
|
/**
|
|
172
270
|
* List all vocab layers accessible to user. Transparently follows
|
|
173
271
|
* pagination cursors and returns the full flat array.
|
|
@@ -175,7 +273,7 @@ class PlaidClient {
|
|
|
175
273
|
* @param {string} [asOf] - Temporal query timestamp
|
|
176
274
|
*/
|
|
177
275
|
list: (asOf) =>
|
|
178
|
-
listAll(this,
|
|
276
|
+
listAll(this, "/api/v1/vocab-layers", { query: { "as-of": asOf } }),
|
|
179
277
|
/**
|
|
180
278
|
* Fetch a single page of vocab layers.
|
|
181
279
|
* @param {object} [opts]
|
|
@@ -185,7 +283,11 @@ class PlaidClient {
|
|
|
185
283
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
186
284
|
*/
|
|
187
285
|
listPage: ({ limit, cursor, asOf } = {}) =>
|
|
188
|
-
listPage(this,
|
|
286
|
+
listPage(this, "/api/v1/vocab-layers", {
|
|
287
|
+
limit,
|
|
288
|
+
cursor,
|
|
289
|
+
query: { "as-of": asOf },
|
|
290
|
+
}),
|
|
189
291
|
/**
|
|
190
292
|
* Async-iterate vocab layers page by page; yields each page's entries array.
|
|
191
293
|
* @param {object} [opts]
|
|
@@ -195,13 +297,17 @@ class PlaidClient {
|
|
|
195
297
|
* @returns {AsyncGenerator<Array>}
|
|
196
298
|
*/
|
|
197
299
|
iterPages: ({ pageSize, asOf } = {}) =>
|
|
198
|
-
iterPages(this,
|
|
300
|
+
iterPages(this, "/api/v1/vocab-layers", {
|
|
301
|
+
pageSize,
|
|
302
|
+
query: { "as-of": asOf },
|
|
303
|
+
}),
|
|
199
304
|
/**
|
|
200
305
|
* Create a new vocab layer. Note: this also registers the user as a maintainer.
|
|
201
306
|
* @param {string} name - The name
|
|
202
307
|
*/
|
|
203
308
|
create: (name, auditMessage) =>
|
|
204
|
-
this._request(
|
|
309
|
+
this._request("POST", "/api/v1/vocab-layers", {
|
|
310
|
+
auditMessage,
|
|
205
311
|
body: bodyOf({ name }),
|
|
206
312
|
}),
|
|
207
313
|
/**
|
|
@@ -210,14 +316,22 @@ class PlaidClient {
|
|
|
210
316
|
* @param {string} userId - The user ID
|
|
211
317
|
*/
|
|
212
318
|
addMaintainer: (id, userId, auditMessage) =>
|
|
213
|
-
this._request(
|
|
319
|
+
this._request(
|
|
320
|
+
"POST",
|
|
321
|
+
`/api/v1/vocab-layers/${id}/maintainers/${userId}`,
|
|
322
|
+
{ auditMessage },
|
|
323
|
+
),
|
|
214
324
|
/**
|
|
215
325
|
* Remove a user's maintainer privileges for this vocab layer.
|
|
216
326
|
* @param {string} id - The resource ID
|
|
217
327
|
* @param {string} userId - The user ID
|
|
218
328
|
*/
|
|
219
329
|
removeMaintainer: (id, userId, auditMessage) =>
|
|
220
|
-
this._request(
|
|
330
|
+
this._request(
|
|
331
|
+
"DELETE",
|
|
332
|
+
`/api/v1/vocab-layers/${id}/maintainers/${userId}`,
|
|
333
|
+
{ auditMessage },
|
|
334
|
+
),
|
|
221
335
|
};
|
|
222
336
|
|
|
223
337
|
this.relations = {
|
|
@@ -227,15 +341,18 @@ class PlaidClient {
|
|
|
227
341
|
* @param {any} body - The request body
|
|
228
342
|
*/
|
|
229
343
|
setMetadata: (relationId, body, auditMessage) =>
|
|
230
|
-
this._request(
|
|
231
|
-
|
|
344
|
+
this._request("PUT", `/api/v1/relations/${relationId}/metadata`, {
|
|
345
|
+
auditMessage,
|
|
346
|
+
rawBody: body,
|
|
347
|
+
skipResponseTransform: true,
|
|
232
348
|
}),
|
|
233
349
|
/**
|
|
234
350
|
* Remove all metadata from a relation.
|
|
235
351
|
* @param {string} relationId - The relation ID
|
|
236
352
|
*/
|
|
237
353
|
deleteMetadata: (relationId, auditMessage) =>
|
|
238
|
-
this._request(
|
|
354
|
+
this._request("DELETE", `/api/v1/relations/${relationId}/metadata`, {
|
|
355
|
+
auditMessage,
|
|
239
356
|
skipResponseTransform: true,
|
|
240
357
|
}),
|
|
241
358
|
/**
|
|
@@ -244,8 +361,10 @@ class PlaidClient {
|
|
|
244
361
|
* @param {any} body - The metadata patch
|
|
245
362
|
*/
|
|
246
363
|
patchMetadata: (relationId, body, auditMessage) =>
|
|
247
|
-
this._request(
|
|
248
|
-
|
|
364
|
+
this._request("PATCH", `/api/v1/relations/${relationId}/metadata`, {
|
|
365
|
+
auditMessage,
|
|
366
|
+
rawBody: body,
|
|
367
|
+
skipResponseTransform: true,
|
|
249
368
|
}),
|
|
250
369
|
/**
|
|
251
370
|
* Update the target span of a relation.
|
|
@@ -253,8 +372,9 @@ class PlaidClient {
|
|
|
253
372
|
* @param {string} spanId - The span ID
|
|
254
373
|
*/
|
|
255
374
|
setTarget: (relationId, spanId, auditMessage) =>
|
|
256
|
-
this._request(
|
|
257
|
-
|
|
375
|
+
this._request("PUT", `/api/v1/relations/${relationId}/target`, {
|
|
376
|
+
auditMessage,
|
|
377
|
+
body: bodyOf({ "span-id": spanId }),
|
|
258
378
|
}),
|
|
259
379
|
/**
|
|
260
380
|
* Get a relation by ID.
|
|
@@ -262,22 +382,25 @@ class PlaidClient {
|
|
|
262
382
|
* @param {string} [asOf] - Temporal query timestamp
|
|
263
383
|
*/
|
|
264
384
|
get: (relationId, asOf) =>
|
|
265
|
-
this._request(
|
|
266
|
-
queryParams: {
|
|
385
|
+
this._request("GET", `/api/v1/relations/${relationId}`, {
|
|
386
|
+
queryParams: { "as-of": asOf },
|
|
267
387
|
}),
|
|
268
388
|
/**
|
|
269
389
|
* Delete a relation.
|
|
270
390
|
* @param {string} relationId - The relation ID
|
|
271
391
|
*/
|
|
272
392
|
delete: (relationId, auditMessage) =>
|
|
273
|
-
this._request(
|
|
393
|
+
this._request("DELETE", `/api/v1/relations/${relationId}`, {
|
|
394
|
+
auditMessage,
|
|
395
|
+
}),
|
|
274
396
|
/**
|
|
275
397
|
* Update a relation's value.
|
|
276
398
|
* @param {string} relationId - The relation ID
|
|
277
399
|
* @param {any} value - The value
|
|
278
400
|
*/
|
|
279
401
|
update: (relationId, value, auditMessage) =>
|
|
280
|
-
this._request(
|
|
402
|
+
this._request("PATCH", `/api/v1/relations/${relationId}`, {
|
|
403
|
+
auditMessage,
|
|
281
404
|
body: bodyOf({ value }),
|
|
282
405
|
}),
|
|
283
406
|
/**
|
|
@@ -286,8 +409,9 @@ class PlaidClient {
|
|
|
286
409
|
* @param {string} spanId - The span ID
|
|
287
410
|
*/
|
|
288
411
|
setSource: (relationId, spanId, auditMessage) =>
|
|
289
|
-
this._request(
|
|
290
|
-
|
|
412
|
+
this._request("PUT", `/api/v1/relations/${relationId}/source`, {
|
|
413
|
+
auditMessage,
|
|
414
|
+
body: bodyOf({ "span-id": spanId }),
|
|
291
415
|
}),
|
|
292
416
|
/**
|
|
293
417
|
* Create a new relation. A relation is a directed edge between two spans
|
|
@@ -300,8 +424,15 @@ class PlaidClient {
|
|
|
300
424
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
301
425
|
*/
|
|
302
426
|
create: (layerId, sourceId, targetId, value, metadata, auditMessage) =>
|
|
303
|
-
this._request(
|
|
304
|
-
|
|
427
|
+
this._request("POST", "/api/v1/relations", {
|
|
428
|
+
auditMessage,
|
|
429
|
+
body: bodyOf({
|
|
430
|
+
"layer-id": layerId,
|
|
431
|
+
"source-id": sourceId,
|
|
432
|
+
"target-id": targetId,
|
|
433
|
+
value,
|
|
434
|
+
metadata,
|
|
435
|
+
}),
|
|
305
436
|
}),
|
|
306
437
|
/**
|
|
307
438
|
* Create multiple relations in a single operation.
|
|
@@ -309,13 +440,16 @@ class PlaidClient {
|
|
|
309
440
|
* @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
|
|
310
441
|
*/
|
|
311
442
|
bulkCreate: (body, auditMessage) =>
|
|
312
|
-
this._request(
|
|
443
|
+
this._request("POST", "/api/v1/relations/bulk", { auditMessage, body }),
|
|
313
444
|
/**
|
|
314
445
|
* Delete multiple relations in a single operation. Provide an array of IDs.
|
|
315
446
|
* @param {Array} body - The request body
|
|
316
447
|
*/
|
|
317
448
|
bulkDelete: (body, auditMessage) =>
|
|
318
|
-
this._request(
|
|
449
|
+
this._request("DELETE", "/api/v1/relations/bulk", {
|
|
450
|
+
auditMessage,
|
|
451
|
+
body,
|
|
452
|
+
}),
|
|
319
453
|
};
|
|
320
454
|
|
|
321
455
|
this.spanLayers = {
|
|
@@ -326,10 +460,18 @@ class PlaidClient {
|
|
|
326
460
|
* @param {string} configKey - The config key
|
|
327
461
|
* @param {any} configValue - Configuration value to set
|
|
328
462
|
*/
|
|
329
|
-
setConfig: (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
463
|
+
setConfig: (
|
|
464
|
+
spanLayerId,
|
|
465
|
+
namespace,
|
|
466
|
+
configKey,
|
|
467
|
+
configValue,
|
|
468
|
+
auditMessage,
|
|
469
|
+
) =>
|
|
470
|
+
this._request(
|
|
471
|
+
"PUT",
|
|
472
|
+
`/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`,
|
|
473
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
474
|
+
),
|
|
333
475
|
/**
|
|
334
476
|
* Remove a configuration value for a layer.
|
|
335
477
|
* @param {string} spanLayerId - The span layer ID
|
|
@@ -337,31 +479,36 @@ class PlaidClient {
|
|
|
337
479
|
* @param {string} configKey - The config key
|
|
338
480
|
*/
|
|
339
481
|
deleteConfig: (spanLayerId, namespace, configKey, auditMessage) =>
|
|
340
|
-
this._request(
|
|
341
|
-
|
|
342
|
-
|
|
482
|
+
this._request(
|
|
483
|
+
"DELETE",
|
|
484
|
+
`/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`,
|
|
485
|
+
{ auditMessage, skipResponseTransform: true },
|
|
486
|
+
),
|
|
343
487
|
/**
|
|
344
488
|
* Get a span layer by ID.
|
|
345
489
|
* @param {string} spanLayerId - The span layer ID
|
|
346
490
|
* @param {string} [asOf] - Temporal query timestamp
|
|
347
491
|
*/
|
|
348
492
|
get: (spanLayerId, asOf) =>
|
|
349
|
-
this._request(
|
|
350
|
-
queryParams: {
|
|
493
|
+
this._request("GET", `/api/v1/span-layers/${spanLayerId}`, {
|
|
494
|
+
queryParams: { "as-of": asOf },
|
|
351
495
|
}),
|
|
352
496
|
/**
|
|
353
497
|
* Delete a span layer.
|
|
354
498
|
* @param {string} spanLayerId - The span layer ID
|
|
355
499
|
*/
|
|
356
500
|
delete: (spanLayerId, auditMessage) =>
|
|
357
|
-
this._request(
|
|
501
|
+
this._request("DELETE", `/api/v1/span-layers/${spanLayerId}`, {
|
|
502
|
+
auditMessage,
|
|
503
|
+
}),
|
|
358
504
|
/**
|
|
359
505
|
* Update a span layer's name.
|
|
360
506
|
* @param {string} spanLayerId - The span layer ID
|
|
361
507
|
* @param {string} name - The name
|
|
362
508
|
*/
|
|
363
509
|
update: (spanLayerId, name, auditMessage) =>
|
|
364
|
-
this._request(
|
|
510
|
+
this._request("PATCH", `/api/v1/span-layers/${spanLayerId}`, {
|
|
511
|
+
auditMessage,
|
|
365
512
|
body: bodyOf({ name }),
|
|
366
513
|
}),
|
|
367
514
|
/**
|
|
@@ -370,8 +517,9 @@ class PlaidClient {
|
|
|
370
517
|
* @param {string} name - The name
|
|
371
518
|
*/
|
|
372
519
|
create: (tokenLayerId, name, auditMessage) =>
|
|
373
|
-
this._request(
|
|
374
|
-
|
|
520
|
+
this._request("POST", "/api/v1/span-layers", {
|
|
521
|
+
auditMessage,
|
|
522
|
+
body: bodyOf({ "token-layer-id": tokenLayerId, name }),
|
|
375
523
|
}),
|
|
376
524
|
/**
|
|
377
525
|
* Shift a span layer's display order.
|
|
@@ -379,7 +527,8 @@ class PlaidClient {
|
|
|
379
527
|
* @param {string} direction - The direction ("up" or "down")
|
|
380
528
|
*/
|
|
381
529
|
shift: (spanLayerId, direction, auditMessage) =>
|
|
382
|
-
this._request(
|
|
530
|
+
this._request("POST", `/api/v1/span-layers/${spanLayerId}/shift`, {
|
|
531
|
+
auditMessage,
|
|
383
532
|
body: bodyOf({ direction }),
|
|
384
533
|
}),
|
|
385
534
|
};
|
|
@@ -391,7 +540,8 @@ class PlaidClient {
|
|
|
391
540
|
* @param {Array} tokens - The tokens
|
|
392
541
|
*/
|
|
393
542
|
setTokens: (spanId, tokens, auditMessage) =>
|
|
394
|
-
this._request(
|
|
543
|
+
this._request("PUT", `/api/v1/spans/${spanId}/tokens`, {
|
|
544
|
+
auditMessage,
|
|
395
545
|
body: bodyOf({ tokens }),
|
|
396
546
|
}),
|
|
397
547
|
/**
|
|
@@ -403,8 +553,14 @@ class PlaidClient {
|
|
|
403
553
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
404
554
|
*/
|
|
405
555
|
create: (spanLayerId, tokens, value, metadata, auditMessage) =>
|
|
406
|
-
this._request(
|
|
407
|
-
|
|
556
|
+
this._request("POST", "/api/v1/spans", {
|
|
557
|
+
auditMessage,
|
|
558
|
+
body: bodyOf({
|
|
559
|
+
"span-layer-id": spanLayerId,
|
|
560
|
+
tokens,
|
|
561
|
+
value,
|
|
562
|
+
metadata,
|
|
563
|
+
}),
|
|
408
564
|
}),
|
|
409
565
|
/**
|
|
410
566
|
* Get a span by ID.
|
|
@@ -412,22 +568,23 @@ class PlaidClient {
|
|
|
412
568
|
* @param {string} [asOf] - Temporal query timestamp
|
|
413
569
|
*/
|
|
414
570
|
get: (spanId, asOf) =>
|
|
415
|
-
this._request(
|
|
416
|
-
queryParams: {
|
|
571
|
+
this._request("GET", `/api/v1/spans/${spanId}`, {
|
|
572
|
+
queryParams: { "as-of": asOf },
|
|
417
573
|
}),
|
|
418
574
|
/**
|
|
419
575
|
* Delete a span.
|
|
420
576
|
* @param {string} spanId - The span ID
|
|
421
577
|
*/
|
|
422
578
|
delete: (spanId, auditMessage) =>
|
|
423
|
-
this._request(
|
|
579
|
+
this._request("DELETE", `/api/v1/spans/${spanId}`, { auditMessage }),
|
|
424
580
|
/**
|
|
425
581
|
* Update a span's value.
|
|
426
582
|
* @param {string} spanId - The span ID
|
|
427
583
|
* @param {any} value - The value
|
|
428
584
|
*/
|
|
429
585
|
update: (spanId, value, auditMessage) =>
|
|
430
|
-
this._request(
|
|
586
|
+
this._request("PATCH", `/api/v1/spans/${spanId}`, {
|
|
587
|
+
auditMessage,
|
|
431
588
|
body: bodyOf({ value }),
|
|
432
589
|
}),
|
|
433
590
|
/**
|
|
@@ -436,28 +593,31 @@ class PlaidClient {
|
|
|
436
593
|
* @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
|
|
437
594
|
*/
|
|
438
595
|
bulkCreate: (body, auditMessage) =>
|
|
439
|
-
this._request(
|
|
596
|
+
this._request("POST", "/api/v1/spans/bulk", { auditMessage, body }),
|
|
440
597
|
/**
|
|
441
598
|
* Delete multiple spans in a single operation. Provide an array of IDs.
|
|
442
599
|
* @param {Array} body - The request body
|
|
443
600
|
*/
|
|
444
601
|
bulkDelete: (body, auditMessage) =>
|
|
445
|
-
this._request(
|
|
602
|
+
this._request("DELETE", "/api/v1/spans/bulk", { auditMessage, body }),
|
|
446
603
|
/**
|
|
447
604
|
* Replace all metadata for a span.
|
|
448
605
|
* @param {string} spanId - The span ID
|
|
449
606
|
* @param {any} body - The request body
|
|
450
607
|
*/
|
|
451
608
|
setMetadata: (spanId, body, auditMessage) =>
|
|
452
|
-
this._request(
|
|
453
|
-
|
|
609
|
+
this._request("PUT", `/api/v1/spans/${spanId}/metadata`, {
|
|
610
|
+
auditMessage,
|
|
611
|
+
rawBody: body,
|
|
612
|
+
skipResponseTransform: true,
|
|
454
613
|
}),
|
|
455
614
|
/**
|
|
456
615
|
* Remove all metadata from a span.
|
|
457
616
|
* @param {string} spanId - The span ID
|
|
458
617
|
*/
|
|
459
618
|
deleteMetadata: (spanId, auditMessage) =>
|
|
460
|
-
this._request(
|
|
619
|
+
this._request("DELETE", `/api/v1/spans/${spanId}/metadata`, {
|
|
620
|
+
auditMessage,
|
|
461
621
|
skipResponseTransform: true,
|
|
462
622
|
}),
|
|
463
623
|
/**
|
|
@@ -466,8 +626,10 @@ class PlaidClient {
|
|
|
466
626
|
* @param {any} body - The metadata patch
|
|
467
627
|
*/
|
|
468
628
|
patchMetadata: (spanId, body, auditMessage) =>
|
|
469
|
-
this._request(
|
|
470
|
-
|
|
629
|
+
this._request("PATCH", `/api/v1/spans/${spanId}/metadata`, {
|
|
630
|
+
auditMessage,
|
|
631
|
+
rawBody: body,
|
|
632
|
+
skipResponseTransform: true,
|
|
471
633
|
}),
|
|
472
634
|
};
|
|
473
635
|
|
|
@@ -478,8 +640,10 @@ class PlaidClient {
|
|
|
478
640
|
* @param {Array} body - The request body
|
|
479
641
|
*/
|
|
480
642
|
submit: (body, auditMessage) =>
|
|
481
|
-
this._request(
|
|
482
|
-
|
|
643
|
+
this._request("POST", "/api/v1/batch", {
|
|
644
|
+
auditMessage,
|
|
645
|
+
body,
|
|
646
|
+
noBatch: true,
|
|
483
647
|
}),
|
|
484
648
|
};
|
|
485
649
|
|
|
@@ -490,15 +654,18 @@ class PlaidClient {
|
|
|
490
654
|
* @param {any} body - The request body
|
|
491
655
|
*/
|
|
492
656
|
setMetadata: (textId, body, auditMessage) =>
|
|
493
|
-
this._request(
|
|
494
|
-
|
|
657
|
+
this._request("PUT", `/api/v1/texts/${textId}/metadata`, {
|
|
658
|
+
auditMessage,
|
|
659
|
+
rawBody: body,
|
|
660
|
+
skipResponseTransform: true,
|
|
495
661
|
}),
|
|
496
662
|
/**
|
|
497
663
|
* Remove all metadata from a text.
|
|
498
664
|
* @param {string} textId - The text ID
|
|
499
665
|
*/
|
|
500
666
|
deleteMetadata: (textId, auditMessage) =>
|
|
501
|
-
this._request(
|
|
667
|
+
this._request("DELETE", `/api/v1/texts/${textId}/metadata`, {
|
|
668
|
+
auditMessage,
|
|
502
669
|
skipResponseTransform: true,
|
|
503
670
|
}),
|
|
504
671
|
/**
|
|
@@ -507,8 +674,10 @@ class PlaidClient {
|
|
|
507
674
|
* @param {any} body - The metadata patch
|
|
508
675
|
*/
|
|
509
676
|
patchMetadata: (textId, body, auditMessage) =>
|
|
510
|
-
this._request(
|
|
511
|
-
|
|
677
|
+
this._request("PATCH", `/api/v1/texts/${textId}/metadata`, {
|
|
678
|
+
auditMessage,
|
|
679
|
+
rawBody: body,
|
|
680
|
+
skipResponseTransform: true,
|
|
512
681
|
}),
|
|
513
682
|
/**
|
|
514
683
|
* Create a new text in a document's text layer. A text is a container for
|
|
@@ -519,8 +688,14 @@ class PlaidClient {
|
|
|
519
688
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
520
689
|
*/
|
|
521
690
|
create: (textLayerId, documentId, body, metadata, auditMessage) =>
|
|
522
|
-
this._request(
|
|
523
|
-
|
|
691
|
+
this._request("POST", "/api/v1/texts", {
|
|
692
|
+
auditMessage,
|
|
693
|
+
body: bodyOf({
|
|
694
|
+
"text-layer-id": textLayerId,
|
|
695
|
+
"document-id": documentId,
|
|
696
|
+
body,
|
|
697
|
+
metadata,
|
|
698
|
+
}),
|
|
524
699
|
}),
|
|
525
700
|
/**
|
|
526
701
|
* Get a text.
|
|
@@ -528,24 +703,30 @@ class PlaidClient {
|
|
|
528
703
|
* @param {string} [asOf] - Temporal query timestamp
|
|
529
704
|
*/
|
|
530
705
|
get: (textId, asOf) =>
|
|
531
|
-
this._request(
|
|
532
|
-
queryParams: {
|
|
706
|
+
this._request("GET", `/api/v1/texts/${textId}`, {
|
|
707
|
+
queryParams: { "as-of": asOf },
|
|
533
708
|
}),
|
|
534
709
|
/**
|
|
535
710
|
* Delete a text and all dependent data.
|
|
536
711
|
* @param {string} textId - The text ID
|
|
537
712
|
*/
|
|
538
713
|
delete: (textId, auditMessage) =>
|
|
539
|
-
this._request(
|
|
714
|
+
this._request("DELETE", `/api/v1/texts/${textId}`, { auditMessage }),
|
|
540
715
|
/**
|
|
541
716
|
* Update a text's body. A diff is computed and token indices are updated
|
|
542
717
|
* so that tokens remain intact. Alternatively, `body` can be a list of
|
|
543
|
-
* edit directives
|
|
718
|
+
* edit directives (code-point indices, applied in order):
|
|
719
|
+
* {type: 'delete', index, value: count}
|
|
720
|
+
* {type: 'insert', index, value: string}
|
|
721
|
+
* {type: 'replace', index, length, value: string} — unlike
|
|
722
|
+
* delete+insert, a token covering the whole range is resized to keep
|
|
723
|
+
* it (respell a word in place without losing its annotations).
|
|
544
724
|
* @param {string} textId - The text ID
|
|
545
725
|
* @param {any} body - The request body
|
|
546
726
|
*/
|
|
547
727
|
update: (textId, body, auditMessage) =>
|
|
548
|
-
this._request(
|
|
728
|
+
this._request("PATCH", `/api/v1/texts/${textId}`, {
|
|
729
|
+
auditMessage,
|
|
549
730
|
body: bodyOf({ body }),
|
|
550
731
|
}),
|
|
551
732
|
};
|
|
@@ -556,42 +737,58 @@ class PlaidClient {
|
|
|
556
737
|
* returns the full flat array. Admin-or-maintainer only.
|
|
557
738
|
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
558
739
|
* @param {object} [opts]
|
|
559
|
-
* @param {string} [opts.q] - Filter to
|
|
740
|
+
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
560
741
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
561
742
|
*/
|
|
562
743
|
list: ({ q, asOf } = {}) =>
|
|
563
|
-
listAll(this,
|
|
744
|
+
listAll(this, "/api/v1/users", { query: { q, "as-of": asOf } }),
|
|
564
745
|
/**
|
|
565
746
|
* Fetch a single page of users (optionally filtered by `q`).
|
|
566
747
|
* @param {object} [opts]
|
|
567
|
-
* @param {string} [opts.q] - Filter to
|
|
748
|
+
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
568
749
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
569
750
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
570
751
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
571
752
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
572
753
|
*/
|
|
573
754
|
listPage: ({ q, limit, cursor, asOf } = {}) =>
|
|
574
|
-
listPage(this,
|
|
755
|
+
listPage(this, "/api/v1/users", {
|
|
756
|
+
limit,
|
|
757
|
+
cursor,
|
|
758
|
+
query: { q, "as-of": asOf },
|
|
759
|
+
}),
|
|
575
760
|
/**
|
|
576
761
|
* Async-iterate users page by page; yields each page's entries array.
|
|
577
762
|
* @param {object} [opts]
|
|
578
|
-
* @param {string} [opts.q] - Filter to
|
|
763
|
+
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
579
764
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
580
765
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
581
766
|
* 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.
|
|
582
767
|
* @returns {AsyncGenerator<Array>}
|
|
583
768
|
*/
|
|
584
769
|
iterPages: ({ q, pageSize, asOf } = {}) =>
|
|
585
|
-
iterPages(this,
|
|
770
|
+
iterPages(this, "/api/v1/users", {
|
|
771
|
+
pageSize,
|
|
772
|
+
query: { q, "as-of": asOf },
|
|
773
|
+
}),
|
|
586
774
|
/**
|
|
587
|
-
* Create a new user
|
|
588
|
-
* @param {string}
|
|
775
|
+
* Create a new user.
|
|
776
|
+
* @param {string} email - The account's email address. It becomes the
|
|
777
|
+
* user's id and is what they log in with; it can never be changed.
|
|
589
778
|
* @param {string} password - The password
|
|
590
779
|
* @param {boolean} isAdmin - Whether the user is an admin
|
|
780
|
+
* @param {string} [displayName] - How the user is shown in the UI.
|
|
781
|
+
* Defaults to the local part of the email.
|
|
591
782
|
*/
|
|
592
|
-
create: (
|
|
593
|
-
this._request(
|
|
594
|
-
|
|
783
|
+
create: (email, password, isAdmin, displayName, auditMessage) =>
|
|
784
|
+
this._request("POST", "/api/v1/users", {
|
|
785
|
+
auditMessage,
|
|
786
|
+
body: bodyOf({
|
|
787
|
+
email,
|
|
788
|
+
password,
|
|
789
|
+
"is-admin": isAdmin,
|
|
790
|
+
"display-name": displayName,
|
|
791
|
+
}),
|
|
595
792
|
}),
|
|
596
793
|
/**
|
|
597
794
|
* Get audit log for a user's actions. Transparently follows pagination
|
|
@@ -601,10 +798,19 @@ class PlaidClient {
|
|
|
601
798
|
* @param {string} [startTime] - Start of time range
|
|
602
799
|
* @param {string} [endTime] - End of time range
|
|
603
800
|
* @param {string} [asOf] - Temporal query timestamp
|
|
801
|
+
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
802
|
+
* types, spelled as in an entry's `op/type` (e.g.
|
|
803
|
+
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
804
|
+
* one of its operations matches, carrying only the ones that did.
|
|
604
805
|
*/
|
|
605
|
-
audit: (userId, startTime, endTime, asOf) =>
|
|
806
|
+
audit: (userId, startTime, endTime, asOf, opTypes) =>
|
|
606
807
|
listAll(this, `/api/v1/users/${userId}/audit`, {
|
|
607
|
-
query: {
|
|
808
|
+
query: {
|
|
809
|
+
"start-time": startTime,
|
|
810
|
+
"end-time": endTime,
|
|
811
|
+
"as-of": asOf,
|
|
812
|
+
"op-types": opTypesParam(opTypes),
|
|
813
|
+
},
|
|
608
814
|
}),
|
|
609
815
|
/**
|
|
610
816
|
* Get a user by ID
|
|
@@ -612,8 +818,8 @@ class PlaidClient {
|
|
|
612
818
|
* @param {string} [asOf] - Temporal query timestamp
|
|
613
819
|
*/
|
|
614
820
|
get: (id, asOf) =>
|
|
615
|
-
this._request(
|
|
616
|
-
queryParams: {
|
|
821
|
+
this._request("GET", `/api/v1/users/${id}`, {
|
|
822
|
+
queryParams: { "as-of": asOf },
|
|
617
823
|
}),
|
|
618
824
|
/**
|
|
619
825
|
* Deactivate a user. Users are never hard-deleted: deactivation
|
|
@@ -624,7 +830,7 @@ class PlaidClient {
|
|
|
624
830
|
* @param {string} id - The resource ID
|
|
625
831
|
*/
|
|
626
832
|
delete: (id, auditMessage) =>
|
|
627
|
-
this._request(
|
|
833
|
+
this._request("DELETE", `/api/v1/users/${id}`, { auditMessage }),
|
|
628
834
|
/**
|
|
629
835
|
* Reactivate a deactivated user, restoring their ability to log in.
|
|
630
836
|
* Project memberships, vocab maintainerships, and API tokens removed
|
|
@@ -632,22 +838,148 @@ class PlaidClient {
|
|
|
632
838
|
* @param {string} id - The resource ID
|
|
633
839
|
*/
|
|
634
840
|
activate: (id, auditMessage) =>
|
|
635
|
-
this._request(
|
|
841
|
+
this._request("POST", `/api/v1/users/${id}/activate`, { auditMessage }),
|
|
636
842
|
/**
|
|
637
|
-
* Modify a user. Admins may change the
|
|
638
|
-
* status of any user. All other users may only modify their own
|
|
639
|
-
* or password.
|
|
640
|
-
*
|
|
843
|
+
* Modify a user. Admins may change the display name, password, and admin
|
|
844
|
+
* status of any user. All other users may only modify their own display
|
|
845
|
+
* name or password.
|
|
846
|
+
*
|
|
847
|
+
* A user's id is their email address and is fixed for the life of the
|
|
848
|
+
* account — it is what they log in with, so nothing can change it.
|
|
849
|
+
* @param {string} id - The resource ID (the user's email address)
|
|
641
850
|
* @param {string} [password] - New password
|
|
642
|
-
* @param {string} [
|
|
851
|
+
* @param {string} [displayName] - New display name
|
|
643
852
|
* @param {boolean} [isAdmin] - New admin status
|
|
644
853
|
*/
|
|
645
|
-
update: (id, password,
|
|
646
|
-
this._request(
|
|
647
|
-
|
|
854
|
+
update: (id, password, displayName, isAdmin, auditMessage) =>
|
|
855
|
+
this._request("PATCH", `/api/v1/users/${id}`, {
|
|
856
|
+
auditMessage,
|
|
857
|
+
body: bodyOf({
|
|
858
|
+
password,
|
|
859
|
+
"display-name": displayName,
|
|
860
|
+
"is-admin": isAdmin,
|
|
861
|
+
}),
|
|
862
|
+
}),
|
|
863
|
+
/**
|
|
864
|
+
* Build a URL for a user's profile picture, suitable for use directly as
|
|
865
|
+
* an `<img>` src. An image element cannot send an Authorization header,
|
|
866
|
+
* so the session token rides in the query string, the same way document
|
|
867
|
+
* media does.
|
|
868
|
+
*
|
|
869
|
+
* Pass the user record's `avatarHash` as the second argument whenever you
|
|
870
|
+
* have it: the URL then addresses that exact picture, so the browser can
|
|
871
|
+
* cache it indefinitely and still pick up a replacement the moment the
|
|
872
|
+
* user changes it. Returns null when the user has no picture, so callers
|
|
873
|
+
* can fall back to initials without a wasted request.
|
|
874
|
+
* @param {string} id - The user ID
|
|
875
|
+
* @param {string} [avatarHash] - The user record's `avatarHash`
|
|
876
|
+
* @returns {string|null}
|
|
877
|
+
*/
|
|
878
|
+
avatarUrl: (id, avatarHash) => {
|
|
879
|
+
if (avatarHash === null) return null;
|
|
880
|
+
const params = new URLSearchParams({ token: this.token });
|
|
881
|
+
if (avatarHash) params.set("v", avatarHash);
|
|
882
|
+
return `${this.baseUrl}/api/v1/users/${id}/avatar?${params}`;
|
|
883
|
+
},
|
|
884
|
+
/**
|
|
885
|
+
* Fetch a user's profile picture as raw bytes. Most callers want
|
|
886
|
+
* avatarUrl() instead. This one is for non-browser consumers.
|
|
887
|
+
* @param {string} id - The user ID
|
|
888
|
+
*/
|
|
889
|
+
getAvatar: (id) =>
|
|
890
|
+
this._request("GET", `/api/v1/users/${id}/avatar`, {
|
|
891
|
+
noBatch: true,
|
|
892
|
+
binaryResponse: true,
|
|
893
|
+
}),
|
|
894
|
+
/**
|
|
895
|
+
* Upload a profile picture. Your own, or anyone's if you are an admin.
|
|
896
|
+
* The server center-crops to a square, scales to the configured edge
|
|
897
|
+
* length, and re-encodes, so no client-side resizing is needed. Accepts
|
|
898
|
+
* PNG, JPEG, WebP, and GIF. Resolves to the updated user record.
|
|
899
|
+
* @param {string} id - The user ID
|
|
900
|
+
* @param {File} file - The image to upload
|
|
901
|
+
*/
|
|
902
|
+
setAvatar: (id, file, auditMessage) => {
|
|
903
|
+
const fd = new FormData();
|
|
904
|
+
fd.append("file", file);
|
|
905
|
+
return this._request("PUT", `/api/v1/users/${id}/avatar`, {
|
|
906
|
+
auditMessage,
|
|
907
|
+
body: fd,
|
|
908
|
+
formData: true,
|
|
909
|
+
noBatch: true,
|
|
910
|
+
});
|
|
911
|
+
},
|
|
912
|
+
/**
|
|
913
|
+
* Remove a profile picture. Your own, or anyone's if you are an admin.
|
|
914
|
+
* @param {string} id - The user ID
|
|
915
|
+
*/
|
|
916
|
+
deleteAvatar: (id, auditMessage) =>
|
|
917
|
+
this._request("DELETE", `/api/v1/users/${id}/avatar`, {
|
|
918
|
+
auditMessage,
|
|
919
|
+
noBatch: true,
|
|
648
920
|
}),
|
|
649
921
|
};
|
|
650
922
|
|
|
923
|
+
this.userData = {
|
|
924
|
+
/**
|
|
925
|
+
* List a user's private data entries ({key, updatedAt}, plus value when
|
|
926
|
+
* includeValues). Owner or admin only.
|
|
927
|
+
* @param {string} userId
|
|
928
|
+
* @param {object} [opts]
|
|
929
|
+
* @param {string} [opts.prefix] - Only keys starting with this prefix
|
|
930
|
+
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
931
|
+
*/
|
|
932
|
+
list: (userId, { prefix, includeValues } = {}) =>
|
|
933
|
+
this._request("GET", `/api/v1/users/${userId}/data`, {
|
|
934
|
+
queryParams: { prefix, "include-values": includeValues },
|
|
935
|
+
}),
|
|
936
|
+
/**
|
|
937
|
+
* Read one private data entry ({key, updatedAt, value}); 404 if absent.
|
|
938
|
+
* @param {string} userId
|
|
939
|
+
* @param {string} key
|
|
940
|
+
*/
|
|
941
|
+
get: (userId, key) =>
|
|
942
|
+
this._request(
|
|
943
|
+
"GET",
|
|
944
|
+
`/api/v1/users/${userId}/data/${encodeURIComponent(key)}`,
|
|
945
|
+
),
|
|
946
|
+
/**
|
|
947
|
+
* Create or replace one private data entry. `value` is any JSON (up to
|
|
948
|
+
* 1 MB), stored as sent. Not audited, not batchable.
|
|
949
|
+
*
|
|
950
|
+
* The server stores it verbatim, but this client recases object keys on
|
|
951
|
+
* the way out and back like any other body (`myKey` <-> `my-key`), so a
|
|
952
|
+
* value whose keys are camelCase round-trips unchanged while one keyed
|
|
953
|
+
* by arbitrary strings does not. Put such a map under a `metadata` key,
|
|
954
|
+
* which both clients pass through untouched.
|
|
955
|
+
* @param {string} userId
|
|
956
|
+
* @param {string} key
|
|
957
|
+
* @param {*} value
|
|
958
|
+
*/
|
|
959
|
+
put: (userId, key, value) =>
|
|
960
|
+
this._request(
|
|
961
|
+
"PUT",
|
|
962
|
+
`/api/v1/users/${userId}/data/${encodeURIComponent(key)}`,
|
|
963
|
+
{
|
|
964
|
+
body: value,
|
|
965
|
+
noBatch: true,
|
|
966
|
+
},
|
|
967
|
+
),
|
|
968
|
+
/**
|
|
969
|
+
* Delete one private data entry; 404 if absent.
|
|
970
|
+
* @param {string} userId
|
|
971
|
+
* @param {string} key
|
|
972
|
+
*/
|
|
973
|
+
delete: (userId, key) =>
|
|
974
|
+
this._request(
|
|
975
|
+
"DELETE",
|
|
976
|
+
`/api/v1/users/${userId}/data/${encodeURIComponent(key)}`,
|
|
977
|
+
{
|
|
978
|
+
noBatch: true,
|
|
979
|
+
},
|
|
980
|
+
),
|
|
981
|
+
};
|
|
982
|
+
|
|
651
983
|
this.apiTokens = {
|
|
652
984
|
/**
|
|
653
985
|
* List a user's named API tokens. Never includes the signed token
|
|
@@ -656,8 +988,7 @@ class PlaidClient {
|
|
|
656
988
|
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
657
989
|
* @param {string} userId - The user ID who owns the tokens
|
|
658
990
|
*/
|
|
659
|
-
list: (userId) =>
|
|
660
|
-
listAll(this, `/api/v1/users/${userId}/tokens`),
|
|
991
|
+
list: (userId) => listAll(this, `/api/v1/users/${userId}/tokens`),
|
|
661
992
|
/**
|
|
662
993
|
* Fetch a single page of a user's named API tokens.
|
|
663
994
|
* @param {string} userId - The user ID who owns the tokens
|
|
@@ -688,7 +1019,8 @@ class PlaidClient {
|
|
|
688
1019
|
* @returns {Promise<{id: string, name: string, token: string}>}
|
|
689
1020
|
*/
|
|
690
1021
|
create: (userId, name, auditMessage) =>
|
|
691
|
-
this._request(
|
|
1022
|
+
this._request("POST", `/api/v1/users/${userId}/tokens`, {
|
|
1023
|
+
auditMessage,
|
|
692
1024
|
body: bodyOf({ name }),
|
|
693
1025
|
}),
|
|
694
1026
|
/**
|
|
@@ -697,7 +1029,109 @@ class PlaidClient {
|
|
|
697
1029
|
* @param {string} tokenId - The token ID to revoke
|
|
698
1030
|
*/
|
|
699
1031
|
revoke: (userId, tokenId, auditMessage) =>
|
|
700
|
-
this._request(
|
|
1032
|
+
this._request("DELETE", `/api/v1/users/${userId}/tokens/${tokenId}`, {
|
|
1033
|
+
auditMessage,
|
|
1034
|
+
}),
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
this.invites = {
|
|
1038
|
+
/**
|
|
1039
|
+
* List invites you minted, oldest first. With `projectId`, lists that
|
|
1040
|
+
* project's invites instead (including co-maintainers'), which requires
|
|
1041
|
+
* maintainer or admin on it. Never includes invite codes — the code is
|
|
1042
|
+
* returned once, by create(), and is not recoverable afterward.
|
|
1043
|
+
* Transparently follows pagination cursors and returns the full flat array.
|
|
1044
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
1045
|
+
* @param {object} [opts]
|
|
1046
|
+
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1047
|
+
*/
|
|
1048
|
+
list: ({ projectId } = {}) =>
|
|
1049
|
+
listAll(this, "/api/v1/invites", {
|
|
1050
|
+
query: { "project-id": projectId },
|
|
1051
|
+
}),
|
|
1052
|
+
/**
|
|
1053
|
+
* Fetch a single page of invites.
|
|
1054
|
+
* @param {object} [opts]
|
|
1055
|
+
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1056
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1057
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1058
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1059
|
+
*/
|
|
1060
|
+
listPage: ({ projectId, limit, cursor } = {}) =>
|
|
1061
|
+
listPage(this, "/api/v1/invites", {
|
|
1062
|
+
limit,
|
|
1063
|
+
cursor,
|
|
1064
|
+
query: { "project-id": projectId },
|
|
1065
|
+
}),
|
|
1066
|
+
/**
|
|
1067
|
+
* Async-iterate invites page by page; yields each page's entries array.
|
|
1068
|
+
* @param {object} [opts]
|
|
1069
|
+
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1070
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
1071
|
+
* 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.
|
|
1072
|
+
* @returns {AsyncGenerator<Array>}
|
|
1073
|
+
*/
|
|
1074
|
+
iterPages: ({ projectId, pageSize } = {}) =>
|
|
1075
|
+
iterPages(this, "/api/v1/invites", {
|
|
1076
|
+
pageSize,
|
|
1077
|
+
query: { "project-id": projectId },
|
|
1078
|
+
}),
|
|
1079
|
+
/**
|
|
1080
|
+
* Mint an invite. The returned `code` is shown ONLY here — it is never
|
|
1081
|
+
* stored and cannot be recovered, so build and hand off the link now.
|
|
1082
|
+
* Use PlaidClient.inviteUrl() to turn it into one.
|
|
1083
|
+
*
|
|
1084
|
+
* Admins may mint anything. A project maintainer may mint role grants on
|
|
1085
|
+
* projects they maintain, and nothing else: no admin grant, no grantless
|
|
1086
|
+
* invite, no password resets — so for a non-admin, projectId/projectRole
|
|
1087
|
+
* are required in practice (403 without them).
|
|
1088
|
+
*
|
|
1089
|
+
* EVERY option is optional; `create()` with no arguments mints a
|
|
1090
|
+
* single-use signup link granting nothing but an account. Two pairing
|
|
1091
|
+
* rules the server enforces with a 400: projectId and projectRole must be
|
|
1092
|
+
* given TOGETHER, and targetUserId may not be combined with projectId,
|
|
1093
|
+
* grantAdmin, or a maxUses above 1.
|
|
1094
|
+
* @param {object} [opts]
|
|
1095
|
+
* @param {string} [opts.projectId] - Project the redeemer joins (requires projectRole)
|
|
1096
|
+
* @param {string} [opts.projectRole] - "reader" | "writer" | "maintainer" (requires projectId)
|
|
1097
|
+
* @param {boolean} [opts.grantAdmin] - Make the new account a global admin (admin only)
|
|
1098
|
+
* @param {string} [opts.targetUserId] - Password reset for that user instead of a signup; admin only, single-use, grants nothing
|
|
1099
|
+
* @param {number} [opts.maxUses] - How many accounts this link may create (default 1)
|
|
1100
|
+
* @param {number} [opts.ttlDays] - Days until it expires (default 14, max 365)
|
|
1101
|
+
* @param {string} [opts.note] - Human label shown in your invite list
|
|
1102
|
+
* @returns {Promise<{id: string, code: string, kind: string, status: string}>}
|
|
1103
|
+
*/
|
|
1104
|
+
create: (
|
|
1105
|
+
{
|
|
1106
|
+
projectId,
|
|
1107
|
+
projectRole,
|
|
1108
|
+
grantAdmin,
|
|
1109
|
+
targetUserId,
|
|
1110
|
+
maxUses,
|
|
1111
|
+
ttlDays,
|
|
1112
|
+
note,
|
|
1113
|
+
} = {},
|
|
1114
|
+
auditMessage,
|
|
1115
|
+
) =>
|
|
1116
|
+
this._request("POST", "/api/v1/invites", {
|
|
1117
|
+
auditMessage,
|
|
1118
|
+
body: bodyOf({
|
|
1119
|
+
"project-id": projectId,
|
|
1120
|
+
"project-role": projectRole,
|
|
1121
|
+
"grant-admin": grantAdmin,
|
|
1122
|
+
"target-user-id": targetUserId,
|
|
1123
|
+
"max-uses": maxUses,
|
|
1124
|
+
"ttl-days": ttlDays,
|
|
1125
|
+
note,
|
|
1126
|
+
}),
|
|
1127
|
+
}),
|
|
1128
|
+
/**
|
|
1129
|
+
* Revoke an invite, killing the link immediately. Idempotent. Allowed for
|
|
1130
|
+
* the creator, an admin, or a maintainer of the invite's project.
|
|
1131
|
+
* @param {string} id - The invite ID
|
|
1132
|
+
*/
|
|
1133
|
+
revoke: (id, auditMessage) =>
|
|
1134
|
+
this._request("DELETE", `/api/v1/invites/${id}`, { auditMessage }),
|
|
701
1135
|
};
|
|
702
1136
|
|
|
703
1137
|
this.tokenLayers = {
|
|
@@ -707,7 +1141,8 @@ class PlaidClient {
|
|
|
707
1141
|
* @param {string} direction - The direction ("up" or "down")
|
|
708
1142
|
*/
|
|
709
1143
|
shift: (tokenLayerId, direction, auditMessage) =>
|
|
710
|
-
this._request(
|
|
1144
|
+
this._request("POST", `/api/v1/token-layers/${tokenLayerId}/shift`, {
|
|
1145
|
+
auditMessage,
|
|
711
1146
|
body: bodyOf({ direction }),
|
|
712
1147
|
}),
|
|
713
1148
|
/**
|
|
@@ -717,9 +1152,21 @@ class PlaidClient {
|
|
|
717
1152
|
* @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.
|
|
718
1153
|
* @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).
|
|
719
1154
|
*/
|
|
720
|
-
create: (
|
|
721
|
-
|
|
722
|
-
|
|
1155
|
+
create: (
|
|
1156
|
+
textLayerId,
|
|
1157
|
+
name,
|
|
1158
|
+
overlapMode,
|
|
1159
|
+
parentTokenLayerId,
|
|
1160
|
+
auditMessage,
|
|
1161
|
+
) =>
|
|
1162
|
+
this._request("POST", "/api/v1/token-layers", {
|
|
1163
|
+
auditMessage,
|
|
1164
|
+
body: bodyOf({
|
|
1165
|
+
"text-layer-id": textLayerId,
|
|
1166
|
+
name,
|
|
1167
|
+
"overlap-mode": overlapMode,
|
|
1168
|
+
"parent-token-layer-id": parentTokenLayerId,
|
|
1169
|
+
}),
|
|
723
1170
|
}),
|
|
724
1171
|
/**
|
|
725
1172
|
* Set a configuration value for a layer in an editor namespace.
|
|
@@ -728,10 +1175,18 @@ class PlaidClient {
|
|
|
728
1175
|
* @param {string} configKey - The config key
|
|
729
1176
|
* @param {any} configValue - Configuration value to set
|
|
730
1177
|
*/
|
|
731
|
-
setConfig: (
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1178
|
+
setConfig: (
|
|
1179
|
+
tokenLayerId,
|
|
1180
|
+
namespace,
|
|
1181
|
+
configKey,
|
|
1182
|
+
configValue,
|
|
1183
|
+
auditMessage,
|
|
1184
|
+
) =>
|
|
1185
|
+
this._request(
|
|
1186
|
+
"PUT",
|
|
1187
|
+
`/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`,
|
|
1188
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
1189
|
+
),
|
|
735
1190
|
/**
|
|
736
1191
|
* Remove a configuration value for a layer.
|
|
737
1192
|
* @param {string} tokenLayerId - The token layer ID
|
|
@@ -739,31 +1194,36 @@ class PlaidClient {
|
|
|
739
1194
|
* @param {string} configKey - The config key
|
|
740
1195
|
*/
|
|
741
1196
|
deleteConfig: (tokenLayerId, namespace, configKey, auditMessage) =>
|
|
742
|
-
this._request(
|
|
743
|
-
|
|
744
|
-
|
|
1197
|
+
this._request(
|
|
1198
|
+
"DELETE",
|
|
1199
|
+
`/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`,
|
|
1200
|
+
{ auditMessage, skipResponseTransform: true },
|
|
1201
|
+
),
|
|
745
1202
|
/**
|
|
746
1203
|
* Get a token layer by ID.
|
|
747
1204
|
* @param {string} tokenLayerId - The token layer ID
|
|
748
1205
|
* @param {string} [asOf] - Temporal query timestamp
|
|
749
1206
|
*/
|
|
750
1207
|
get: (tokenLayerId, asOf) =>
|
|
751
|
-
this._request(
|
|
752
|
-
queryParams: {
|
|
1208
|
+
this._request("GET", `/api/v1/token-layers/${tokenLayerId}`, {
|
|
1209
|
+
queryParams: { "as-of": asOf },
|
|
753
1210
|
}),
|
|
754
1211
|
/**
|
|
755
1212
|
* Delete a token layer.
|
|
756
1213
|
* @param {string} tokenLayerId - The token layer ID
|
|
757
1214
|
*/
|
|
758
1215
|
delete: (tokenLayerId, auditMessage) =>
|
|
759
|
-
this._request(
|
|
1216
|
+
this._request("DELETE", `/api/v1/token-layers/${tokenLayerId}`, {
|
|
1217
|
+
auditMessage,
|
|
1218
|
+
}),
|
|
760
1219
|
/**
|
|
761
1220
|
* Update a token layer's name.
|
|
762
1221
|
* @param {string} tokenLayerId - The token layer ID
|
|
763
1222
|
* @param {string} name - The name
|
|
764
1223
|
*/
|
|
765
1224
|
update: (tokenLayerId, name, auditMessage) =>
|
|
766
|
-
this._request(
|
|
1225
|
+
this._request("PATCH", `/api/v1/token-layers/${tokenLayerId}`, {
|
|
1226
|
+
auditMessage,
|
|
767
1227
|
body: bodyOf({ name }),
|
|
768
1228
|
}),
|
|
769
1229
|
};
|
|
@@ -775,29 +1235,33 @@ class PlaidClient {
|
|
|
775
1235
|
* @param {string} [asOf] - Temporal query timestamp
|
|
776
1236
|
*/
|
|
777
1237
|
checkLock: (documentId, asOf) =>
|
|
778
|
-
this._request(
|
|
779
|
-
queryParams: {
|
|
1238
|
+
this._request("GET", `/api/v1/documents/${documentId}/lock`, {
|
|
1239
|
+
queryParams: { "as-of": asOf },
|
|
780
1240
|
}),
|
|
781
1241
|
/**
|
|
782
1242
|
* Acquire or refresh a document lock
|
|
783
1243
|
* @param {string} documentId - The document ID
|
|
784
1244
|
*/
|
|
785
1245
|
acquireLock: (documentId, auditMessage) =>
|
|
786
|
-
this._request(
|
|
1246
|
+
this._request("POST", `/api/v1/documents/${documentId}/lock`, {
|
|
1247
|
+
auditMessage,
|
|
1248
|
+
}),
|
|
787
1249
|
/**
|
|
788
1250
|
* Release a document lock
|
|
789
1251
|
* @param {string} documentId - The document ID
|
|
790
1252
|
*/
|
|
791
1253
|
releaseLock: (documentId, auditMessage) =>
|
|
792
|
-
this._request(
|
|
1254
|
+
this._request("DELETE", `/api/v1/documents/${documentId}/lock`, {
|
|
1255
|
+
auditMessage,
|
|
1256
|
+
}),
|
|
793
1257
|
/**
|
|
794
|
-
* Get media file for a document
|
|
1258
|
+
* Get the media file for a document. Media is not versioned, so there is
|
|
1259
|
+
* no as-of form: the route refuses the parameter. Prefer the document's
|
|
1260
|
+
* own `mediaUrl`, which carries the file's version for caching.
|
|
795
1261
|
* @param {string} documentId - The document ID
|
|
796
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
797
1262
|
*/
|
|
798
|
-
getMedia: (documentId
|
|
799
|
-
this._request(
|
|
800
|
-
queryParams: { 'as-of': asOf },
|
|
1263
|
+
getMedia: (documentId) =>
|
|
1264
|
+
this._request("GET", `/api/v1/documents/${documentId}/media`, {
|
|
801
1265
|
noBatch: true,
|
|
802
1266
|
binaryResponse: true,
|
|
803
1267
|
}),
|
|
@@ -805,12 +1269,18 @@ class PlaidClient {
|
|
|
805
1269
|
* Upload a media file for a document. Uses Apache Tika for content validation.
|
|
806
1270
|
* @param {string} documentId - The document ID
|
|
807
1271
|
* @param {File} file - The file to upload
|
|
1272
|
+
* @param {string} [auditMessage] - Custom audit-log message for this write
|
|
1273
|
+
* @param {object} [options] - `{ onProgress }`: called with `{ loaded, total }` (bytes) as the file goes up. In a browser the upload then travels by XMLHttpRequest, and the request timeout only fires when the upload stalls, not on total time.
|
|
808
1274
|
*/
|
|
809
|
-
uploadMedia: (documentId, file, auditMessage) => {
|
|
1275
|
+
uploadMedia: (documentId, file, auditMessage, { onProgress } = {}) => {
|
|
810
1276
|
const fd = new FormData();
|
|
811
|
-
fd.append(
|
|
812
|
-
return this._request(
|
|
813
|
-
|
|
1277
|
+
fd.append("file", file);
|
|
1278
|
+
return this._request("PUT", `/api/v1/documents/${documentId}/media`, {
|
|
1279
|
+
auditMessage,
|
|
1280
|
+
body: fd,
|
|
1281
|
+
formData: true,
|
|
1282
|
+
noBatch: true,
|
|
1283
|
+
onUploadProgress: onProgress,
|
|
814
1284
|
});
|
|
815
1285
|
},
|
|
816
1286
|
/**
|
|
@@ -818,7 +1288,8 @@ class PlaidClient {
|
|
|
818
1288
|
* @param {string} documentId - The document ID
|
|
819
1289
|
*/
|
|
820
1290
|
deleteMedia: (documentId, auditMessage) =>
|
|
821
|
-
this._request(
|
|
1291
|
+
this._request("DELETE", `/api/v1/documents/${documentId}/media`, {
|
|
1292
|
+
auditMessage,
|
|
822
1293
|
noBatch: true,
|
|
823
1294
|
}),
|
|
824
1295
|
/**
|
|
@@ -827,15 +1298,18 @@ class PlaidClient {
|
|
|
827
1298
|
* @param {any} body - The request body
|
|
828
1299
|
*/
|
|
829
1300
|
setMetadata: (documentId, body, auditMessage) =>
|
|
830
|
-
this._request(
|
|
831
|
-
|
|
1301
|
+
this._request("PUT", `/api/v1/documents/${documentId}/metadata`, {
|
|
1302
|
+
auditMessage,
|
|
1303
|
+
rawBody: body,
|
|
1304
|
+
skipResponseTransform: true,
|
|
832
1305
|
}),
|
|
833
1306
|
/**
|
|
834
1307
|
* Remove all metadata from a document.
|
|
835
1308
|
* @param {string} documentId - The document ID
|
|
836
1309
|
*/
|
|
837
1310
|
deleteMetadata: (documentId, auditMessage) =>
|
|
838
|
-
this._request(
|
|
1311
|
+
this._request("DELETE", `/api/v1/documents/${documentId}/metadata`, {
|
|
1312
|
+
auditMessage,
|
|
839
1313
|
skipResponseTransform: true,
|
|
840
1314
|
}),
|
|
841
1315
|
/**
|
|
@@ -844,8 +1318,10 @@ class PlaidClient {
|
|
|
844
1318
|
* @param {any} body - The metadata patch
|
|
845
1319
|
*/
|
|
846
1320
|
patchMetadata: (documentId, body, auditMessage) =>
|
|
847
|
-
this._request(
|
|
848
|
-
|
|
1321
|
+
this._request("PATCH", `/api/v1/documents/${documentId}/metadata`, {
|
|
1322
|
+
auditMessage,
|
|
1323
|
+
rawBody: body,
|
|
1324
|
+
skipResponseTransform: true,
|
|
849
1325
|
}),
|
|
850
1326
|
/**
|
|
851
1327
|
* Get audit log for a document. Transparently follows pagination cursors
|
|
@@ -855,34 +1331,78 @@ class PlaidClient {
|
|
|
855
1331
|
* @param {string} [startTime] - Start of time range
|
|
856
1332
|
* @param {string} [endTime] - End of time range
|
|
857
1333
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1334
|
+
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
1335
|
+
* types, spelled as in an entry's `op/type` (e.g.
|
|
1336
|
+
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
1337
|
+
* one of its operations matches, carrying only the ones that did.
|
|
858
1338
|
*/
|
|
859
|
-
audit: (documentId, startTime, endTime, asOf) =>
|
|
1339
|
+
audit: (documentId, startTime, endTime, asOf, opTypes) =>
|
|
860
1340
|
listAll(this, `/api/v1/documents/${documentId}/audit`, {
|
|
861
|
-
query: {
|
|
1341
|
+
query: {
|
|
1342
|
+
"start-time": startTime,
|
|
1343
|
+
"end-time": endTime,
|
|
1344
|
+
"as-of": asOf,
|
|
1345
|
+
"op-types": opTypesParam(opTypes),
|
|
1346
|
+
},
|
|
1347
|
+
}),
|
|
1348
|
+
/**
|
|
1349
|
+
* Restore a document to its state at an earlier time, as one
|
|
1350
|
+
* operation: what was deleted since then comes back under its original
|
|
1351
|
+
* id, what was added since is removed, and what changed is set back,
|
|
1352
|
+
* across every layer. A layer deleted since then, or a vocabulary entry
|
|
1353
|
+
* that no longer exists, is skipped and reported under `skipped`.
|
|
1354
|
+
* Resolves to a summary of the changes. Maintainers only.
|
|
1355
|
+
* @param {string} documentId - The document ID
|
|
1356
|
+
* @param {string} asOf - The moment to go back to (ISO-8601 instant),
|
|
1357
|
+
* typically a history entry's `endTime`
|
|
1358
|
+
* @param {object} [options] - `{ dryRun }`: with `dryRun` true nothing
|
|
1359
|
+
* is written and the summary says what would change
|
|
1360
|
+
* @param {string} [auditMessage] - Custom audit message for this operation
|
|
1361
|
+
*/
|
|
1362
|
+
restore: (documentId, asOf, { dryRun } = {}, auditMessage) =>
|
|
1363
|
+
this._request("POST", `/api/v1/documents/${documentId}/restore`, {
|
|
1364
|
+
queryParams: { "as-of": asOf, "dry-run": dryRun ? "true" : undefined },
|
|
1365
|
+
auditMessage,
|
|
862
1366
|
}),
|
|
863
1367
|
/**
|
|
864
1368
|
* Get a document. Set `includeBody` to true to include all data.
|
|
1369
|
+
*
|
|
1370
|
+
* `layers` narrows a body read to the layers you name (ids of any kind:
|
|
1371
|
+
* text, token, span, or relation). A layer comes back when it is named
|
|
1372
|
+
* or is an ancestor of a named layer, and carries its own
|
|
1373
|
+
* texts/tokens/spans/relations/vocabs only when it is itself named — so
|
|
1374
|
+
* name the text layer too if you also want the text body. An id that is
|
|
1375
|
+
* not a layer of this document's project is an error, not a quietly
|
|
1376
|
+
* smaller response. Requires `includeBody`.
|
|
865
1377
|
* @param {string} documentId - The document ID
|
|
866
1378
|
* @param {boolean} [includeBody] - Include document body data
|
|
867
1379
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1380
|
+
* @param {string[]|string} [layers] - Layer ids to restrict a body read to
|
|
868
1381
|
*/
|
|
869
|
-
get: (documentId, includeBody, asOf) =>
|
|
870
|
-
this._request(
|
|
871
|
-
queryParams: {
|
|
1382
|
+
get: (documentId, includeBody, asOf, layers) =>
|
|
1383
|
+
this._request("GET", `/api/v1/documents/${documentId}`, {
|
|
1384
|
+
queryParams: {
|
|
1385
|
+
"include-body": includeBody,
|
|
1386
|
+
"as-of": asOf,
|
|
1387
|
+
layers: layersParam(layers),
|
|
1388
|
+
},
|
|
872
1389
|
}),
|
|
873
1390
|
/**
|
|
874
1391
|
* Delete a document and all data contained.
|
|
875
1392
|
* @param {string} documentId - The document ID
|
|
876
1393
|
*/
|
|
877
1394
|
delete: (documentId, auditMessage) =>
|
|
878
|
-
this._request(
|
|
1395
|
+
this._request("DELETE", `/api/v1/documents/${documentId}`, {
|
|
1396
|
+
auditMessage,
|
|
1397
|
+
}),
|
|
879
1398
|
/**
|
|
880
1399
|
* Update a document's name.
|
|
881
1400
|
* @param {string} documentId - The document ID
|
|
882
1401
|
* @param {string} name - The name
|
|
883
1402
|
*/
|
|
884
1403
|
update: (documentId, name, auditMessage) =>
|
|
885
|
-
this._request(
|
|
1404
|
+
this._request("PATCH", `/api/v1/documents/${documentId}`, {
|
|
1405
|
+
auditMessage,
|
|
886
1406
|
body: bodyOf({ name }),
|
|
887
1407
|
}),
|
|
888
1408
|
/**
|
|
@@ -892,8 +1412,9 @@ class PlaidClient {
|
|
|
892
1412
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
893
1413
|
*/
|
|
894
1414
|
create: (projectId, name, metadata, auditMessage) =>
|
|
895
|
-
this._request(
|
|
896
|
-
|
|
1415
|
+
this._request("POST", "/api/v1/documents", {
|
|
1416
|
+
auditMessage,
|
|
1417
|
+
body: bodyOf({ "project-id": projectId, name, metadata }),
|
|
897
1418
|
}),
|
|
898
1419
|
};
|
|
899
1420
|
|
|
@@ -904,28 +1425,36 @@ class PlaidClient {
|
|
|
904
1425
|
* @param {string} userId - The user ID
|
|
905
1426
|
*/
|
|
906
1427
|
addWriter: (id, userId, auditMessage) =>
|
|
907
|
-
this._request(
|
|
1428
|
+
this._request("POST", `/api/v1/projects/${id}/writers/${userId}`, {
|
|
1429
|
+
auditMessage,
|
|
1430
|
+
}),
|
|
908
1431
|
/**
|
|
909
1432
|
* Remove a user's writer privileges for this project.
|
|
910
1433
|
* @param {string} id - The resource ID
|
|
911
1434
|
* @param {string} userId - The user ID
|
|
912
1435
|
*/
|
|
913
1436
|
removeWriter: (id, userId, auditMessage) =>
|
|
914
|
-
this._request(
|
|
1437
|
+
this._request("DELETE", `/api/v1/projects/${id}/writers/${userId}`, {
|
|
1438
|
+
auditMessage,
|
|
1439
|
+
}),
|
|
915
1440
|
/**
|
|
916
1441
|
* Set a user's access level to read-only for this project.
|
|
917
1442
|
* @param {string} id - The resource ID
|
|
918
1443
|
* @param {string} userId - The user ID
|
|
919
1444
|
*/
|
|
920
1445
|
addReader: (id, userId, auditMessage) =>
|
|
921
|
-
this._request(
|
|
1446
|
+
this._request("POST", `/api/v1/projects/${id}/readers/${userId}`, {
|
|
1447
|
+
auditMessage,
|
|
1448
|
+
}),
|
|
922
1449
|
/**
|
|
923
1450
|
* Remove a user's reader privileges for this project.
|
|
924
1451
|
* @param {string} id - The resource ID
|
|
925
1452
|
* @param {string} userId - The user ID
|
|
926
1453
|
*/
|
|
927
1454
|
removeReader: (id, userId, auditMessage) =>
|
|
928
|
-
this._request(
|
|
1455
|
+
this._request("DELETE", `/api/v1/projects/${id}/readers/${userId}`, {
|
|
1456
|
+
auditMessage,
|
|
1457
|
+
}),
|
|
929
1458
|
/**
|
|
930
1459
|
* Set a configuration value for a project in an editor namespace.
|
|
931
1460
|
* @param {string} id - The resource ID
|
|
@@ -934,9 +1463,11 @@ class PlaidClient {
|
|
|
934
1463
|
* @param {any} configValue - Configuration value to set
|
|
935
1464
|
*/
|
|
936
1465
|
setConfig: (id, namespace, configKey, configValue, auditMessage) =>
|
|
937
|
-
this._request(
|
|
938
|
-
|
|
939
|
-
|
|
1466
|
+
this._request(
|
|
1467
|
+
"PUT",
|
|
1468
|
+
`/api/v1/projects/${id}/config/${namespace}/${configKey}`,
|
|
1469
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
1470
|
+
),
|
|
940
1471
|
/**
|
|
941
1472
|
* Remove a configuration value for a project.
|
|
942
1473
|
* @param {string} id - The resource ID
|
|
@@ -944,23 +1475,31 @@ class PlaidClient {
|
|
|
944
1475
|
* @param {string} configKey - The config key
|
|
945
1476
|
*/
|
|
946
1477
|
deleteConfig: (id, namespace, configKey, auditMessage) =>
|
|
947
|
-
this._request(
|
|
948
|
-
|
|
949
|
-
|
|
1478
|
+
this._request(
|
|
1479
|
+
"DELETE",
|
|
1480
|
+
`/api/v1/projects/${id}/config/${namespace}/${configKey}`,
|
|
1481
|
+
{ auditMessage, skipResponseTransform: true },
|
|
1482
|
+
),
|
|
950
1483
|
/**
|
|
951
1484
|
* Assign a user as a maintainer for this project.
|
|
952
1485
|
* @param {string} id - The resource ID
|
|
953
1486
|
* @param {string} userId - The user ID
|
|
954
1487
|
*/
|
|
955
1488
|
addMaintainer: (id, userId, auditMessage) =>
|
|
956
|
-
this._request(
|
|
1489
|
+
this._request("POST", `/api/v1/projects/${id}/maintainers/${userId}`, {
|
|
1490
|
+
auditMessage,
|
|
1491
|
+
}),
|
|
957
1492
|
/**
|
|
958
1493
|
* Remove a user's maintainer privileges for this project.
|
|
959
1494
|
* @param {string} id - The resource ID
|
|
960
1495
|
* @param {string} userId - The user ID
|
|
961
1496
|
*/
|
|
962
1497
|
removeMaintainer: (id, userId, auditMessage) =>
|
|
963
|
-
this._request(
|
|
1498
|
+
this._request(
|
|
1499
|
+
"DELETE",
|
|
1500
|
+
`/api/v1/projects/${id}/maintainers/${userId}`,
|
|
1501
|
+
{ auditMessage },
|
|
1502
|
+
),
|
|
964
1503
|
/**
|
|
965
1504
|
* Get audit log for a project. Transparently follows pagination cursors
|
|
966
1505
|
* and returns the full flat array.
|
|
@@ -969,10 +1508,33 @@ class PlaidClient {
|
|
|
969
1508
|
* @param {string} [startTime] - Start of time range
|
|
970
1509
|
* @param {string} [endTime] - End of time range
|
|
971
1510
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1511
|
+
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
1512
|
+
* types, spelled as in an entry's `op/type` (e.g.
|
|
1513
|
+
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
1514
|
+
* one of its operations matches, carrying only the ones that did.
|
|
972
1515
|
*/
|
|
973
|
-
audit: (projectId, startTime, endTime, asOf) =>
|
|
1516
|
+
audit: (projectId, startTime, endTime, asOf, opTypes) =>
|
|
974
1517
|
listAll(this, `/api/v1/projects/${projectId}/audit`, {
|
|
975
|
-
query: {
|
|
1518
|
+
query: {
|
|
1519
|
+
"start-time": startTime,
|
|
1520
|
+
"end-time": endTime,
|
|
1521
|
+
"as-of": asOf,
|
|
1522
|
+
"op-types": opTypesParam(opTypes),
|
|
1523
|
+
},
|
|
1524
|
+
}),
|
|
1525
|
+
/**
|
|
1526
|
+
* When the calling user last wrote to each document in a project, as a
|
|
1527
|
+
* `{documentId: timestamp}` map. Documents they have never written to
|
|
1528
|
+
* are absent. One request covers a whole document list.
|
|
1529
|
+
*
|
|
1530
|
+
* The response is NOT key-transformed: its keys are document ids, and
|
|
1531
|
+
* camelCasing would mangle the hyphens in a UUID.
|
|
1532
|
+
* @param {string} projectId - The project ID
|
|
1533
|
+
* @returns {Promise<Object<string, string>>}
|
|
1534
|
+
*/
|
|
1535
|
+
myLastEdits: (projectId) =>
|
|
1536
|
+
this._request("GET", `/api/v1/projects/${projectId}/audit/last-edits`, {
|
|
1537
|
+
skipResponseTransform: true,
|
|
976
1538
|
}),
|
|
977
1539
|
/**
|
|
978
1540
|
* Link a vocabulary to a project.
|
|
@@ -980,14 +1542,18 @@ class PlaidClient {
|
|
|
980
1542
|
* @param {string} vocabId - The vocab layer ID
|
|
981
1543
|
*/
|
|
982
1544
|
linkVocab: (id, vocabId, auditMessage) =>
|
|
983
|
-
this._request(
|
|
1545
|
+
this._request("POST", `/api/v1/projects/${id}/vocabs/${vocabId}`, {
|
|
1546
|
+
auditMessage,
|
|
1547
|
+
}),
|
|
984
1548
|
/**
|
|
985
1549
|
* Unlink a vocabulary from a project.
|
|
986
1550
|
* @param {string} id - The resource ID
|
|
987
1551
|
* @param {string} vocabId - The vocab layer ID
|
|
988
1552
|
*/
|
|
989
1553
|
unlinkVocab: (id, vocabId, auditMessage) =>
|
|
990
|
-
this._request(
|
|
1554
|
+
this._request("DELETE", `/api/v1/projects/${id}/vocabs/${vocabId}`, {
|
|
1555
|
+
auditMessage,
|
|
1556
|
+
}),
|
|
991
1557
|
/**
|
|
992
1558
|
* Get a project by ID. To fetch the project's documents, use
|
|
993
1559
|
* listDocuments(id) — the include-documents flag has been removed.
|
|
@@ -995,8 +1561,8 @@ class PlaidClient {
|
|
|
995
1561
|
* @param {string} [asOf] - Temporal query timestamp
|
|
996
1562
|
*/
|
|
997
1563
|
get: (id, asOf) =>
|
|
998
|
-
this._request(
|
|
999
|
-
queryParams: {
|
|
1564
|
+
this._request("GET", `/api/v1/projects/${id}`, {
|
|
1565
|
+
queryParams: { "as-of": asOf },
|
|
1000
1566
|
}),
|
|
1001
1567
|
/**
|
|
1002
1568
|
* List all documents in a project. Transparently follows pagination
|
|
@@ -1007,8 +1573,7 @@ class PlaidClient {
|
|
|
1007
1573
|
* server rejects `?as-of=` on the documents-list route with a 400.
|
|
1008
1574
|
* @param {string} id - The project ID
|
|
1009
1575
|
*/
|
|
1010
|
-
listDocuments: (id) =>
|
|
1011
|
-
listAll(this, `/api/v1/projects/${id}/documents`),
|
|
1576
|
+
listDocuments: (id) => listAll(this, `/api/v1/projects/${id}/documents`),
|
|
1012
1577
|
/**
|
|
1013
1578
|
* Fetch a single page of a project's documents.
|
|
1014
1579
|
*
|
|
@@ -1048,14 +1613,18 @@ class PlaidClient {
|
|
|
1048
1613
|
* @param {number} [options.timeout=0] - Per-request timeout in ms (0/null disables)
|
|
1049
1614
|
*/
|
|
1050
1615
|
delete: (id, auditMessage, { timeout = 0 } = {}) =>
|
|
1051
|
-
this._request(
|
|
1616
|
+
this._request("DELETE", `/api/v1/projects/${id}`, {
|
|
1617
|
+
auditMessage,
|
|
1618
|
+
timeout,
|
|
1619
|
+
}),
|
|
1052
1620
|
/**
|
|
1053
1621
|
* Update a project's name.
|
|
1054
1622
|
* @param {string} id - The resource ID
|
|
1055
1623
|
* @param {string} name - The name
|
|
1056
1624
|
*/
|
|
1057
1625
|
update: (id, name, auditMessage) =>
|
|
1058
|
-
this._request(
|
|
1626
|
+
this._request("PATCH", `/api/v1/projects/${id}`, {
|
|
1627
|
+
auditMessage,
|
|
1059
1628
|
body: bodyOf({ name }),
|
|
1060
1629
|
}),
|
|
1061
1630
|
/**
|
|
@@ -1065,7 +1634,7 @@ class PlaidClient {
|
|
|
1065
1634
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1066
1635
|
*/
|
|
1067
1636
|
list: (asOf) =>
|
|
1068
|
-
listAll(this,
|
|
1637
|
+
listAll(this, "/api/v1/projects", { query: { "as-of": asOf } }),
|
|
1069
1638
|
/**
|
|
1070
1639
|
* Fetch a single page of projects.
|
|
1071
1640
|
* @param {object} [opts]
|
|
@@ -1075,7 +1644,11 @@ class PlaidClient {
|
|
|
1075
1644
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1076
1645
|
*/
|
|
1077
1646
|
listPage: ({ limit, cursor, asOf } = {}) =>
|
|
1078
|
-
listPage(this,
|
|
1647
|
+
listPage(this, "/api/v1/projects", {
|
|
1648
|
+
limit,
|
|
1649
|
+
cursor,
|
|
1650
|
+
query: { "as-of": asOf },
|
|
1651
|
+
}),
|
|
1079
1652
|
/**
|
|
1080
1653
|
* Async-iterate projects page by page; yields each page's entries array.
|
|
1081
1654
|
* @param {object} [opts]
|
|
@@ -1085,13 +1658,17 @@ class PlaidClient {
|
|
|
1085
1658
|
* @returns {AsyncGenerator<Array>}
|
|
1086
1659
|
*/
|
|
1087
1660
|
iterPages: ({ pageSize, asOf } = {}) =>
|
|
1088
|
-
iterPages(this,
|
|
1661
|
+
iterPages(this, "/api/v1/projects", {
|
|
1662
|
+
pageSize,
|
|
1663
|
+
query: { "as-of": asOf },
|
|
1664
|
+
}),
|
|
1089
1665
|
/**
|
|
1090
1666
|
* Create a new project. Note: this also registers the user as a maintainer.
|
|
1091
1667
|
* @param {string} name - The name
|
|
1092
1668
|
*/
|
|
1093
1669
|
create: (name, auditMessage) =>
|
|
1094
|
-
this._request(
|
|
1670
|
+
this._request("POST", "/api/v1/projects", {
|
|
1671
|
+
auditMessage,
|
|
1095
1672
|
body: bodyOf({ name }),
|
|
1096
1673
|
}),
|
|
1097
1674
|
};
|
|
@@ -1104,10 +1681,18 @@ class PlaidClient {
|
|
|
1104
1681
|
* @param {string} configKey - The config key
|
|
1105
1682
|
* @param {any} configValue - Configuration value to set
|
|
1106
1683
|
*/
|
|
1107
|
-
setConfig: (
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1684
|
+
setConfig: (
|
|
1685
|
+
textLayerId,
|
|
1686
|
+
namespace,
|
|
1687
|
+
configKey,
|
|
1688
|
+
configValue,
|
|
1689
|
+
auditMessage,
|
|
1690
|
+
) =>
|
|
1691
|
+
this._request(
|
|
1692
|
+
"PUT",
|
|
1693
|
+
`/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`,
|
|
1694
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
1695
|
+
),
|
|
1111
1696
|
/**
|
|
1112
1697
|
* Remove a configuration value for a layer.
|
|
1113
1698
|
* @param {string} textLayerId - The text layer ID
|
|
@@ -1115,31 +1700,36 @@ class PlaidClient {
|
|
|
1115
1700
|
* @param {string} configKey - The config key
|
|
1116
1701
|
*/
|
|
1117
1702
|
deleteConfig: (textLayerId, namespace, configKey, auditMessage) =>
|
|
1118
|
-
this._request(
|
|
1119
|
-
|
|
1120
|
-
|
|
1703
|
+
this._request(
|
|
1704
|
+
"DELETE",
|
|
1705
|
+
`/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`,
|
|
1706
|
+
{ auditMessage, skipResponseTransform: true },
|
|
1707
|
+
),
|
|
1121
1708
|
/**
|
|
1122
1709
|
* Get a text layer by ID.
|
|
1123
1710
|
* @param {string} textLayerId - The text layer ID
|
|
1124
1711
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1125
1712
|
*/
|
|
1126
1713
|
get: (textLayerId, asOf) =>
|
|
1127
|
-
this._request(
|
|
1128
|
-
queryParams: {
|
|
1714
|
+
this._request("GET", `/api/v1/text-layers/${textLayerId}`, {
|
|
1715
|
+
queryParams: { "as-of": asOf },
|
|
1129
1716
|
}),
|
|
1130
1717
|
/**
|
|
1131
1718
|
* Delete a text layer.
|
|
1132
1719
|
* @param {string} textLayerId - The text layer ID
|
|
1133
1720
|
*/
|
|
1134
1721
|
delete: (textLayerId, auditMessage) =>
|
|
1135
|
-
this._request(
|
|
1722
|
+
this._request("DELETE", `/api/v1/text-layers/${textLayerId}`, {
|
|
1723
|
+
auditMessage,
|
|
1724
|
+
}),
|
|
1136
1725
|
/**
|
|
1137
1726
|
* Update a text layer's name.
|
|
1138
1727
|
* @param {string} textLayerId - The text layer ID
|
|
1139
1728
|
* @param {string} name - The name
|
|
1140
1729
|
*/
|
|
1141
1730
|
update: (textLayerId, name, auditMessage) =>
|
|
1142
|
-
this._request(
|
|
1731
|
+
this._request("PATCH", `/api/v1/text-layers/${textLayerId}`, {
|
|
1732
|
+
auditMessage,
|
|
1143
1733
|
body: bodyOf({ name }),
|
|
1144
1734
|
}),
|
|
1145
1735
|
/**
|
|
@@ -1148,7 +1738,8 @@ class PlaidClient {
|
|
|
1148
1738
|
* @param {string} direction - The direction ("up" or "down")
|
|
1149
1739
|
*/
|
|
1150
1740
|
shift: (textLayerId, direction, auditMessage) =>
|
|
1151
|
-
this._request(
|
|
1741
|
+
this._request("POST", `/api/v1/text-layers/${textLayerId}/shift`, {
|
|
1742
|
+
auditMessage,
|
|
1152
1743
|
body: bodyOf({ direction }),
|
|
1153
1744
|
}),
|
|
1154
1745
|
/**
|
|
@@ -1157,8 +1748,9 @@ class PlaidClient {
|
|
|
1157
1748
|
* @param {string} name - The name
|
|
1158
1749
|
*/
|
|
1159
1750
|
create: (projectId, name, auditMessage) =>
|
|
1160
|
-
this._request(
|
|
1161
|
-
|
|
1751
|
+
this._request("POST", "/api/v1/text-layers", {
|
|
1752
|
+
auditMessage,
|
|
1753
|
+
body: bodyOf({ "project-id": projectId, name }),
|
|
1162
1754
|
}),
|
|
1163
1755
|
};
|
|
1164
1756
|
|
|
@@ -1169,15 +1761,18 @@ class PlaidClient {
|
|
|
1169
1761
|
* @param {any} body - The request body
|
|
1170
1762
|
*/
|
|
1171
1763
|
setMetadata: (id, body, auditMessage) =>
|
|
1172
|
-
this._request(
|
|
1173
|
-
|
|
1764
|
+
this._request("PUT", `/api/v1/vocab-items/${id}/metadata`, {
|
|
1765
|
+
auditMessage,
|
|
1766
|
+
rawBody: body,
|
|
1767
|
+
skipResponseTransform: true,
|
|
1174
1768
|
}),
|
|
1175
1769
|
/**
|
|
1176
1770
|
* Remove all metadata from a vocab item.
|
|
1177
1771
|
* @param {string} id - The resource ID
|
|
1178
1772
|
*/
|
|
1179
1773
|
deleteMetadata: (id, auditMessage) =>
|
|
1180
|
-
this._request(
|
|
1774
|
+
this._request("DELETE", `/api/v1/vocab-items/${id}/metadata`, {
|
|
1775
|
+
auditMessage,
|
|
1181
1776
|
skipResponseTransform: true,
|
|
1182
1777
|
}),
|
|
1183
1778
|
/**
|
|
@@ -1186,8 +1781,10 @@ class PlaidClient {
|
|
|
1186
1781
|
* @param {any} body - The metadata patch
|
|
1187
1782
|
*/
|
|
1188
1783
|
patchMetadata: (id, body, auditMessage) =>
|
|
1189
|
-
this._request(
|
|
1190
|
-
|
|
1784
|
+
this._request("PATCH", `/api/v1/vocab-items/${id}/metadata`, {
|
|
1785
|
+
auditMessage,
|
|
1786
|
+
rawBody: body,
|
|
1787
|
+
skipResponseTransform: true,
|
|
1191
1788
|
}),
|
|
1192
1789
|
/**
|
|
1193
1790
|
* Create a new vocab item
|
|
@@ -1196,8 +1793,9 @@ class PlaidClient {
|
|
|
1196
1793
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
1197
1794
|
*/
|
|
1198
1795
|
create: (vocabLayerId, form, metadata, auditMessage) =>
|
|
1199
|
-
this._request(
|
|
1200
|
-
|
|
1796
|
+
this._request("POST", "/api/v1/vocab-items", {
|
|
1797
|
+
auditMessage,
|
|
1798
|
+
body: bodyOf({ "vocab-layer-id": vocabLayerId, form, metadata }),
|
|
1201
1799
|
}),
|
|
1202
1800
|
/**
|
|
1203
1801
|
* Create multiple vocab items in a single operation. Entries may target
|
|
@@ -1205,36 +1803,43 @@ class PlaidClient {
|
|
|
1205
1803
|
* @param {Array<{vocabLayerId: string, form: string, metadata?: any}>} body - The vocab items to create
|
|
1206
1804
|
*/
|
|
1207
1805
|
bulkCreate: (body, auditMessage) =>
|
|
1208
|
-
this._request(
|
|
1806
|
+
this._request("POST", "/api/v1/vocab-items/bulk", {
|
|
1807
|
+
auditMessage,
|
|
1808
|
+
body,
|
|
1809
|
+
}),
|
|
1209
1810
|
/**
|
|
1210
1811
|
* Delete multiple vocab items in a single operation. Each item's
|
|
1211
1812
|
* descendant vocab links are deleted too. Provide an array of IDs.
|
|
1212
1813
|
* @param {string[]} body - The vocab item IDs to delete
|
|
1213
1814
|
*/
|
|
1214
1815
|
bulkDelete: (body, auditMessage) =>
|
|
1215
|
-
this._request(
|
|
1816
|
+
this._request("DELETE", "/api/v1/vocab-items/bulk", {
|
|
1817
|
+
auditMessage,
|
|
1818
|
+
body,
|
|
1819
|
+
}),
|
|
1216
1820
|
/**
|
|
1217
1821
|
* Get a vocab item by ID
|
|
1218
1822
|
* @param {string} id - The resource ID
|
|
1219
1823
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1220
1824
|
*/
|
|
1221
1825
|
get: (id, asOf) =>
|
|
1222
|
-
this._request(
|
|
1223
|
-
queryParams: {
|
|
1826
|
+
this._request("GET", `/api/v1/vocab-items/${id}`, {
|
|
1827
|
+
queryParams: { "as-of": asOf },
|
|
1224
1828
|
}),
|
|
1225
1829
|
/**
|
|
1226
1830
|
* Delete a vocab item
|
|
1227
1831
|
* @param {string} id - The resource ID
|
|
1228
1832
|
*/
|
|
1229
1833
|
delete: (id, auditMessage) =>
|
|
1230
|
-
this._request(
|
|
1834
|
+
this._request("DELETE", `/api/v1/vocab-items/${id}`, { auditMessage }),
|
|
1231
1835
|
/**
|
|
1232
1836
|
* Update a vocab item's form
|
|
1233
1837
|
* @param {string} id - The resource ID
|
|
1234
1838
|
* @param {string} form - The vocab item form
|
|
1235
1839
|
*/
|
|
1236
1840
|
update: (id, form, auditMessage) =>
|
|
1237
|
-
this._request(
|
|
1841
|
+
this._request("PATCH", `/api/v1/vocab-items/${id}`, {
|
|
1842
|
+
auditMessage,
|
|
1238
1843
|
body: bodyOf({ form }),
|
|
1239
1844
|
}),
|
|
1240
1845
|
};
|
|
@@ -1246,17 +1851,20 @@ class PlaidClient {
|
|
|
1246
1851
|
* @param {string} direction - The direction ("up" or "down")
|
|
1247
1852
|
*/
|
|
1248
1853
|
shift: (relationLayerId, direction, auditMessage) =>
|
|
1249
|
-
this._request(
|
|
1250
|
-
|
|
1251
|
-
|
|
1854
|
+
this._request(
|
|
1855
|
+
"POST",
|
|
1856
|
+
`/api/v1/relation-layers/${relationLayerId}/shift`,
|
|
1857
|
+
{ auditMessage, body: bodyOf({ direction }) },
|
|
1858
|
+
),
|
|
1252
1859
|
/**
|
|
1253
1860
|
* Create a new relation layer.
|
|
1254
1861
|
* @param {string} spanLayerId - The span layer ID
|
|
1255
1862
|
* @param {string} name - The name
|
|
1256
1863
|
*/
|
|
1257
1864
|
create: (spanLayerId, name, auditMessage) =>
|
|
1258
|
-
this._request(
|
|
1259
|
-
|
|
1865
|
+
this._request("POST", "/api/v1/relation-layers", {
|
|
1866
|
+
auditMessage,
|
|
1867
|
+
body: bodyOf({ "span-layer-id": spanLayerId, name }),
|
|
1260
1868
|
}),
|
|
1261
1869
|
/**
|
|
1262
1870
|
* Set a configuration value for a layer in an editor namespace.
|
|
@@ -1265,10 +1873,18 @@ class PlaidClient {
|
|
|
1265
1873
|
* @param {string} configKey - The config key
|
|
1266
1874
|
* @param {any} configValue - Configuration value to set
|
|
1267
1875
|
*/
|
|
1268
|
-
setConfig: (
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1876
|
+
setConfig: (
|
|
1877
|
+
relationLayerId,
|
|
1878
|
+
namespace,
|
|
1879
|
+
configKey,
|
|
1880
|
+
configValue,
|
|
1881
|
+
auditMessage,
|
|
1882
|
+
) =>
|
|
1883
|
+
this._request(
|
|
1884
|
+
"PUT",
|
|
1885
|
+
`/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`,
|
|
1886
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
1887
|
+
),
|
|
1272
1888
|
/**
|
|
1273
1889
|
* Remove a configuration value for a layer.
|
|
1274
1890
|
* @param {string} relationLayerId - The relation layer ID
|
|
@@ -1276,31 +1892,36 @@ class PlaidClient {
|
|
|
1276
1892
|
* @param {string} configKey - The config key
|
|
1277
1893
|
*/
|
|
1278
1894
|
deleteConfig: (relationLayerId, namespace, configKey, auditMessage) =>
|
|
1279
|
-
this._request(
|
|
1280
|
-
|
|
1281
|
-
|
|
1895
|
+
this._request(
|
|
1896
|
+
"DELETE",
|
|
1897
|
+
`/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`,
|
|
1898
|
+
{ auditMessage, skipResponseTransform: true },
|
|
1899
|
+
),
|
|
1282
1900
|
/**
|
|
1283
1901
|
* Get a relation layer by ID.
|
|
1284
1902
|
* @param {string} relationLayerId - The relation layer ID
|
|
1285
1903
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1286
1904
|
*/
|
|
1287
1905
|
get: (relationLayerId, asOf) =>
|
|
1288
|
-
this._request(
|
|
1289
|
-
queryParams: {
|
|
1906
|
+
this._request("GET", `/api/v1/relation-layers/${relationLayerId}`, {
|
|
1907
|
+
queryParams: { "as-of": asOf },
|
|
1290
1908
|
}),
|
|
1291
1909
|
/**
|
|
1292
1910
|
* Delete a relation layer.
|
|
1293
1911
|
* @param {string} relationLayerId - The relation layer ID
|
|
1294
1912
|
*/
|
|
1295
1913
|
delete: (relationLayerId, auditMessage) =>
|
|
1296
|
-
this._request(
|
|
1914
|
+
this._request("DELETE", `/api/v1/relation-layers/${relationLayerId}`, {
|
|
1915
|
+
auditMessage,
|
|
1916
|
+
}),
|
|
1297
1917
|
/**
|
|
1298
1918
|
* Update a relation layer's name.
|
|
1299
1919
|
* @param {string} relationLayerId - The relation layer ID
|
|
1300
1920
|
* @param {string} name - The name
|
|
1301
1921
|
*/
|
|
1302
1922
|
update: (relationLayerId, name, auditMessage) =>
|
|
1303
|
-
this._request(
|
|
1923
|
+
this._request("PATCH", `/api/v1/relation-layers/${relationLayerId}`, {
|
|
1924
|
+
auditMessage,
|
|
1304
1925
|
body: bodyOf({ name }),
|
|
1305
1926
|
}),
|
|
1306
1927
|
};
|
|
@@ -1324,9 +1945,25 @@ class PlaidClient {
|
|
|
1324
1945
|
* @param {number} [precedence] - Ordering precedence
|
|
1325
1946
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
1326
1947
|
*/
|
|
1327
|
-
create: (
|
|
1328
|
-
|
|
1329
|
-
|
|
1948
|
+
create: (
|
|
1949
|
+
tokenLayerId,
|
|
1950
|
+
text,
|
|
1951
|
+
begin,
|
|
1952
|
+
end,
|
|
1953
|
+
precedence,
|
|
1954
|
+
metadata,
|
|
1955
|
+
auditMessage,
|
|
1956
|
+
) =>
|
|
1957
|
+
this._request("POST", "/api/v1/tokens", {
|
|
1958
|
+
auditMessage,
|
|
1959
|
+
body: bodyOf({
|
|
1960
|
+
"token-layer-id": tokenLayerId,
|
|
1961
|
+
text,
|
|
1962
|
+
begin,
|
|
1963
|
+
end,
|
|
1964
|
+
precedence,
|
|
1965
|
+
metadata,
|
|
1966
|
+
}),
|
|
1330
1967
|
}),
|
|
1331
1968
|
/**
|
|
1332
1969
|
* Get a token.
|
|
@@ -1334,8 +1971,8 @@ class PlaidClient {
|
|
|
1334
1971
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1335
1972
|
*/
|
|
1336
1973
|
get: (tokenId, asOf) =>
|
|
1337
|
-
this._request(
|
|
1338
|
-
queryParams: {
|
|
1974
|
+
this._request("GET", `/api/v1/tokens/${tokenId}`, {
|
|
1975
|
+
queryParams: { "as-of": asOf },
|
|
1339
1976
|
}),
|
|
1340
1977
|
/**
|
|
1341
1978
|
* Delete a token and remove it from any spans. If this causes a span to
|
|
@@ -1343,7 +1980,7 @@ class PlaidClient {
|
|
|
1343
1980
|
* @param {string} tokenId - The token ID
|
|
1344
1981
|
*/
|
|
1345
1982
|
delete: (tokenId, auditMessage) =>
|
|
1346
|
-
this._request(
|
|
1983
|
+
this._request("DELETE", `/api/v1/tokens/${tokenId}`, { auditMessage }),
|
|
1347
1984
|
/**
|
|
1348
1985
|
* Update a token.
|
|
1349
1986
|
* @param {string} tokenId - The token ID
|
|
@@ -1355,7 +1992,8 @@ class PlaidClient {
|
|
|
1355
1992
|
* drops undefined, so the three cases map correctly to the server.
|
|
1356
1993
|
*/
|
|
1357
1994
|
update: (tokenId, begin, end, precedence, auditMessage) =>
|
|
1358
|
-
this._request(
|
|
1995
|
+
this._request("PATCH", `/api/v1/tokens/${tokenId}`, {
|
|
1996
|
+
auditMessage,
|
|
1359
1997
|
body: bodyOf({ begin, end, precedence }),
|
|
1360
1998
|
}),
|
|
1361
1999
|
/**
|
|
@@ -1364,13 +2002,13 @@ class PlaidClient {
|
|
|
1364
2002
|
* @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
|
|
1365
2003
|
*/
|
|
1366
2004
|
bulkCreate: (body, auditMessage) =>
|
|
1367
|
-
this._request(
|
|
2005
|
+
this._request("POST", "/api/v1/tokens/bulk", { auditMessage, body }),
|
|
1368
2006
|
/**
|
|
1369
2007
|
* Delete multiple tokens in a single operation. Provide an array of IDs.
|
|
1370
2008
|
* @param {Array} body - The request body
|
|
1371
2009
|
*/
|
|
1372
2010
|
bulkDelete: (body, auditMessage) =>
|
|
1373
|
-
this._request(
|
|
2011
|
+
this._request("DELETE", "/api/v1/tokens/bulk", { auditMessage, body }),
|
|
1374
2012
|
/**
|
|
1375
2013
|
* Split a token at a Unicode code-point offset. The original token becomes the
|
|
1376
2014
|
* left half (keeps its ID, spans, vocab-links); the new right token's ID is returned.
|
|
@@ -1378,7 +2016,8 @@ class PlaidClient {
|
|
|
1378
2016
|
* @param {number} position - Code-point offset to split at (strictly between begin and end)
|
|
1379
2017
|
*/
|
|
1380
2018
|
split: (tokenId, position, auditMessage) =>
|
|
1381
|
-
this._request(
|
|
2019
|
+
this._request("POST", `/api/v1/tokens/${tokenId}/split`, {
|
|
2020
|
+
auditMessage,
|
|
1382
2021
|
body: bodyOf({ position }),
|
|
1383
2022
|
}),
|
|
1384
2023
|
/**
|
|
@@ -1390,8 +2029,9 @@ class PlaidClient {
|
|
|
1390
2029
|
* @param {string} otherTokenId - The other token to merge in
|
|
1391
2030
|
*/
|
|
1392
2031
|
merge: (tokenId, otherTokenId, auditMessage) =>
|
|
1393
|
-
this._request(
|
|
1394
|
-
|
|
2032
|
+
this._request("POST", `/api/v1/tokens/${tokenId}/merge`, {
|
|
2033
|
+
auditMessage,
|
|
2034
|
+
body: bodyOf({ "other-token-id": otherTokenId }),
|
|
1395
2035
|
}),
|
|
1396
2036
|
/**
|
|
1397
2037
|
* Shift a token's boundary. On partitioning layers the adjacent token is
|
|
@@ -1402,7 +2042,8 @@ class PlaidClient {
|
|
|
1402
2042
|
* @param {number} [end] - New end offset, exclusive (Unicode code points)
|
|
1403
2043
|
*/
|
|
1404
2044
|
shift: (tokenId, begin, end, auditMessage) =>
|
|
1405
|
-
this._request(
|
|
2045
|
+
this._request("POST", `/api/v1/tokens/${tokenId}/shift`, {
|
|
2046
|
+
auditMessage,
|
|
1406
2047
|
body: bodyOf({ begin, end }),
|
|
1407
2048
|
}),
|
|
1408
2049
|
/**
|
|
@@ -1411,15 +2052,18 @@ class PlaidClient {
|
|
|
1411
2052
|
* @param {any} body - The request body
|
|
1412
2053
|
*/
|
|
1413
2054
|
setMetadata: (tokenId, body, auditMessage) =>
|
|
1414
|
-
this._request(
|
|
1415
|
-
|
|
2055
|
+
this._request("PUT", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2056
|
+
auditMessage,
|
|
2057
|
+
rawBody: body,
|
|
2058
|
+
skipResponseTransform: true,
|
|
1416
2059
|
}),
|
|
1417
2060
|
/**
|
|
1418
2061
|
* Remove all metadata from a token.
|
|
1419
2062
|
* @param {string} tokenId - The token ID
|
|
1420
2063
|
*/
|
|
1421
2064
|
deleteMetadata: (tokenId, auditMessage) =>
|
|
1422
|
-
this._request(
|
|
2065
|
+
this._request("DELETE", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2066
|
+
auditMessage,
|
|
1423
2067
|
skipResponseTransform: true,
|
|
1424
2068
|
}),
|
|
1425
2069
|
/**
|
|
@@ -1428,8 +2072,10 @@ class PlaidClient {
|
|
|
1428
2072
|
* @param {any} body - The metadata patch
|
|
1429
2073
|
*/
|
|
1430
2074
|
patchMetadata: (tokenId, body, auditMessage) =>
|
|
1431
|
-
this._request(
|
|
1432
|
-
|
|
2075
|
+
this._request("PATCH", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2076
|
+
auditMessage,
|
|
2077
|
+
rawBody: body,
|
|
2078
|
+
skipResponseTransform: true,
|
|
1433
2079
|
}),
|
|
1434
2080
|
};
|
|
1435
2081
|
|
|
@@ -1445,14 +2091,22 @@ class PlaidClient {
|
|
|
1445
2091
|
createSSEConnection(this, projectId, onEvent, path),
|
|
1446
2092
|
|
|
1447
2093
|
/**
|
|
1448
|
-
* Send a message to project listeners
|
|
2094
|
+
* Send a message to project listeners.
|
|
2095
|
+
*
|
|
2096
|
+
* `data` may be any JSON value and is sent VERBATIM (`rawBody`): a
|
|
2097
|
+
* message payload is opaque application data, like `metadata` and
|
|
2098
|
+
* `config`, so its keys must not be re-cased on the way out. Without
|
|
2099
|
+
* this a key such as `case-marker` would reach listeners as
|
|
2100
|
+
* `caseMarker` in JS and `case_marker` in Python. `listen` restores it
|
|
2101
|
+
* verbatim on the way in.
|
|
1449
2102
|
* @param {string} projectId - The UUID of the project to send to
|
|
1450
2103
|
* @param {any} data - The message data to send
|
|
1451
2104
|
* @returns {Promise<any>} Response from the send operation
|
|
1452
2105
|
*/
|
|
1453
2106
|
sendMessage: (projectId, data, auditMessage) =>
|
|
1454
|
-
this._request(
|
|
1455
|
-
|
|
2107
|
+
this._request("POST", `/api/v1/projects/${projectId}/message`, {
|
|
2108
|
+
auditMessage,
|
|
2109
|
+
rawBody: { body: data },
|
|
1456
2110
|
}),
|
|
1457
2111
|
|
|
1458
2112
|
/**
|
|
@@ -1462,8 +2116,7 @@ class PlaidClient {
|
|
|
1462
2116
|
* @param {string} projectId - The UUID of the project to query
|
|
1463
2117
|
* @returns {Promise<Array>} Array of discovered service information
|
|
1464
2118
|
*/
|
|
1465
|
-
discoverServices: (projectId) =>
|
|
1466
|
-
discoverServices(this, projectId),
|
|
2119
|
+
discoverServices: (projectId) => discoverServices(this, projectId),
|
|
1467
2120
|
|
|
1468
2121
|
/**
|
|
1469
2122
|
* Forget a previously-seen (offline) service. Maintainer-only; 409 if
|
|
@@ -1477,26 +2130,70 @@ class PlaidClient {
|
|
|
1477
2130
|
|
|
1478
2131
|
/**
|
|
1479
2132
|
* Register as a service and handle incoming work requests.
|
|
2133
|
+
*
|
|
2134
|
+
* The registration reopens its channel whenever it drops, so a server
|
|
2135
|
+
* restart needs no service restart.
|
|
1480
2136
|
* @param {string} projectId - The UUID of the project to serve
|
|
1481
2137
|
* @param {Object} serviceInfo - Service information {serviceId, serviceName, description}
|
|
1482
2138
|
* @param {function} onServiceRequest - Callback (data, responseHelper)
|
|
1483
2139
|
* @param {Object} [extras] - Optional additional service metadata
|
|
1484
|
-
* @
|
|
2140
|
+
* @param {function} [onStatus] - Optional callback (event, projectId, detail) for
|
|
2141
|
+
* connection transitions: 'registered', 'reconnected', 'disconnected'
|
|
2142
|
+
* @returns {Object} Service registration with .stop(), .isRunning(), .isConnected()
|
|
1485
2143
|
*/
|
|
1486
|
-
serve: (projectId, serviceInfo, onServiceRequest, extras) =>
|
|
1487
|
-
serve(this, projectId, serviceInfo, onServiceRequest, extras),
|
|
2144
|
+
serve: (projectId, serviceInfo, onServiceRequest, extras, onStatus) =>
|
|
2145
|
+
serve(this, projectId, serviceInfo, onServiceRequest, extras, onStatus),
|
|
1488
2146
|
|
|
1489
2147
|
/**
|
|
1490
2148
|
* Request a service to perform work and await its result.
|
|
2149
|
+
*
|
|
2150
|
+
* The request outlives this call: after a timeout, an abort, or a
|
|
2151
|
+
* dropped connection the service goes on, and `attachServiceRequest`
|
|
2152
|
+
* collects the result given the request id, which `opts.onAccepted`
|
|
2153
|
+
* receives as soon as the server has taken the request. Pass
|
|
2154
|
+
* `opts.requestId` (a UUID you mint) to know the id before submitting;
|
|
2155
|
+
* submitting an id that names a request you already made rejoins it
|
|
2156
|
+
* instead of starting another.
|
|
1491
2157
|
* @param {string} projectId - The UUID of the project
|
|
1492
2158
|
* @param {string} serviceId - The ID of the service to request
|
|
1493
2159
|
* @param {any} data - The request data
|
|
1494
|
-
* @param {number} [timeout] - Timeout in
|
|
2160
|
+
* @param {number} [timeout=10000] - Timeout in ms
|
|
1495
2161
|
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
1496
|
-
* @
|
|
2162
|
+
* @param {AbortSignal} [signal] - Abort to stop waiting; rejects with an AbortError
|
|
2163
|
+
* @param {Object} [opts] - {requestId, onAccepted}
|
|
2164
|
+
* @returns {Promise<any>} The service's result
|
|
1497
2165
|
*/
|
|
1498
|
-
requestService: (projectId, serviceId, data, timeout, onProgress) =>
|
|
1499
|
-
requestService(this, projectId, serviceId, data, timeout, onProgress),
|
|
2166
|
+
requestService: (projectId, serviceId, data, timeout, onProgress, signal, opts) =>
|
|
2167
|
+
requestService(this, projectId, serviceId, data, timeout, onProgress, signal, opts),
|
|
2168
|
+
|
|
2169
|
+
/**
|
|
2170
|
+
* Rejoin a service request made earlier and await its result: the
|
|
2171
|
+
* latest progress is replayed, then the result comes, or at once if the
|
|
2172
|
+
* request already finished. Only the user who submitted it (or an
|
|
2173
|
+
* admin). Rejects with an error whose `status` is 404 when the request
|
|
2174
|
+
* is unknown or expired (a finished request's result is kept for a
|
|
2175
|
+
* while, not forever).
|
|
2176
|
+
* @param {string} projectId - The UUID of the project
|
|
2177
|
+
* @param {string} requestId - The request id
|
|
2178
|
+
* @param {number} [timeout=10000] - Timeout in ms
|
|
2179
|
+
* @param {function} [onProgress] - Called with each progress payload
|
|
2180
|
+
* @param {AbortSignal} [signal] - Abort to stop waiting
|
|
2181
|
+
* @returns {Promise<any>} The service's result
|
|
2182
|
+
*/
|
|
2183
|
+
attachServiceRequest: (projectId, requestId, timeout, onProgress, signal) =>
|
|
2184
|
+
attachServiceRequest(this, projectId, requestId, timeout, onProgress, signal),
|
|
2185
|
+
|
|
2186
|
+
/**
|
|
2187
|
+
* Ask the service to stop a request made earlier. The request still
|
|
2188
|
+
* ends with whatever the service then reports, on the stream of whoever
|
|
2189
|
+
* is awaiting it. Rejects with 404 if unknown or expired, 409 once
|
|
2190
|
+
* finished.
|
|
2191
|
+
* @param {string} projectId - The UUID of the project
|
|
2192
|
+
* @param {string} requestId - The request id
|
|
2193
|
+
* @returns {Promise<void>}
|
|
2194
|
+
*/
|
|
2195
|
+
cancelServiceRequest: (projectId, requestId) =>
|
|
2196
|
+
cancelServiceRequest(this, projectId, requestId),
|
|
1500
2197
|
};
|
|
1501
2198
|
|
|
1502
2199
|
/**
|
|
@@ -1534,8 +2231,201 @@ class PlaidClient {
|
|
|
1534
2231
|
* For 'count': {return: 'count', count}. Entity cells are full entity objects
|
|
1535
2232
|
* (same shape as the GET endpoints).
|
|
1536
2233
|
*/
|
|
2234
|
+
// bypassBatch: a query is a read. `client.batched()` flips one flag on the
|
|
2235
|
+
// whole client, so a query issued by unrelated code while a batch is open
|
|
2236
|
+
// (the IGT island fetching precedent while a document reconciles) used to
|
|
2237
|
+
// be queued into it: the caller got `{batched: true}` instead of results,
|
|
2238
|
+
// and the server ran the query against the batch's tx Connection, which
|
|
2239
|
+
// 500s and rolls back every write in that batch.
|
|
1537
2240
|
this.query = (body, auditMessage) =>
|
|
1538
|
-
this._request(
|
|
2241
|
+
this._request("POST", "/api/v1/query", { auditMessage, body, bypassBatch: true });
|
|
2242
|
+
|
|
2243
|
+
// Logical-operation groups (audit-log grouping). There is no create: a
|
|
2244
|
+
// group row is made lazily by the first write carrying `?group-id=`
|
|
2245
|
+
// (see beginOperation). Not an audited write, so no auditMessage arg.
|
|
2246
|
+
this.operationGroups = {
|
|
2247
|
+
/**
|
|
2248
|
+
* Get a logical-operation group (its label + creator).
|
|
2249
|
+
* @param {string} id - The group id
|
|
2250
|
+
*/
|
|
2251
|
+
get: (id) => this._request("GET", `/api/v1/operation-groups/${id}`),
|
|
2252
|
+
/**
|
|
2253
|
+
* Relabel a logical-operation group after the fact. Owner or admin only.
|
|
2254
|
+
* @param {string} id - The group id
|
|
2255
|
+
* @param {string|null} message - The new label
|
|
2256
|
+
*/
|
|
2257
|
+
update: (id, message) =>
|
|
2258
|
+
this._request("PATCH", `/api/v1/operation-groups/${id}`, {
|
|
2259
|
+
body: { message },
|
|
2260
|
+
}),
|
|
2261
|
+
};
|
|
2262
|
+
|
|
2263
|
+
this.comments = {
|
|
2264
|
+
/**
|
|
2265
|
+
* Post a comment on an entity. Requires write access to the entity's
|
|
2266
|
+
* project, or to the vocabulary for a vocab-item; the author is the
|
|
2267
|
+
* authenticated caller. Comments are not audited and do not bump the
|
|
2268
|
+
* document version.
|
|
2269
|
+
*
|
|
2270
|
+
* A comment outlives its anchor: deleting the entity does not delete
|
|
2271
|
+
* the comment. `anchorLabel` is the caption shown once that happens
|
|
2272
|
+
* ("Gloss of ktab, sentence 4"), so pass what the comment is about.
|
|
2273
|
+
* @param {'document'|'text'|'token'|'span'|'relation'|'vocab-item'} entityType - What kind of thing is being commented on
|
|
2274
|
+
* @param {string} entityId - The commented entity's id
|
|
2275
|
+
* @param {string} body - The comment text (1..10000 characters)
|
|
2276
|
+
* @param {object} [opts]
|
|
2277
|
+
* @param {string} [opts.anchorLabel] - What the comment is about, in words (at most 200 characters)
|
|
2278
|
+
*/
|
|
2279
|
+
create: (entityType, entityId, body, { anchorLabel } = {}) =>
|
|
2280
|
+
this._request("POST", "/api/v1/comments", {
|
|
2281
|
+
body: bodyOf({
|
|
2282
|
+
"entity-type": entityType,
|
|
2283
|
+
"entity-id": entityId,
|
|
2284
|
+
body,
|
|
2285
|
+
"anchor-label": anchorLabel,
|
|
2286
|
+
}),
|
|
2287
|
+
}),
|
|
2288
|
+
/**
|
|
2289
|
+
* Read one comment.
|
|
2290
|
+
* @param {string} id - The comment id
|
|
2291
|
+
*/
|
|
2292
|
+
get: (id) => this._request("GET", `/api/v1/comments/${id}`),
|
|
2293
|
+
/**
|
|
2294
|
+
* Edit a comment's body. Only the comment's AUTHOR may do this - not
|
|
2295
|
+
* maintainers, not admins. Sets `edited` on the comment.
|
|
2296
|
+
* @param {string} id - The comment id
|
|
2297
|
+
* @param {string} body - The replacement text
|
|
2298
|
+
*/
|
|
2299
|
+
update: (id, body) =>
|
|
2300
|
+
this._request("PATCH", `/api/v1/comments/${id}`, { body: { body } }),
|
|
2301
|
+
/**
|
|
2302
|
+
* Delete a comment. The author may delete their own; a project
|
|
2303
|
+
* maintainer (or admin) may delete any.
|
|
2304
|
+
* @param {string} id - The comment id
|
|
2305
|
+
*/
|
|
2306
|
+
delete: (id) => this._request("DELETE", `/api/v1/comments/${id}`),
|
|
2307
|
+
/**
|
|
2308
|
+
* List comments in a project, oldest first. Transparently follows
|
|
2309
|
+
* pagination cursors and returns the full flat array.
|
|
2310
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching - use listPage() for a single page in a batch.
|
|
2311
|
+
* @param {string} projectId - The project to read
|
|
2312
|
+
* @param {object} [filters] - Narrow the scope
|
|
2313
|
+
* @param {string} [filters.documentId] - Every comment anywhere in one document
|
|
2314
|
+
* @param {string} [filters.entityType] - With entityId, one entity's thread
|
|
2315
|
+
* @param {string} [filters.entityId] - With entityType, one entity's thread
|
|
2316
|
+
*/
|
|
2317
|
+
list: (projectId, { documentId, entityType, entityId } = {}) =>
|
|
2318
|
+
listAll(this, `/api/v1/projects/${projectId}/comments`, {
|
|
2319
|
+
query: {
|
|
2320
|
+
"document-id": documentId,
|
|
2321
|
+
"entity-type": entityType,
|
|
2322
|
+
"entity-id": entityId,
|
|
2323
|
+
},
|
|
2324
|
+
}),
|
|
2325
|
+
/**
|
|
2326
|
+
* Fetch a single page of a project's comments.
|
|
2327
|
+
* @param {string} projectId - The project to read
|
|
2328
|
+
* @param {object} [opts]
|
|
2329
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
2330
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
2331
|
+
* @param {string} [opts.documentId] - Every comment anywhere in one document
|
|
2332
|
+
* @param {string} [opts.entityType] - With entityId, one entity's thread
|
|
2333
|
+
* @param {string} [opts.entityId] - With entityType, one entity's thread
|
|
2334
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2335
|
+
*/
|
|
2336
|
+
listPage: (projectId, { limit, cursor, documentId, entityType, entityId } = {}) =>
|
|
2337
|
+
listPage(this, `/api/v1/projects/${projectId}/comments`, {
|
|
2338
|
+
limit,
|
|
2339
|
+
cursor,
|
|
2340
|
+
query: {
|
|
2341
|
+
"document-id": documentId,
|
|
2342
|
+
"entity-type": entityType,
|
|
2343
|
+
"entity-id": entityId,
|
|
2344
|
+
},
|
|
2345
|
+
}),
|
|
2346
|
+
/**
|
|
2347
|
+
* Async-iterate a project's comments page by page; yields each page's entries array.
|
|
2348
|
+
* 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.
|
|
2349
|
+
* @param {string} projectId - The project to read
|
|
2350
|
+
* @param {object} [opts]
|
|
2351
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
2352
|
+
* @param {string} [opts.documentId] - Every comment anywhere in one document
|
|
2353
|
+
* @param {string} [opts.entityType] - With entityId, one entity's thread
|
|
2354
|
+
* @param {string} [opts.entityId] - With entityType, one entity's thread
|
|
2355
|
+
* @returns {AsyncGenerator<Array>}
|
|
2356
|
+
*/
|
|
2357
|
+
iterPages: (projectId, { pageSize, documentId, entityType, entityId } = {}) =>
|
|
2358
|
+
iterPages(this, `/api/v1/projects/${projectId}/comments`, {
|
|
2359
|
+
pageSize,
|
|
2360
|
+
query: {
|
|
2361
|
+
"document-id": documentId,
|
|
2362
|
+
"entity-type": entityType,
|
|
2363
|
+
"entity-id": entityId,
|
|
2364
|
+
},
|
|
2365
|
+
}),
|
|
2366
|
+
/**
|
|
2367
|
+
* Comment counts per entity, as an `{entityId: n}` map, over the same
|
|
2368
|
+
* scope and filters as `list`. One cheap request paints a comment
|
|
2369
|
+
* indicator on every annotated item in a document without paging
|
|
2370
|
+
* through the bodies.
|
|
2371
|
+
*
|
|
2372
|
+
* The response is NOT key-transformed: its keys are entity ids, and
|
|
2373
|
+
* camelCasing would mangle the hyphens in a UUID.
|
|
2374
|
+
* @param {string} projectId - The project to read
|
|
2375
|
+
* @param {object} [filters] - Same filters as list()
|
|
2376
|
+
* @returns {Promise<Object<string, number>>}
|
|
2377
|
+
*/
|
|
2378
|
+
counts: (projectId, { documentId, entityType, entityId } = {}) =>
|
|
2379
|
+
this._request("GET", `/api/v1/projects/${projectId}/comments/counts`, {
|
|
2380
|
+
queryParams: {
|
|
2381
|
+
"document-id": documentId,
|
|
2382
|
+
"entity-type": entityType,
|
|
2383
|
+
"entity-id": entityId,
|
|
2384
|
+
},
|
|
2385
|
+
skipResponseTransform: true,
|
|
2386
|
+
}),
|
|
2387
|
+
/**
|
|
2388
|
+
* List the comments on a vocabulary's entries, oldest first. Requires
|
|
2389
|
+
* read access to the vocabulary. Transparently follows pagination
|
|
2390
|
+
* cursors and returns the full flat array.
|
|
2391
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching - use listInVocabPage() for a single page in a batch.
|
|
2392
|
+
* @param {string} vocabId - The vocab layer to read
|
|
2393
|
+
* @param {object} [filters]
|
|
2394
|
+
* @param {string} [filters.entityId] - One entry's thread
|
|
2395
|
+
*/
|
|
2396
|
+
listInVocab: (vocabId, { entityId } = {}) =>
|
|
2397
|
+
listAll(this, `/api/v1/vocab-layers/${vocabId}/comments`, {
|
|
2398
|
+
query: { "entity-id": entityId },
|
|
2399
|
+
}),
|
|
2400
|
+
/**
|
|
2401
|
+
* Fetch a single page of a vocabulary's comments.
|
|
2402
|
+
* @param {string} vocabId - The vocab layer to read
|
|
2403
|
+
* @param {object} [opts]
|
|
2404
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
2405
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
2406
|
+
* @param {string} [opts.entityId] - One entry's thread
|
|
2407
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2408
|
+
*/
|
|
2409
|
+
listInVocabPage: (vocabId, { limit, cursor, entityId } = {}) =>
|
|
2410
|
+
listPage(this, `/api/v1/vocab-layers/${vocabId}/comments`, {
|
|
2411
|
+
limit,
|
|
2412
|
+
cursor,
|
|
2413
|
+
query: { "entity-id": entityId },
|
|
2414
|
+
}),
|
|
2415
|
+
/**
|
|
2416
|
+
* Comment counts per entry of a vocabulary, as an `{entityId: n}` map.
|
|
2417
|
+
* The response is NOT key-transformed: its keys are entry ids.
|
|
2418
|
+
* @param {string} vocabId - The vocab layer to read
|
|
2419
|
+
* @param {object} [filters]
|
|
2420
|
+
* @param {string} [filters.entityId] - One entry only
|
|
2421
|
+
* @returns {Promise<Object<string, number>>}
|
|
2422
|
+
*/
|
|
2423
|
+
countsInVocab: (vocabId, { entityId } = {}) =>
|
|
2424
|
+
this._request("GET", `/api/v1/vocab-layers/${vocabId}/comments/counts`, {
|
|
2425
|
+
queryParams: { "entity-id": entityId },
|
|
2426
|
+
skipResponseTransform: true,
|
|
2427
|
+
}),
|
|
2428
|
+
};
|
|
1539
2429
|
}
|
|
1540
2430
|
|
|
1541
2431
|
// --- Core methods ---
|
|
@@ -1559,51 +2449,96 @@ class PlaidClient {
|
|
|
1559
2449
|
}
|
|
1560
2450
|
|
|
1561
2451
|
/**
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
2452
|
+
* Begin a LOGICAL OPERATION: a user-meaningful action ("Merge morphemes",
|
|
2453
|
+
* "Re-transcribe") implemented as many low-level writes, possibly across
|
|
2454
|
+
* several batches and even a service round-trip. Until `endOperation()`,
|
|
2455
|
+
* every write is stamped with a client-minted `?group-id=` (and the message)
|
|
2456
|
+
* so the audit log shows the whole run as ONE expandable entry labeled
|
|
2457
|
+
* `message`, with each write's own description underneath.
|
|
2458
|
+
*
|
|
2459
|
+
* Grouping is orthogonal to batches: a batch is a transaction boundary, an
|
|
2460
|
+
* operation is an intent boundary. An operation is NOT atomic — if write 3
|
|
2461
|
+
* of 5 fails, writes 1–2 stay committed (and logged under the group). Use a
|
|
2462
|
+
* batch inside the operation for any step that must be all-or-nothing.
|
|
2463
|
+
*
|
|
2464
|
+
* Nesting flattens: a `beginOperation` while one is open is a no-op that
|
|
2465
|
+
* joins the outer operation (the outer label wins), and the matching
|
|
2466
|
+
* `endOperation` is likewise a no-op. The label is recorded on the FIRST
|
|
2467
|
+
* write, so an operation that is never ended (crash, closed tab) is still
|
|
2468
|
+
* labeled in the log.
|
|
2469
|
+
*
|
|
2470
|
+
* @param {string} message - Human label for the operation.
|
|
2471
|
+
* @param {object} [opts] - Optional `{ id }`: adopt an existing group id instead of minting one (a service joining the requester's operation; `requestService` propagates an open operation to the service automatically).
|
|
2472
|
+
* @returns {string} The operation's group id.
|
|
1571
2473
|
*/
|
|
1572
|
-
|
|
1573
|
-
this.
|
|
1574
|
-
|
|
2474
|
+
beginOperation(message, { id } = {}) {
|
|
2475
|
+
if (this.operationGroup) {
|
|
2476
|
+
this.operationGroup.depth += 1;
|
|
2477
|
+
return this.operationGroup.id;
|
|
2478
|
+
}
|
|
2479
|
+
this.operationGroup = {
|
|
2480
|
+
id: id || crypto.randomUUID(),
|
|
2481
|
+
message: message == null ? null : String(message),
|
|
2482
|
+
depth: 1,
|
|
2483
|
+
written: false,
|
|
2484
|
+
refined: undefined,
|
|
2485
|
+
};
|
|
2486
|
+
return this.operationGroup.id;
|
|
1575
2487
|
}
|
|
1576
2488
|
|
|
1577
|
-
/**
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
2489
|
+
/**
|
|
2490
|
+
* End the current logical operation. With no argument this is purely local
|
|
2491
|
+
* (no request). Pass a refined `message` to relabel the group now that the
|
|
2492
|
+
* outcome is known (e.g. `endOperation('Merged 3 morphemes')`) — that sends
|
|
2493
|
+
* one PATCH, skipped if the operation never wrote anything. A refine from a
|
|
2494
|
+
* nested (flattened) `endOperation` is ignored; the outer label wins.
|
|
2495
|
+
* @param {string} [message] - Optional refined label.
|
|
2496
|
+
* @returns {Promise<void>}
|
|
2497
|
+
*/
|
|
2498
|
+
async endOperation(message) {
|
|
2499
|
+
const group = this.operationGroup;
|
|
2500
|
+
if (!group) return;
|
|
2501
|
+
if (group.depth > 1) {
|
|
2502
|
+
group.depth -= 1;
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
this.operationGroup = null;
|
|
2506
|
+
const refined = message !== undefined ? message : group.refined;
|
|
2507
|
+
if (refined !== undefined && group.written) {
|
|
2508
|
+
try {
|
|
2509
|
+
await this.operationGroups.update(group.id, refined);
|
|
2510
|
+
} catch (e) {
|
|
2511
|
+
// 404: the group never materialized server-side (every tagged write
|
|
2512
|
+
// failed or a batch was aborted). Nothing to relabel — not an error.
|
|
2513
|
+
if (e.status !== 404) throw e;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
1581
2516
|
}
|
|
1582
2517
|
|
|
1583
2518
|
/**
|
|
1584
|
-
* Run `fn`
|
|
1585
|
-
*
|
|
1586
|
-
*
|
|
2519
|
+
* Run `fn` as one logical operation (see beginOperation), ending it when
|
|
2520
|
+
* `fn` settles — including on throw. `fn` receives a `setMessage(msg)`
|
|
2521
|
+
* callback to refine the label once the outcome is known.
|
|
1587
2522
|
*
|
|
1588
|
-
* await client.
|
|
1589
|
-
* client.
|
|
1590
|
-
*
|
|
1591
|
-
* await client.withAuditMessage('Import sentence', async () => {
|
|
1592
|
-
* await client.tokens.create(...);
|
|
1593
|
-
* await client.spans.create(...);
|
|
2523
|
+
* await client.withOperation('Merge morphemes', async (setMessage) => {
|
|
2524
|
+
* await client.batched(() => { ... });
|
|
2525
|
+
* setMessage(`Merged ${n} morphemes`);
|
|
1594
2526
|
* });
|
|
1595
2527
|
*
|
|
1596
|
-
* @param {string} message -
|
|
1597
|
-
* @param {
|
|
2528
|
+
* @param {string} message - Human label for the operation.
|
|
2529
|
+
* @param {function} fn - The work to run; receives `setMessage(msg)` to refine the label once the outcome is known.
|
|
1598
2530
|
* @returns {Promise<any>} Whatever `fn` resolves to.
|
|
1599
2531
|
*/
|
|
1600
|
-
async
|
|
1601
|
-
|
|
1602
|
-
|
|
2532
|
+
async withOperation(message, fn) {
|
|
2533
|
+
this.beginOperation(message);
|
|
2534
|
+
const group = this.operationGroup;
|
|
2535
|
+
const setMessage = (msg) => {
|
|
2536
|
+
if (group.depth === 1) group.refined = msg;
|
|
2537
|
+
};
|
|
1603
2538
|
try {
|
|
1604
|
-
return await fn();
|
|
2539
|
+
return await fn(setMessage);
|
|
1605
2540
|
} finally {
|
|
1606
|
-
this.
|
|
2541
|
+
await this.endOperation();
|
|
1607
2542
|
}
|
|
1608
2543
|
}
|
|
1609
2544
|
|
|
@@ -1623,7 +2558,7 @@ class PlaidClient {
|
|
|
1623
2558
|
*/
|
|
1624
2559
|
async submitBatch() {
|
|
1625
2560
|
if (!this.isBatching) {
|
|
1626
|
-
throw new Error(
|
|
2561
|
+
throw new Error("No active batch. Call beginBatch() first.");
|
|
1627
2562
|
}
|
|
1628
2563
|
|
|
1629
2564
|
if (this.batchOperations.length === 0) {
|
|
@@ -1632,55 +2567,84 @@ class PlaidClient {
|
|
|
1632
2567
|
}
|
|
1633
2568
|
|
|
1634
2569
|
try {
|
|
1635
|
-
|
|
1636
|
-
const
|
|
2570
|
+
const url = `${this.baseUrl}/api/v1/batch`;
|
|
2571
|
+
const ops = this.batchOperations.map((op) => ({
|
|
1637
2572
|
path: op.path,
|
|
1638
2573
|
method: op.method.toUpperCase(),
|
|
1639
2574
|
...(op.body && { body: op.body }),
|
|
1640
2575
|
}));
|
|
2576
|
+
// The server caps a batch at MAX_BATCH_OPS so one transaction cannot
|
|
2577
|
+
// hold the write lock without bound. A larger batch goes as consecutive
|
|
2578
|
+
// requests, results concatenated in queue order: it could not have been
|
|
2579
|
+
// one transaction anyway, and a repair or bulk edit over a big document
|
|
2580
|
+
// must not fail on its size alone.
|
|
2581
|
+
const results = [];
|
|
2582
|
+
for (let i = 0; i < ops.length; i += MAX_BATCH_OPS) {
|
|
2583
|
+
results.push(...(await this._postBatch(url, ops.slice(i, i + MAX_BATCH_OPS))));
|
|
2584
|
+
}
|
|
2585
|
+
return results;
|
|
2586
|
+
} finally {
|
|
2587
|
+
this.isBatching = false;
|
|
2588
|
+
this.batchOperations = [];
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
1641
2591
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
|
|
2592
|
+
/** POST one batch request (at most MAX_BATCH_OPS operations); resolves to its transformed results. */
|
|
2593
|
+
async _postBatch(url, body) {
|
|
2594
|
+
const fetchOptions = {
|
|
2595
|
+
method: "POST",
|
|
2596
|
+
headers: {
|
|
2597
|
+
Authorization: `Bearer ${this.token}`,
|
|
2598
|
+
"Content-Type": "application/json",
|
|
2599
|
+
},
|
|
2600
|
+
body: JSON.stringify(body),
|
|
2601
|
+
};
|
|
1652
2602
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
2603
|
+
try {
|
|
2604
|
+
// Retry a 503: the batch is atomic, so a refused one wrote nothing and
|
|
2605
|
+
// repeating it is safe. A fresh timeout signal per attempt — an
|
|
2606
|
+
// AbortSignal.timeout stays aborted once it fires.
|
|
2607
|
+
const response = await retryWhileBusy(async () => {
|
|
2608
|
+
const signal = timeoutSignal(this.batchTimeout);
|
|
2609
|
+
const res = await fetch(
|
|
2610
|
+
url,
|
|
2611
|
+
signal ? { ...fetchOptions, signal } : fetchOptions,
|
|
2612
|
+
);
|
|
2613
|
+
if (!res.ok) {
|
|
2614
|
+
throw makeHttpError(res, await parseErrorBody(res), url, "POST");
|
|
1657
2615
|
}
|
|
2616
|
+
return res;
|
|
2617
|
+
});
|
|
2618
|
+
|
|
2619
|
+
const results = await response.json();
|
|
1658
2620
|
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
2621
|
+
// Extract document versions from each batch response
|
|
2622
|
+
for (const result of results) {
|
|
2623
|
+
if (result.headers && result.headers["X-Document-Versions"]) {
|
|
2624
|
+
try {
|
|
2625
|
+
const versionsMap = JSON.parse(
|
|
2626
|
+
result.headers["X-Document-Versions"],
|
|
2627
|
+
);
|
|
2628
|
+
if (typeof versionsMap === "object" && versionsMap !== null) {
|
|
2629
|
+
// Clone once per response, then merge — not once per entry.
|
|
2630
|
+
this.documentVersions = {
|
|
2631
|
+
...this.documentVersions,
|
|
2632
|
+
...versionsMap,
|
|
2633
|
+
};
|
|
1672
2634
|
}
|
|
2635
|
+
} catch (e) {
|
|
2636
|
+
console.warn(
|
|
2637
|
+
"Failed to parse document versions header from batch response:",
|
|
2638
|
+
e,
|
|
2639
|
+
);
|
|
1673
2640
|
}
|
|
1674
2641
|
}
|
|
1675
|
-
|
|
1676
|
-
return results.map(result => transformResponse(result));
|
|
1677
|
-
} catch (error) {
|
|
1678
|
-
if (error.status) throw error;
|
|
1679
|
-
throw makeNetworkError(error, url, 'POST');
|
|
1680
2642
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
2643
|
+
|
|
2644
|
+
return results.map((result) => transformResponse(result));
|
|
2645
|
+
} catch (error) {
|
|
2646
|
+
if (error.status) throw error;
|
|
2647
|
+
throw makeNetworkError(error, url, "POST");
|
|
1684
2648
|
}
|
|
1685
2649
|
}
|
|
1686
2650
|
|
|
@@ -1739,29 +2703,114 @@ class PlaidClient {
|
|
|
1739
2703
|
* @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
|
|
1740
2704
|
* @returns {Promise<PlaidClient>} - Authenticated client instance
|
|
1741
2705
|
*/
|
|
2706
|
+
/**
|
|
2707
|
+
* Build the link to hand someone for an invite code. The server never sees
|
|
2708
|
+
* an app URL, so the app that minted the invite is the one that names it.
|
|
2709
|
+
* Both SPAs use HashRouter, so the code rides in the fragment — which also
|
|
2710
|
+
* keeps it out of server access logs.
|
|
2711
|
+
* @param {string} appUrl - Where the SPA lives, e.g. "https://plaid.example.org/igt/"
|
|
2712
|
+
* @param {string} code - The code returned by invites.create()
|
|
2713
|
+
* @returns {string}
|
|
2714
|
+
*/
|
|
2715
|
+
static inviteUrl(appUrl, code) {
|
|
2716
|
+
return `${appUrl.replace(/#.*$/, "").replace(/\/$/, "")}/#/invite/${encodeURIComponent(code)}`;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
/**
|
|
2720
|
+
* Describe an invite code, with NO authentication — this is what a signup
|
|
2721
|
+
* page calls before the redeemer has an account. Returns the kind of link
|
|
2722
|
+
* ("signup" or "password-reset"), its status ("active", "used", "expired",
|
|
2723
|
+
* "revoked"), and the project it grants access to, if any.
|
|
2724
|
+
*
|
|
2725
|
+
* Throws a 404-shaped error if the code is unknown. A known-but-dead code
|
|
2726
|
+
* resolves normally with a non-"active" status, so the page can say why.
|
|
2727
|
+
* @param {string} baseUrl - The API base URL
|
|
2728
|
+
* @param {string} code - The invite code
|
|
2729
|
+
* @param {object} [options]
|
|
2730
|
+
* @returns {Promise<{kind: string, status: string, expiresAt: string, projectName?: string, projectRole?: string, email?: string, grantAdmin: boolean}>}
|
|
2731
|
+
*/
|
|
2732
|
+
static async lookupInvite(baseUrl, code, options = {}) {
|
|
2733
|
+
return anonymousPost(
|
|
2734
|
+
baseUrl,
|
|
2735
|
+
"/api/v1/invite-codes/lookup",
|
|
2736
|
+
{ code },
|
|
2737
|
+
options,
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
/**
|
|
2742
|
+
* Redeem an invite code, with NO authentication.
|
|
2743
|
+
*
|
|
2744
|
+
* For a signup invite, pass `email` and `password` to create the account
|
|
2745
|
+
* (and optionally `displayName`); the invite's grants are applied in the
|
|
2746
|
+
* same transaction. For a password reset link, pass `password` only.
|
|
2747
|
+
* Resolves to a logged-in client, exactly like login() — the redeemer just
|
|
2748
|
+
* chose these credentials, so there is no reason to send them to a login
|
|
2749
|
+
* form to retype them.
|
|
2750
|
+
* @param {string} baseUrl - The API base URL
|
|
2751
|
+
* @param {string} code - The invite code
|
|
2752
|
+
* @param {object} credentials
|
|
2753
|
+
* @param {string} [credentials.email] - The new account's email address,
|
|
2754
|
+
* which becomes its id and login (signup only)
|
|
2755
|
+
* @param {string} [credentials.displayName] - How the new user is shown in
|
|
2756
|
+
* the UI; defaults to the local part of the email (signup only)
|
|
2757
|
+
* @param {string} credentials.password - Desired password (min 8 characters)
|
|
2758
|
+
* @param {object} [options]
|
|
2759
|
+
* @returns {Promise<{client: PlaidClient, userId: string, kind: string}>}
|
|
2760
|
+
*/
|
|
2761
|
+
static async redeemInvite(
|
|
2762
|
+
baseUrl,
|
|
2763
|
+
code,
|
|
2764
|
+
{ email, password, displayName } = {},
|
|
2765
|
+
options = {},
|
|
2766
|
+
) {
|
|
2767
|
+
const data = await anonymousPost(
|
|
2768
|
+
baseUrl,
|
|
2769
|
+
"/api/v1/invite-codes/redeem",
|
|
2770
|
+
bodyOf({ code, email, password, "display-name": displayName }),
|
|
2771
|
+
options,
|
|
2772
|
+
);
|
|
2773
|
+
return {
|
|
2774
|
+
client: new PlaidClient(
|
|
2775
|
+
baseUrl.replace(/\/$/, ""),
|
|
2776
|
+
data.token || "",
|
|
2777
|
+
options,
|
|
2778
|
+
),
|
|
2779
|
+
userId: data.userId,
|
|
2780
|
+
kind: data.kind,
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
|
|
1742
2784
|
static async login(baseUrl, userId, password, options = {}) {
|
|
1743
|
-
baseUrl = baseUrl.replace(/\/$/,
|
|
2785
|
+
baseUrl = baseUrl.replace(/\/$/, "");
|
|
1744
2786
|
const url = `${baseUrl}/api/v1/login`;
|
|
1745
2787
|
try {
|
|
1746
2788
|
const fetchOptions = {
|
|
1747
|
-
method:
|
|
1748
|
-
headers: {
|
|
1749
|
-
body: JSON.stringify({
|
|
2789
|
+
method: "POST",
|
|
2790
|
+
headers: { "Content-Type": "application/json" },
|
|
2791
|
+
body: JSON.stringify({ "user-id": userId, password }),
|
|
1750
2792
|
};
|
|
1751
|
-
const signal = timeoutSignal(
|
|
2793
|
+
const signal = timeoutSignal(
|
|
2794
|
+
options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS,
|
|
2795
|
+
);
|
|
1752
2796
|
if (signal) fetchOptions.signal = signal;
|
|
1753
2797
|
|
|
1754
2798
|
const response = await fetch(url, fetchOptions);
|
|
1755
2799
|
if (!response.ok) {
|
|
1756
|
-
throw makeHttpError(
|
|
2800
|
+
throw makeHttpError(
|
|
2801
|
+
response,
|
|
2802
|
+
await parseErrorBody(response),
|
|
2803
|
+
url,
|
|
2804
|
+
"POST",
|
|
2805
|
+
);
|
|
1757
2806
|
}
|
|
1758
2807
|
|
|
1759
2808
|
const data = await response.json();
|
|
1760
|
-
const token = data.token ||
|
|
2809
|
+
const token = data.token || "";
|
|
1761
2810
|
return new PlaidClient(baseUrl, token, options);
|
|
1762
2811
|
} catch (error) {
|
|
1763
2812
|
if (error.status) throw error;
|
|
1764
|
-
throw makeNetworkError(error, url,
|
|
2813
|
+
throw makeNetworkError(error, url, "POST");
|
|
1765
2814
|
}
|
|
1766
2815
|
}
|
|
1767
2816
|
}
|
|
@@ -1771,8 +2820,21 @@ export { PlaidClient };
|
|
|
1771
2820
|
|
|
1772
2821
|
// Unicode code-point helpers for text offsets (token begin/end are code-point
|
|
1773
2822
|
// indices). See ./codepoint.js.
|
|
1774
|
-
export {
|
|
1775
|
-
|
|
2823
|
+
export {
|
|
2824
|
+
cpLength,
|
|
2825
|
+
cpSlice,
|
|
2826
|
+
cpSlicer,
|
|
2827
|
+
utf16ToCp,
|
|
2828
|
+
cpToUtf16,
|
|
2829
|
+
cpIndexOf,
|
|
2830
|
+
} from "./codepoint.js";
|
|
2831
|
+
export {
|
|
2832
|
+
PLAID_NAMESPACE,
|
|
2833
|
+
ROLE_KEY,
|
|
2834
|
+
ROLES,
|
|
2835
|
+
readRole,
|
|
2836
|
+
findByRole,
|
|
2837
|
+
} from "./roles.js";
|
|
1776
2838
|
// Service self-description helpers: filter discovered services by task, read a
|
|
1777
2839
|
// service's parameter schema/summary, and build/coerce form values. See
|
|
1778
2840
|
// ./serviceSchema.js.
|
|
@@ -1784,18 +2846,34 @@ export {
|
|
|
1784
2846
|
getServiceSummary,
|
|
1785
2847
|
buildDefaultValues,
|
|
1786
2848
|
coerceParamValues,
|
|
1787
|
-
} from
|
|
1788
|
-
// Provenance: the cross-app convention for
|
|
1789
|
-
//
|
|
1790
|
-
//
|
|
1791
|
-
// "Provenance".
|
|
2849
|
+
} from "./serviceSchema.js";
|
|
2850
|
+
// Provenance: the cross-app convention for who made an annotation (flat
|
|
2851
|
+
// prov/provSource/provConfirmed metadata; absence = a verifier; 'inferred' =
|
|
2852
|
+
// a machine; 'contributed' = a person whose work is reviewed), plus the
|
|
2853
|
+
// machine-writer contract. See ./provenance.js and the manual, "Provenance".
|
|
1792
2854
|
export {
|
|
1793
2855
|
PROV,
|
|
1794
2856
|
PROV_STATES,
|
|
2857
|
+
PROV_CONFIRMED,
|
|
1795
2858
|
stampInferred,
|
|
1796
2859
|
confirmedInferred,
|
|
2860
|
+
stampContributed,
|
|
1797
2861
|
provState,
|
|
2862
|
+
provOrigin,
|
|
2863
|
+
isMachine,
|
|
1798
2864
|
isProtected,
|
|
2865
|
+
needsReview,
|
|
1799
2866
|
verifyOnEdit,
|
|
2867
|
+
contributeOnEdit,
|
|
2868
|
+
mergeMetadata,
|
|
1800
2869
|
serviceSource,
|
|
1801
|
-
|
|
2870
|
+
userSource,
|
|
2871
|
+
REVIEW_KEY,
|
|
2872
|
+
PROJECT_ROLES,
|
|
2873
|
+
readReview,
|
|
2874
|
+
projectRole,
|
|
2875
|
+
isReviewed,
|
|
2876
|
+
withReviewedUser,
|
|
2877
|
+
writerPolicy,
|
|
2878
|
+
} from "./provenance.js";
|
|
2879
|
+
export { MAX_BATCH_OPS };
|