@larc-iu/plaid-client 0.2.0-alpha.1 → 0.2.0-alpha.12
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 +800 -107
- package/package.json +1 -1
- package/src/http.js +254 -35
- package/src/index.js +1604 -368
- package/src/provenance.js +278 -27
- package/src/roles.js +9 -0
- package/src/serviceSchema.js +61 -31
- package/src/services.js +321 -44
- 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) {
|
|
@@ -29,28 +56,66 @@ function bodyOf(obj) {
|
|
|
29
56
|
// and redemption). Deliberately does not go through PlaidClient._request: these
|
|
30
57
|
// are called before any client exists, which is the whole point.
|
|
31
58
|
async function anonymousPost(baseUrl, path, body, options = {}) {
|
|
32
|
-
const base = baseUrl.replace(/\/$/,
|
|
59
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
33
60
|
const url = `${base}${path}`;
|
|
34
61
|
try {
|
|
35
62
|
const fetchOptions = {
|
|
36
|
-
method:
|
|
37
|
-
headers: {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
38
65
|
body: JSON.stringify(body),
|
|
39
66
|
};
|
|
40
|
-
const signal = timeoutSignal(
|
|
67
|
+
const signal = timeoutSignal(
|
|
68
|
+
options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS,
|
|
69
|
+
);
|
|
41
70
|
if (signal) fetchOptions.signal = signal;
|
|
42
71
|
|
|
43
72
|
const response = await fetch(url, fetchOptions);
|
|
44
73
|
if (!response.ok) {
|
|
45
|
-
throw makeHttpError(
|
|
74
|
+
throw makeHttpError(
|
|
75
|
+
response,
|
|
76
|
+
await parseErrorBody(response),
|
|
77
|
+
url,
|
|
78
|
+
"POST",
|
|
79
|
+
);
|
|
46
80
|
}
|
|
47
81
|
return transformResponse(await response.json());
|
|
48
82
|
} catch (error) {
|
|
49
83
|
if (error.status) throw error;
|
|
50
|
-
throw makeNetworkError(error, url,
|
|
84
|
+
throw makeNetworkError(error, url, "POST");
|
|
51
85
|
}
|
|
52
86
|
}
|
|
53
87
|
|
|
88
|
+
/**
|
|
89
|
+
* GET a path with no authentication, for the two endpoints a client may need
|
|
90
|
+
* before it has a session: /health and /api/v1/info.
|
|
91
|
+
*/
|
|
92
|
+
async function anonymousGet(baseUrl, path, options = {}) {
|
|
93
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
94
|
+
const url = `${base}${path}`;
|
|
95
|
+
try {
|
|
96
|
+
const fetchOptions = { method: "GET" };
|
|
97
|
+
const signal = timeoutSignal(
|
|
98
|
+
options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS,
|
|
99
|
+
);
|
|
100
|
+
if (signal) fetchOptions.signal = signal;
|
|
101
|
+
|
|
102
|
+
const response = await fetch(url, fetchOptions);
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw makeHttpError(response, await parseErrorBody(response), url, "GET");
|
|
105
|
+
}
|
|
106
|
+
return transformResponse(await response.json());
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.status) throw error;
|
|
109
|
+
throw makeNetworkError(error, url, "GET");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The server's cap on operations per batch request (plaid.rest-api.v1.batch).
|
|
115
|
+
* A batch queued past it is submitted as consecutive requests.
|
|
116
|
+
*/
|
|
117
|
+
const MAX_BATCH_OPS = 1000;
|
|
118
|
+
|
|
54
119
|
class PlaidClient {
|
|
55
120
|
/**
|
|
56
121
|
* Create a new PlaidClient instance
|
|
@@ -58,11 +123,24 @@ class PlaidClient {
|
|
|
58
123
|
* @param {string} token - The authentication token
|
|
59
124
|
* @param {object} [options] - Client options
|
|
60
125
|
* @param {number} [options.timeout=30000] - Per-request timeout in ms (0 or null disables it)
|
|
126
|
+
* @param {number} [options.batchTimeout=180000] - Timeout for batch submissions in ms (0 or null disables it)
|
|
61
127
|
*/
|
|
62
128
|
constructor(baseUrl, token, options = {}) {
|
|
63
|
-
this.baseUrl = baseUrl.replace(/\/$/,
|
|
129
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
64
130
|
this.token = token;
|
|
65
|
-
this.timeout =
|
|
131
|
+
this.timeout =
|
|
132
|
+
options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS;
|
|
133
|
+
// Batch submissions get a longer budget than a single request. Aborting a
|
|
134
|
+
// batch does NOT stop the server: it keeps running the transaction and
|
|
135
|
+
// holding the single SQLite write lock, so a client that gives up early
|
|
136
|
+
// just adds a retry on top of a write that is still in progress. Falls
|
|
137
|
+
// back to an explicit `timeout` when one was given.
|
|
138
|
+
this.batchTimeout =
|
|
139
|
+
options.batchTimeout !== undefined
|
|
140
|
+
? options.batchTimeout
|
|
141
|
+
: options.timeout !== undefined
|
|
142
|
+
? options.timeout
|
|
143
|
+
: DEFAULT_BATCH_TIMEOUT_MS;
|
|
66
144
|
this.isBatching = false;
|
|
67
145
|
this.batchOperations = [];
|
|
68
146
|
this.documentVersions = {};
|
|
@@ -88,8 +166,9 @@ class PlaidClient {
|
|
|
88
166
|
* @param {any} [metadata] - Metadata for the link. Omit to leave unset; pass null to send JSON null.
|
|
89
167
|
*/
|
|
90
168
|
create: (vocabItem, tokens, metadata, auditMessage) =>
|
|
91
|
-
this._request(
|
|
92
|
-
|
|
169
|
+
this._request("POST", "/api/v1/vocab-links", {
|
|
170
|
+
auditMessage,
|
|
171
|
+
body: bodyOf({ "vocab-item": vocabItem, tokens, metadata }),
|
|
93
172
|
}),
|
|
94
173
|
/**
|
|
95
174
|
* Create multiple vocab links in a single operation. Entries may
|
|
@@ -99,28 +178,37 @@ class PlaidClient {
|
|
|
99
178
|
* @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
|
|
100
179
|
*/
|
|
101
180
|
bulkCreate: (body, auditMessage) =>
|
|
102
|
-
this._request(
|
|
181
|
+
this._request("POST", "/api/v1/vocab-links/bulk", {
|
|
182
|
+
auditMessage,
|
|
183
|
+
body,
|
|
184
|
+
}),
|
|
103
185
|
/**
|
|
104
186
|
* Delete multiple vocab links in a single operation. Provide an array of IDs.
|
|
105
187
|
* @param {string[]} body - The vocab link IDs to delete
|
|
106
188
|
*/
|
|
107
189
|
bulkDelete: (body, auditMessage) =>
|
|
108
|
-
this._request(
|
|
190
|
+
this._request("DELETE", "/api/v1/vocab-links/bulk", {
|
|
191
|
+
auditMessage,
|
|
192
|
+
body,
|
|
193
|
+
}),
|
|
109
194
|
/**
|
|
110
195
|
* Replace all metadata for a vocab link. The entire metadata map is replaced - existing metadata keys not included in the request will be removed.
|
|
111
196
|
* @param {string} id - The resource ID
|
|
112
197
|
* @param {any} body - The request body
|
|
113
198
|
*/
|
|
114
199
|
setMetadata: (id, body, auditMessage) =>
|
|
115
|
-
this._request(
|
|
116
|
-
|
|
200
|
+
this._request("PUT", `/api/v1/vocab-links/${id}/metadata`, {
|
|
201
|
+
auditMessage,
|
|
202
|
+
rawBody: body,
|
|
203
|
+
skipResponseTransform: true,
|
|
117
204
|
}),
|
|
118
205
|
/**
|
|
119
206
|
* Remove all metadata from a vocab link.
|
|
120
207
|
* @param {string} id - The resource ID
|
|
121
208
|
*/
|
|
122
209
|
deleteMetadata: (id, auditMessage) =>
|
|
123
|
-
this._request(
|
|
210
|
+
this._request("DELETE", `/api/v1/vocab-links/${id}/metadata`, {
|
|
211
|
+
auditMessage,
|
|
124
212
|
skipResponseTransform: true,
|
|
125
213
|
}),
|
|
126
214
|
/**
|
|
@@ -129,8 +217,10 @@ class PlaidClient {
|
|
|
129
217
|
* @param {any} body - The metadata patch
|
|
130
218
|
*/
|
|
131
219
|
patchMetadata: (id, body, auditMessage) =>
|
|
132
|
-
this._request(
|
|
133
|
-
|
|
220
|
+
this._request("PATCH", `/api/v1/vocab-links/${id}/metadata`, {
|
|
221
|
+
auditMessage,
|
|
222
|
+
rawBody: body,
|
|
223
|
+
skipResponseTransform: true,
|
|
134
224
|
}),
|
|
135
225
|
/**
|
|
136
226
|
* Get a vocab link by ID
|
|
@@ -138,15 +228,15 @@ class PlaidClient {
|
|
|
138
228
|
* @param {string} [asOf] - Temporal query timestamp
|
|
139
229
|
*/
|
|
140
230
|
get: (id, asOf) =>
|
|
141
|
-
this._request(
|
|
142
|
-
queryParams: {
|
|
231
|
+
this._request("GET", `/api/v1/vocab-links/${id}`, {
|
|
232
|
+
queryParams: { "as-of": asOf },
|
|
143
233
|
}),
|
|
144
234
|
/**
|
|
145
235
|
* Delete a vocab link
|
|
146
236
|
* @param {string} id - The resource ID
|
|
147
237
|
*/
|
|
148
238
|
delete: (id, auditMessage) =>
|
|
149
|
-
this._request(
|
|
239
|
+
this._request("DELETE", `/api/v1/vocab-links/${id}`, { auditMessage }),
|
|
150
240
|
};
|
|
151
241
|
|
|
152
242
|
this.vocabLayers = {
|
|
@@ -157,22 +247,23 @@ class PlaidClient {
|
|
|
157
247
|
* @param {string} [asOf] - Temporal query timestamp
|
|
158
248
|
*/
|
|
159
249
|
get: (id, includeItems, asOf) =>
|
|
160
|
-
this._request(
|
|
161
|
-
queryParams: {
|
|
250
|
+
this._request("GET", `/api/v1/vocab-layers/${id}`, {
|
|
251
|
+
queryParams: { "include-items": includeItems, "as-of": asOf },
|
|
162
252
|
}),
|
|
163
253
|
/**
|
|
164
254
|
* Delete a vocab layer.
|
|
165
255
|
* @param {string} id - The resource ID
|
|
166
256
|
*/
|
|
167
257
|
delete: (id, auditMessage) =>
|
|
168
|
-
this._request(
|
|
258
|
+
this._request("DELETE", `/api/v1/vocab-layers/${id}`, { auditMessage }),
|
|
169
259
|
/**
|
|
170
260
|
* Update a vocab layer's name.
|
|
171
261
|
* @param {string} id - The resource ID
|
|
172
262
|
* @param {string} name - The name
|
|
173
263
|
*/
|
|
174
264
|
update: (id, name, auditMessage) =>
|
|
175
|
-
this._request(
|
|
265
|
+
this._request("PATCH", `/api/v1/vocab-layers/${id}`, {
|
|
266
|
+
auditMessage,
|
|
176
267
|
body: bodyOf({ name }),
|
|
177
268
|
}),
|
|
178
269
|
/**
|
|
@@ -183,9 +274,11 @@ class PlaidClient {
|
|
|
183
274
|
* @param {any} configValue - Configuration value to set
|
|
184
275
|
*/
|
|
185
276
|
setConfig: (id, namespace, configKey, configValue, auditMessage) =>
|
|
186
|
-
this._request(
|
|
187
|
-
|
|
188
|
-
|
|
277
|
+
this._request(
|
|
278
|
+
"PUT",
|
|
279
|
+
`/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`,
|
|
280
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
281
|
+
),
|
|
189
282
|
/**
|
|
190
283
|
* Remove a configuration value for a layer.
|
|
191
284
|
* @param {string} id - The resource ID
|
|
@@ -193,9 +286,11 @@ class PlaidClient {
|
|
|
193
286
|
* @param {string} configKey - The config key
|
|
194
287
|
*/
|
|
195
288
|
deleteConfig: (id, namespace, configKey, auditMessage) =>
|
|
196
|
-
this._request(
|
|
197
|
-
|
|
198
|
-
|
|
289
|
+
this._request(
|
|
290
|
+
"DELETE",
|
|
291
|
+
`/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`,
|
|
292
|
+
{ auditMessage, skipResponseTransform: true },
|
|
293
|
+
),
|
|
199
294
|
/**
|
|
200
295
|
* List all vocab layers accessible to user. Transparently follows
|
|
201
296
|
* pagination cursors and returns the full flat array.
|
|
@@ -203,7 +298,7 @@ class PlaidClient {
|
|
|
203
298
|
* @param {string} [asOf] - Temporal query timestamp
|
|
204
299
|
*/
|
|
205
300
|
list: (asOf) =>
|
|
206
|
-
listAll(this,
|
|
301
|
+
listAll(this, "/api/v1/vocab-layers", { query: { "as-of": asOf } }),
|
|
207
302
|
/**
|
|
208
303
|
* Fetch a single page of vocab layers.
|
|
209
304
|
* @param {object} [opts]
|
|
@@ -213,7 +308,11 @@ class PlaidClient {
|
|
|
213
308
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
214
309
|
*/
|
|
215
310
|
listPage: ({ limit, cursor, asOf } = {}) =>
|
|
216
|
-
listPage(this,
|
|
311
|
+
listPage(this, "/api/v1/vocab-layers", {
|
|
312
|
+
limit,
|
|
313
|
+
cursor,
|
|
314
|
+
query: { "as-of": asOf },
|
|
315
|
+
}),
|
|
217
316
|
/**
|
|
218
317
|
* Async-iterate vocab layers page by page; yields each page's entries array.
|
|
219
318
|
* @param {object} [opts]
|
|
@@ -223,13 +322,17 @@ class PlaidClient {
|
|
|
223
322
|
* @returns {AsyncGenerator<Array>}
|
|
224
323
|
*/
|
|
225
324
|
iterPages: ({ pageSize, asOf } = {}) =>
|
|
226
|
-
iterPages(this,
|
|
325
|
+
iterPages(this, "/api/v1/vocab-layers", {
|
|
326
|
+
pageSize,
|
|
327
|
+
query: { "as-of": asOf },
|
|
328
|
+
}),
|
|
227
329
|
/**
|
|
228
330
|
* Create a new vocab layer. Note: this also registers the user as a maintainer.
|
|
229
331
|
* @param {string} name - The name
|
|
230
332
|
*/
|
|
231
333
|
create: (name, auditMessage) =>
|
|
232
|
-
this._request(
|
|
334
|
+
this._request("POST", "/api/v1/vocab-layers", {
|
|
335
|
+
auditMessage,
|
|
233
336
|
body: bodyOf({ name }),
|
|
234
337
|
}),
|
|
235
338
|
/**
|
|
@@ -238,14 +341,22 @@ class PlaidClient {
|
|
|
238
341
|
* @param {string} userId - The user ID
|
|
239
342
|
*/
|
|
240
343
|
addMaintainer: (id, userId, auditMessage) =>
|
|
241
|
-
this._request(
|
|
344
|
+
this._request(
|
|
345
|
+
"POST",
|
|
346
|
+
`/api/v1/vocab-layers/${id}/maintainers/${userId}`,
|
|
347
|
+
{ auditMessage },
|
|
348
|
+
),
|
|
242
349
|
/**
|
|
243
350
|
* Remove a user's maintainer privileges for this vocab layer.
|
|
244
351
|
* @param {string} id - The resource ID
|
|
245
352
|
* @param {string} userId - The user ID
|
|
246
353
|
*/
|
|
247
354
|
removeMaintainer: (id, userId, auditMessage) =>
|
|
248
|
-
this._request(
|
|
355
|
+
this._request(
|
|
356
|
+
"DELETE",
|
|
357
|
+
`/api/v1/vocab-layers/${id}/maintainers/${userId}`,
|
|
358
|
+
{ auditMessage },
|
|
359
|
+
),
|
|
249
360
|
};
|
|
250
361
|
|
|
251
362
|
this.relations = {
|
|
@@ -255,15 +366,18 @@ class PlaidClient {
|
|
|
255
366
|
* @param {any} body - The request body
|
|
256
367
|
*/
|
|
257
368
|
setMetadata: (relationId, body, auditMessage) =>
|
|
258
|
-
this._request(
|
|
259
|
-
|
|
369
|
+
this._request("PUT", `/api/v1/relations/${relationId}/metadata`, {
|
|
370
|
+
auditMessage,
|
|
371
|
+
rawBody: body,
|
|
372
|
+
skipResponseTransform: true,
|
|
260
373
|
}),
|
|
261
374
|
/**
|
|
262
375
|
* Remove all metadata from a relation.
|
|
263
376
|
* @param {string} relationId - The relation ID
|
|
264
377
|
*/
|
|
265
378
|
deleteMetadata: (relationId, auditMessage) =>
|
|
266
|
-
this._request(
|
|
379
|
+
this._request("DELETE", `/api/v1/relations/${relationId}/metadata`, {
|
|
380
|
+
auditMessage,
|
|
267
381
|
skipResponseTransform: true,
|
|
268
382
|
}),
|
|
269
383
|
/**
|
|
@@ -272,8 +386,10 @@ class PlaidClient {
|
|
|
272
386
|
* @param {any} body - The metadata patch
|
|
273
387
|
*/
|
|
274
388
|
patchMetadata: (relationId, body, auditMessage) =>
|
|
275
|
-
this._request(
|
|
276
|
-
|
|
389
|
+
this._request("PATCH", `/api/v1/relations/${relationId}/metadata`, {
|
|
390
|
+
auditMessage,
|
|
391
|
+
rawBody: body,
|
|
392
|
+
skipResponseTransform: true,
|
|
277
393
|
}),
|
|
278
394
|
/**
|
|
279
395
|
* Update the target span of a relation.
|
|
@@ -281,8 +397,9 @@ class PlaidClient {
|
|
|
281
397
|
* @param {string} spanId - The span ID
|
|
282
398
|
*/
|
|
283
399
|
setTarget: (relationId, spanId, auditMessage) =>
|
|
284
|
-
this._request(
|
|
285
|
-
|
|
400
|
+
this._request("PUT", `/api/v1/relations/${relationId}/target`, {
|
|
401
|
+
auditMessage,
|
|
402
|
+
body: bodyOf({ "span-id": spanId }),
|
|
286
403
|
}),
|
|
287
404
|
/**
|
|
288
405
|
* Get a relation by ID.
|
|
@@ -290,22 +407,25 @@ class PlaidClient {
|
|
|
290
407
|
* @param {string} [asOf] - Temporal query timestamp
|
|
291
408
|
*/
|
|
292
409
|
get: (relationId, asOf) =>
|
|
293
|
-
this._request(
|
|
294
|
-
queryParams: {
|
|
410
|
+
this._request("GET", `/api/v1/relations/${relationId}`, {
|
|
411
|
+
queryParams: { "as-of": asOf },
|
|
295
412
|
}),
|
|
296
413
|
/**
|
|
297
414
|
* Delete a relation.
|
|
298
415
|
* @param {string} relationId - The relation ID
|
|
299
416
|
*/
|
|
300
417
|
delete: (relationId, auditMessage) =>
|
|
301
|
-
this._request(
|
|
418
|
+
this._request("DELETE", `/api/v1/relations/${relationId}`, {
|
|
419
|
+
auditMessage,
|
|
420
|
+
}),
|
|
302
421
|
/**
|
|
303
422
|
* Update a relation's value.
|
|
304
423
|
* @param {string} relationId - The relation ID
|
|
305
424
|
* @param {any} value - The value
|
|
306
425
|
*/
|
|
307
426
|
update: (relationId, value, auditMessage) =>
|
|
308
|
-
this._request(
|
|
427
|
+
this._request("PATCH", `/api/v1/relations/${relationId}`, {
|
|
428
|
+
auditMessage,
|
|
309
429
|
body: bodyOf({ value }),
|
|
310
430
|
}),
|
|
311
431
|
/**
|
|
@@ -314,8 +434,9 @@ class PlaidClient {
|
|
|
314
434
|
* @param {string} spanId - The span ID
|
|
315
435
|
*/
|
|
316
436
|
setSource: (relationId, spanId, auditMessage) =>
|
|
317
|
-
this._request(
|
|
318
|
-
|
|
437
|
+
this._request("PUT", `/api/v1/relations/${relationId}/source`, {
|
|
438
|
+
auditMessage,
|
|
439
|
+
body: bodyOf({ "span-id": spanId }),
|
|
319
440
|
}),
|
|
320
441
|
/**
|
|
321
442
|
* Create a new relation. A relation is a directed edge between two spans
|
|
@@ -328,8 +449,15 @@ class PlaidClient {
|
|
|
328
449
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
329
450
|
*/
|
|
330
451
|
create: (layerId, sourceId, targetId, value, metadata, auditMessage) =>
|
|
331
|
-
this._request(
|
|
332
|
-
|
|
452
|
+
this._request("POST", "/api/v1/relations", {
|
|
453
|
+
auditMessage,
|
|
454
|
+
body: bodyOf({
|
|
455
|
+
"layer-id": layerId,
|
|
456
|
+
"source-id": sourceId,
|
|
457
|
+
"target-id": targetId,
|
|
458
|
+
value,
|
|
459
|
+
metadata,
|
|
460
|
+
}),
|
|
333
461
|
}),
|
|
334
462
|
/**
|
|
335
463
|
* Create multiple relations in a single operation.
|
|
@@ -337,13 +465,16 @@ class PlaidClient {
|
|
|
337
465
|
* @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
|
|
338
466
|
*/
|
|
339
467
|
bulkCreate: (body, auditMessage) =>
|
|
340
|
-
this._request(
|
|
468
|
+
this._request("POST", "/api/v1/relations/bulk", { auditMessage, body }),
|
|
341
469
|
/**
|
|
342
470
|
* Delete multiple relations in a single operation. Provide an array of IDs.
|
|
343
471
|
* @param {Array} body - The request body
|
|
344
472
|
*/
|
|
345
473
|
bulkDelete: (body, auditMessage) =>
|
|
346
|
-
this._request(
|
|
474
|
+
this._request("DELETE", "/api/v1/relations/bulk", {
|
|
475
|
+
auditMessage,
|
|
476
|
+
body,
|
|
477
|
+
}),
|
|
347
478
|
};
|
|
348
479
|
|
|
349
480
|
this.spanLayers = {
|
|
@@ -354,10 +485,18 @@ class PlaidClient {
|
|
|
354
485
|
* @param {string} configKey - The config key
|
|
355
486
|
* @param {any} configValue - Configuration value to set
|
|
356
487
|
*/
|
|
357
|
-
setConfig: (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
488
|
+
setConfig: (
|
|
489
|
+
spanLayerId,
|
|
490
|
+
namespace,
|
|
491
|
+
configKey,
|
|
492
|
+
configValue,
|
|
493
|
+
auditMessage,
|
|
494
|
+
) =>
|
|
495
|
+
this._request(
|
|
496
|
+
"PUT",
|
|
497
|
+
`/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`,
|
|
498
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
499
|
+
),
|
|
361
500
|
/**
|
|
362
501
|
* Remove a configuration value for a layer.
|
|
363
502
|
* @param {string} spanLayerId - The span layer ID
|
|
@@ -365,31 +504,36 @@ class PlaidClient {
|
|
|
365
504
|
* @param {string} configKey - The config key
|
|
366
505
|
*/
|
|
367
506
|
deleteConfig: (spanLayerId, namespace, configKey, auditMessage) =>
|
|
368
|
-
this._request(
|
|
369
|
-
|
|
370
|
-
|
|
507
|
+
this._request(
|
|
508
|
+
"DELETE",
|
|
509
|
+
`/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`,
|
|
510
|
+
{ auditMessage, skipResponseTransform: true },
|
|
511
|
+
),
|
|
371
512
|
/**
|
|
372
513
|
* Get a span layer by ID.
|
|
373
514
|
* @param {string} spanLayerId - The span layer ID
|
|
374
515
|
* @param {string} [asOf] - Temporal query timestamp
|
|
375
516
|
*/
|
|
376
517
|
get: (spanLayerId, asOf) =>
|
|
377
|
-
this._request(
|
|
378
|
-
queryParams: {
|
|
518
|
+
this._request("GET", `/api/v1/span-layers/${spanLayerId}`, {
|
|
519
|
+
queryParams: { "as-of": asOf },
|
|
379
520
|
}),
|
|
380
521
|
/**
|
|
381
522
|
* Delete a span layer.
|
|
382
523
|
* @param {string} spanLayerId - The span layer ID
|
|
383
524
|
*/
|
|
384
525
|
delete: (spanLayerId, auditMessage) =>
|
|
385
|
-
this._request(
|
|
526
|
+
this._request("DELETE", `/api/v1/span-layers/${spanLayerId}`, {
|
|
527
|
+
auditMessage,
|
|
528
|
+
}),
|
|
386
529
|
/**
|
|
387
530
|
* Update a span layer's name.
|
|
388
531
|
* @param {string} spanLayerId - The span layer ID
|
|
389
532
|
* @param {string} name - The name
|
|
390
533
|
*/
|
|
391
534
|
update: (spanLayerId, name, auditMessage) =>
|
|
392
|
-
this._request(
|
|
535
|
+
this._request("PATCH", `/api/v1/span-layers/${spanLayerId}`, {
|
|
536
|
+
auditMessage,
|
|
393
537
|
body: bodyOf({ name }),
|
|
394
538
|
}),
|
|
395
539
|
/**
|
|
@@ -398,8 +542,9 @@ class PlaidClient {
|
|
|
398
542
|
* @param {string} name - The name
|
|
399
543
|
*/
|
|
400
544
|
create: (tokenLayerId, name, auditMessage) =>
|
|
401
|
-
this._request(
|
|
402
|
-
|
|
545
|
+
this._request("POST", "/api/v1/span-layers", {
|
|
546
|
+
auditMessage,
|
|
547
|
+
body: bodyOf({ "token-layer-id": tokenLayerId, name }),
|
|
403
548
|
}),
|
|
404
549
|
/**
|
|
405
550
|
* Shift a span layer's display order.
|
|
@@ -407,7 +552,8 @@ class PlaidClient {
|
|
|
407
552
|
* @param {string} direction - The direction ("up" or "down")
|
|
408
553
|
*/
|
|
409
554
|
shift: (spanLayerId, direction, auditMessage) =>
|
|
410
|
-
this._request(
|
|
555
|
+
this._request("POST", `/api/v1/span-layers/${spanLayerId}/shift`, {
|
|
556
|
+
auditMessage,
|
|
411
557
|
body: bodyOf({ direction }),
|
|
412
558
|
}),
|
|
413
559
|
};
|
|
@@ -419,7 +565,8 @@ class PlaidClient {
|
|
|
419
565
|
* @param {Array} tokens - The tokens
|
|
420
566
|
*/
|
|
421
567
|
setTokens: (spanId, tokens, auditMessage) =>
|
|
422
|
-
this._request(
|
|
568
|
+
this._request("PUT", `/api/v1/spans/${spanId}/tokens`, {
|
|
569
|
+
auditMessage,
|
|
423
570
|
body: bodyOf({ tokens }),
|
|
424
571
|
}),
|
|
425
572
|
/**
|
|
@@ -431,8 +578,14 @@ class PlaidClient {
|
|
|
431
578
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
432
579
|
*/
|
|
433
580
|
create: (spanLayerId, tokens, value, metadata, auditMessage) =>
|
|
434
|
-
this._request(
|
|
435
|
-
|
|
581
|
+
this._request("POST", "/api/v1/spans", {
|
|
582
|
+
auditMessage,
|
|
583
|
+
body: bodyOf({
|
|
584
|
+
"span-layer-id": spanLayerId,
|
|
585
|
+
tokens,
|
|
586
|
+
value,
|
|
587
|
+
metadata,
|
|
588
|
+
}),
|
|
436
589
|
}),
|
|
437
590
|
/**
|
|
438
591
|
* Get a span by ID.
|
|
@@ -440,22 +593,23 @@ class PlaidClient {
|
|
|
440
593
|
* @param {string} [asOf] - Temporal query timestamp
|
|
441
594
|
*/
|
|
442
595
|
get: (spanId, asOf) =>
|
|
443
|
-
this._request(
|
|
444
|
-
queryParams: {
|
|
596
|
+
this._request("GET", `/api/v1/spans/${spanId}`, {
|
|
597
|
+
queryParams: { "as-of": asOf },
|
|
445
598
|
}),
|
|
446
599
|
/**
|
|
447
600
|
* Delete a span.
|
|
448
601
|
* @param {string} spanId - The span ID
|
|
449
602
|
*/
|
|
450
603
|
delete: (spanId, auditMessage) =>
|
|
451
|
-
this._request(
|
|
604
|
+
this._request("DELETE", `/api/v1/spans/${spanId}`, { auditMessage }),
|
|
452
605
|
/**
|
|
453
606
|
* Update a span's value.
|
|
454
607
|
* @param {string} spanId - The span ID
|
|
455
608
|
* @param {any} value - The value
|
|
456
609
|
*/
|
|
457
610
|
update: (spanId, value, auditMessage) =>
|
|
458
|
-
this._request(
|
|
611
|
+
this._request("PATCH", `/api/v1/spans/${spanId}`, {
|
|
612
|
+
auditMessage,
|
|
459
613
|
body: bodyOf({ value }),
|
|
460
614
|
}),
|
|
461
615
|
/**
|
|
@@ -464,28 +618,31 @@ class PlaidClient {
|
|
|
464
618
|
* @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
|
|
465
619
|
*/
|
|
466
620
|
bulkCreate: (body, auditMessage) =>
|
|
467
|
-
this._request(
|
|
621
|
+
this._request("POST", "/api/v1/spans/bulk", { auditMessage, body }),
|
|
468
622
|
/**
|
|
469
623
|
* Delete multiple spans in a single operation. Provide an array of IDs.
|
|
470
624
|
* @param {Array} body - The request body
|
|
471
625
|
*/
|
|
472
626
|
bulkDelete: (body, auditMessage) =>
|
|
473
|
-
this._request(
|
|
627
|
+
this._request("DELETE", "/api/v1/spans/bulk", { auditMessage, body }),
|
|
474
628
|
/**
|
|
475
629
|
* Replace all metadata for a span.
|
|
476
630
|
* @param {string} spanId - The span ID
|
|
477
631
|
* @param {any} body - The request body
|
|
478
632
|
*/
|
|
479
633
|
setMetadata: (spanId, body, auditMessage) =>
|
|
480
|
-
this._request(
|
|
481
|
-
|
|
634
|
+
this._request("PUT", `/api/v1/spans/${spanId}/metadata`, {
|
|
635
|
+
auditMessage,
|
|
636
|
+
rawBody: body,
|
|
637
|
+
skipResponseTransform: true,
|
|
482
638
|
}),
|
|
483
639
|
/**
|
|
484
640
|
* Remove all metadata from a span.
|
|
485
641
|
* @param {string} spanId - The span ID
|
|
486
642
|
*/
|
|
487
643
|
deleteMetadata: (spanId, auditMessage) =>
|
|
488
|
-
this._request(
|
|
644
|
+
this._request("DELETE", `/api/v1/spans/${spanId}/metadata`, {
|
|
645
|
+
auditMessage,
|
|
489
646
|
skipResponseTransform: true,
|
|
490
647
|
}),
|
|
491
648
|
/**
|
|
@@ -494,11 +651,49 @@ class PlaidClient {
|
|
|
494
651
|
* @param {any} body - The metadata patch
|
|
495
652
|
*/
|
|
496
653
|
patchMetadata: (spanId, body, auditMessage) =>
|
|
497
|
-
this._request(
|
|
498
|
-
|
|
654
|
+
this._request("PATCH", `/api/v1/spans/${spanId}/metadata`, {
|
|
655
|
+
auditMessage,
|
|
656
|
+
rawBody: body,
|
|
657
|
+
skipResponseTransform: true,
|
|
499
658
|
}),
|
|
500
659
|
};
|
|
501
660
|
|
|
661
|
+
// Server-level facts. Fetched at most once: the limits cannot change while
|
|
662
|
+
// the server is up, and a caller asking "will this file be accepted" should
|
|
663
|
+
// not pay a round trip to find out.
|
|
664
|
+
let infoPromise = null;
|
|
665
|
+
this.server = {
|
|
666
|
+
/**
|
|
667
|
+
* This server's version and the limits it enforces, e.g.
|
|
668
|
+
* `{ limits: { mediaFileBytes, jsonBodyBytes, batchOperations, … } }`.
|
|
669
|
+
* Sizes are in bytes. Unauthenticated.
|
|
670
|
+
*/
|
|
671
|
+
info: () => {
|
|
672
|
+
if (!infoPromise) {
|
|
673
|
+
// bypassBatch: a read belongs to whoever asked for it, not to
|
|
674
|
+
// whatever batch happens to be open on this shared client.
|
|
675
|
+
infoPromise = this._request("GET", "/api/v1/info", {
|
|
676
|
+
bypassBatch: true,
|
|
677
|
+
}).catch((err) => {
|
|
678
|
+
// A failure must not be cached: a client that starts before the
|
|
679
|
+
// server is up would never see the limits at all.
|
|
680
|
+
infoPromise = null;
|
|
681
|
+
throw err;
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
return infoPromise;
|
|
685
|
+
},
|
|
686
|
+
/** Just the limits, which is what a caller almost always wants. */
|
|
687
|
+
limits: async () => (await this.server.info()).limits,
|
|
688
|
+
/**
|
|
689
|
+
* Liveness, version and database size. Unauthenticated, and served
|
|
690
|
+
* outside the REST router at /health, so it answers even while the API
|
|
691
|
+
* is refusing requests. Never cached — the point is that it is current.
|
|
692
|
+
* @returns {Promise<{ok: boolean, version: string, uptimeMs: number, audit: object}>}
|
|
693
|
+
*/
|
|
694
|
+
health: () => PlaidClient.health(this.baseUrl, { timeout: this.timeout }),
|
|
695
|
+
};
|
|
696
|
+
|
|
502
697
|
this.batch = {
|
|
503
698
|
/**
|
|
504
699
|
* Execute multiple API operations atomically. If any operation fails, all
|
|
@@ -506,8 +701,10 @@ class PlaidClient {
|
|
|
506
701
|
* @param {Array} body - The request body
|
|
507
702
|
*/
|
|
508
703
|
submit: (body, auditMessage) =>
|
|
509
|
-
this._request(
|
|
510
|
-
|
|
704
|
+
this._request("POST", "/api/v1/batch", {
|
|
705
|
+
auditMessage,
|
|
706
|
+
body,
|
|
707
|
+
noBatch: true,
|
|
511
708
|
}),
|
|
512
709
|
};
|
|
513
710
|
|
|
@@ -518,15 +715,18 @@ class PlaidClient {
|
|
|
518
715
|
* @param {any} body - The request body
|
|
519
716
|
*/
|
|
520
717
|
setMetadata: (textId, body, auditMessage) =>
|
|
521
|
-
this._request(
|
|
522
|
-
|
|
718
|
+
this._request("PUT", `/api/v1/texts/${textId}/metadata`, {
|
|
719
|
+
auditMessage,
|
|
720
|
+
rawBody: body,
|
|
721
|
+
skipResponseTransform: true,
|
|
523
722
|
}),
|
|
524
723
|
/**
|
|
525
724
|
* Remove all metadata from a text.
|
|
526
725
|
* @param {string} textId - The text ID
|
|
527
726
|
*/
|
|
528
727
|
deleteMetadata: (textId, auditMessage) =>
|
|
529
|
-
this._request(
|
|
728
|
+
this._request("DELETE", `/api/v1/texts/${textId}/metadata`, {
|
|
729
|
+
auditMessage,
|
|
530
730
|
skipResponseTransform: true,
|
|
531
731
|
}),
|
|
532
732
|
/**
|
|
@@ -535,8 +735,10 @@ class PlaidClient {
|
|
|
535
735
|
* @param {any} body - The metadata patch
|
|
536
736
|
*/
|
|
537
737
|
patchMetadata: (textId, body, auditMessage) =>
|
|
538
|
-
this._request(
|
|
539
|
-
|
|
738
|
+
this._request("PATCH", `/api/v1/texts/${textId}/metadata`, {
|
|
739
|
+
auditMessage,
|
|
740
|
+
rawBody: body,
|
|
741
|
+
skipResponseTransform: true,
|
|
540
742
|
}),
|
|
541
743
|
/**
|
|
542
744
|
* Create a new text in a document's text layer. A text is a container for
|
|
@@ -547,8 +749,14 @@ class PlaidClient {
|
|
|
547
749
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
548
750
|
*/
|
|
549
751
|
create: (textLayerId, documentId, body, metadata, auditMessage) =>
|
|
550
|
-
this._request(
|
|
551
|
-
|
|
752
|
+
this._request("POST", "/api/v1/texts", {
|
|
753
|
+
auditMessage,
|
|
754
|
+
body: bodyOf({
|
|
755
|
+
"text-layer-id": textLayerId,
|
|
756
|
+
"document-id": documentId,
|
|
757
|
+
body,
|
|
758
|
+
metadata,
|
|
759
|
+
}),
|
|
552
760
|
}),
|
|
553
761
|
/**
|
|
554
762
|
* Get a text.
|
|
@@ -556,24 +764,30 @@ class PlaidClient {
|
|
|
556
764
|
* @param {string} [asOf] - Temporal query timestamp
|
|
557
765
|
*/
|
|
558
766
|
get: (textId, asOf) =>
|
|
559
|
-
this._request(
|
|
560
|
-
queryParams: {
|
|
767
|
+
this._request("GET", `/api/v1/texts/${textId}`, {
|
|
768
|
+
queryParams: { "as-of": asOf },
|
|
561
769
|
}),
|
|
562
770
|
/**
|
|
563
771
|
* Delete a text and all dependent data.
|
|
564
772
|
* @param {string} textId - The text ID
|
|
565
773
|
*/
|
|
566
774
|
delete: (textId, auditMessage) =>
|
|
567
|
-
this._request(
|
|
775
|
+
this._request("DELETE", `/api/v1/texts/${textId}`, { auditMessage }),
|
|
568
776
|
/**
|
|
569
777
|
* Update a text's body. A diff is computed and token indices are updated
|
|
570
778
|
* so that tokens remain intact. Alternatively, `body` can be a list of
|
|
571
|
-
* edit directives
|
|
779
|
+
* edit directives (code-point indices, applied in order):
|
|
780
|
+
* {type: 'delete', index, value: count}
|
|
781
|
+
* {type: 'insert', index, value: string}
|
|
782
|
+
* {type: 'replace', index, length, value: string} — unlike
|
|
783
|
+
* delete+insert, a token covering the whole range is resized to keep
|
|
784
|
+
* it (respell a word in place without losing its annotations).
|
|
572
785
|
* @param {string} textId - The text ID
|
|
573
786
|
* @param {any} body - The request body
|
|
574
787
|
*/
|
|
575
788
|
update: (textId, body, auditMessage) =>
|
|
576
|
-
this._request(
|
|
789
|
+
this._request("PATCH", `/api/v1/texts/${textId}`, {
|
|
790
|
+
auditMessage,
|
|
577
791
|
body: bodyOf({ body }),
|
|
578
792
|
}),
|
|
579
793
|
};
|
|
@@ -584,42 +798,58 @@ class PlaidClient {
|
|
|
584
798
|
* returns the full flat array. Admin-or-maintainer only.
|
|
585
799
|
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
586
800
|
* @param {object} [opts]
|
|
587
|
-
* @param {string} [opts.q] - Filter to
|
|
801
|
+
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
588
802
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
589
803
|
*/
|
|
590
804
|
list: ({ q, asOf } = {}) =>
|
|
591
|
-
listAll(this,
|
|
805
|
+
listAll(this, "/api/v1/users", { query: { q, "as-of": asOf } }),
|
|
592
806
|
/**
|
|
593
807
|
* Fetch a single page of users (optionally filtered by `q`).
|
|
594
808
|
* @param {object} [opts]
|
|
595
|
-
* @param {string} [opts.q] - Filter to
|
|
809
|
+
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
596
810
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
597
811
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
598
812
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
599
813
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
600
814
|
*/
|
|
601
815
|
listPage: ({ q, limit, cursor, asOf } = {}) =>
|
|
602
|
-
listPage(this,
|
|
816
|
+
listPage(this, "/api/v1/users", {
|
|
817
|
+
limit,
|
|
818
|
+
cursor,
|
|
819
|
+
query: { q, "as-of": asOf },
|
|
820
|
+
}),
|
|
603
821
|
/**
|
|
604
822
|
* Async-iterate users page by page; yields each page's entries array.
|
|
605
823
|
* @param {object} [opts]
|
|
606
|
-
* @param {string} [opts.q] - Filter to
|
|
824
|
+
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
607
825
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
608
826
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
609
827
|
* 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.
|
|
610
828
|
* @returns {AsyncGenerator<Array>}
|
|
611
829
|
*/
|
|
612
830
|
iterPages: ({ q, pageSize, asOf } = {}) =>
|
|
613
|
-
iterPages(this,
|
|
831
|
+
iterPages(this, "/api/v1/users", {
|
|
832
|
+
pageSize,
|
|
833
|
+
query: { q, "as-of": asOf },
|
|
834
|
+
}),
|
|
614
835
|
/**
|
|
615
|
-
* Create a new user
|
|
616
|
-
* @param {string}
|
|
836
|
+
* Create a new user.
|
|
837
|
+
* @param {string} email - The account's email address. It becomes the
|
|
838
|
+
* user's id and is what they log in with; it can never be changed.
|
|
617
839
|
* @param {string} password - The password
|
|
618
840
|
* @param {boolean} isAdmin - Whether the user is an admin
|
|
841
|
+
* @param {string} [displayName] - How the user is shown in the UI.
|
|
842
|
+
* Defaults to the local part of the email.
|
|
619
843
|
*/
|
|
620
|
-
create: (
|
|
621
|
-
this._request(
|
|
622
|
-
|
|
844
|
+
create: (email, password, isAdmin, displayName, auditMessage) =>
|
|
845
|
+
this._request("POST", "/api/v1/users", {
|
|
846
|
+
auditMessage,
|
|
847
|
+
body: bodyOf({
|
|
848
|
+
email,
|
|
849
|
+
password,
|
|
850
|
+
"is-admin": isAdmin,
|
|
851
|
+
"display-name": displayName,
|
|
852
|
+
}),
|
|
623
853
|
}),
|
|
624
854
|
/**
|
|
625
855
|
* Get audit log for a user's actions. Transparently follows pagination
|
|
@@ -629,10 +859,44 @@ class PlaidClient {
|
|
|
629
859
|
* @param {string} [startTime] - Start of time range
|
|
630
860
|
* @param {string} [endTime] - End of time range
|
|
631
861
|
* @param {string} [asOf] - Temporal query timestamp
|
|
862
|
+
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
863
|
+
* types, spelled as in an entry's `op/type` (e.g.
|
|
864
|
+
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
865
|
+
* one of its operations matches, carrying only the ones that did.
|
|
632
866
|
*/
|
|
633
|
-
audit: (userId, startTime, endTime, asOf) =>
|
|
867
|
+
audit: (userId, startTime, endTime, asOf, opTypes) =>
|
|
634
868
|
listAll(this, `/api/v1/users/${userId}/audit`, {
|
|
635
|
-
query: {
|
|
869
|
+
query: {
|
|
870
|
+
"start-time": startTime,
|
|
871
|
+
"end-time": endTime,
|
|
872
|
+
"as-of": asOf,
|
|
873
|
+
"op-types": opTypesParam(opTypes),
|
|
874
|
+
},
|
|
875
|
+
}),
|
|
876
|
+
/**
|
|
877
|
+
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
878
|
+
* rather than audit() wherever the caller wants the recent end of a log
|
|
879
|
+
* that may be long: audit() walks every page before it resolves.
|
|
880
|
+
* @param {object} [opts]
|
|
881
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
882
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
883
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
884
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
885
|
+
*/
|
|
886
|
+
auditPage: (
|
|
887
|
+
userId,
|
|
888
|
+
{ startTime, endTime, asOf, opTypes, order, limit, cursor } = {},
|
|
889
|
+
) =>
|
|
890
|
+
listPage(this, `/api/v1/users/${userId}/audit`, {
|
|
891
|
+
limit,
|
|
892
|
+
cursor,
|
|
893
|
+
query: {
|
|
894
|
+
"start-time": startTime,
|
|
895
|
+
"end-time": endTime,
|
|
896
|
+
"as-of": asOf,
|
|
897
|
+
"op-types": opTypesParam(opTypes),
|
|
898
|
+
order,
|
|
899
|
+
},
|
|
636
900
|
}),
|
|
637
901
|
/**
|
|
638
902
|
* Get a user by ID
|
|
@@ -640,8 +904,8 @@ class PlaidClient {
|
|
|
640
904
|
* @param {string} [asOf] - Temporal query timestamp
|
|
641
905
|
*/
|
|
642
906
|
get: (id, asOf) =>
|
|
643
|
-
this._request(
|
|
644
|
-
queryParams: {
|
|
907
|
+
this._request("GET", `/api/v1/users/${id}`, {
|
|
908
|
+
queryParams: { "as-of": asOf },
|
|
645
909
|
}),
|
|
646
910
|
/**
|
|
647
911
|
* Deactivate a user. Users are never hard-deleted: deactivation
|
|
@@ -652,7 +916,7 @@ class PlaidClient {
|
|
|
652
916
|
* @param {string} id - The resource ID
|
|
653
917
|
*/
|
|
654
918
|
delete: (id, auditMessage) =>
|
|
655
|
-
this._request(
|
|
919
|
+
this._request("DELETE", `/api/v1/users/${id}`, { auditMessage }),
|
|
656
920
|
/**
|
|
657
921
|
* Reactivate a deactivated user, restoring their ability to log in.
|
|
658
922
|
* Project memberships, vocab maintainerships, and API tokens removed
|
|
@@ -660,19 +924,27 @@ class PlaidClient {
|
|
|
660
924
|
* @param {string} id - The resource ID
|
|
661
925
|
*/
|
|
662
926
|
activate: (id, auditMessage) =>
|
|
663
|
-
this._request(
|
|
927
|
+
this._request("POST", `/api/v1/users/${id}/activate`, { auditMessage }),
|
|
664
928
|
/**
|
|
665
|
-
* Modify a user. Admins may change the
|
|
666
|
-
* status of any user. All other users may only modify their own
|
|
667
|
-
* or password.
|
|
668
|
-
*
|
|
929
|
+
* Modify a user. Admins may change the display name, password, and admin
|
|
930
|
+
* status of any user. All other users may only modify their own display
|
|
931
|
+
* name or password.
|
|
932
|
+
*
|
|
933
|
+
* A user's id is their email address and is fixed for the life of the
|
|
934
|
+
* account — it is what they log in with, so nothing can change it.
|
|
935
|
+
* @param {string} id - The resource ID (the user's email address)
|
|
669
936
|
* @param {string} [password] - New password
|
|
670
|
-
* @param {string} [
|
|
937
|
+
* @param {string} [displayName] - New display name
|
|
671
938
|
* @param {boolean} [isAdmin] - New admin status
|
|
672
939
|
*/
|
|
673
|
-
update: (id, password,
|
|
674
|
-
this._request(
|
|
675
|
-
|
|
940
|
+
update: (id, password, displayName, isAdmin, auditMessage) =>
|
|
941
|
+
this._request("PATCH", `/api/v1/users/${id}`, {
|
|
942
|
+
auditMessage,
|
|
943
|
+
body: bodyOf({
|
|
944
|
+
password,
|
|
945
|
+
"display-name": displayName,
|
|
946
|
+
"is-admin": isAdmin,
|
|
947
|
+
}),
|
|
676
948
|
}),
|
|
677
949
|
/**
|
|
678
950
|
* Build a URL for a user's profile picture, suitable for use directly as
|
|
@@ -692,7 +964,7 @@ class PlaidClient {
|
|
|
692
964
|
avatarUrl: (id, avatarHash) => {
|
|
693
965
|
if (avatarHash === null) return null;
|
|
694
966
|
const params = new URLSearchParams({ token: this.token });
|
|
695
|
-
if (avatarHash) params.set(
|
|
967
|
+
if (avatarHash) params.set("v", avatarHash);
|
|
696
968
|
return `${this.baseUrl}/api/v1/users/${id}/avatar?${params}`;
|
|
697
969
|
},
|
|
698
970
|
/**
|
|
@@ -701,7 +973,7 @@ class PlaidClient {
|
|
|
701
973
|
* @param {string} id - The user ID
|
|
702
974
|
*/
|
|
703
975
|
getAvatar: (id) =>
|
|
704
|
-
this._request(
|
|
976
|
+
this._request("GET", `/api/v1/users/${id}/avatar`, {
|
|
705
977
|
noBatch: true,
|
|
706
978
|
binaryResponse: true,
|
|
707
979
|
}),
|
|
@@ -715,9 +987,12 @@ class PlaidClient {
|
|
|
715
987
|
*/
|
|
716
988
|
setAvatar: (id, file, auditMessage) => {
|
|
717
989
|
const fd = new FormData();
|
|
718
|
-
fd.append(
|
|
719
|
-
return this._request(
|
|
720
|
-
|
|
990
|
+
fd.append("file", file);
|
|
991
|
+
return this._request("PUT", `/api/v1/users/${id}/avatar`, {
|
|
992
|
+
auditMessage,
|
|
993
|
+
body: fd,
|
|
994
|
+
formData: true,
|
|
995
|
+
noBatch: true,
|
|
721
996
|
});
|
|
722
997
|
},
|
|
723
998
|
/**
|
|
@@ -725,11 +1000,72 @@ class PlaidClient {
|
|
|
725
1000
|
* @param {string} id - The user ID
|
|
726
1001
|
*/
|
|
727
1002
|
deleteAvatar: (id, auditMessage) =>
|
|
728
|
-
this._request(
|
|
1003
|
+
this._request("DELETE", `/api/v1/users/${id}/avatar`, {
|
|
1004
|
+
auditMessage,
|
|
729
1005
|
noBatch: true,
|
|
730
1006
|
}),
|
|
731
1007
|
};
|
|
732
1008
|
|
|
1009
|
+
this.userData = {
|
|
1010
|
+
/**
|
|
1011
|
+
* List a user's private data entries ({key, updatedAt}, plus value when
|
|
1012
|
+
* includeValues). Owner or admin only.
|
|
1013
|
+
* @param {string} userId
|
|
1014
|
+
* @param {object} [opts]
|
|
1015
|
+
* @param {string} [opts.prefix] - Only keys starting with this prefix
|
|
1016
|
+
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
1017
|
+
*/
|
|
1018
|
+
list: (userId, { prefix, includeValues } = {}) =>
|
|
1019
|
+
this._request("GET", `/api/v1/users/${userId}/data`, {
|
|
1020
|
+
queryParams: { prefix, "include-values": includeValues },
|
|
1021
|
+
}),
|
|
1022
|
+
/**
|
|
1023
|
+
* Read one private data entry ({key, updatedAt, value}); 404 if absent.
|
|
1024
|
+
* @param {string} userId
|
|
1025
|
+
* @param {string} key
|
|
1026
|
+
*/
|
|
1027
|
+
get: (userId, key) =>
|
|
1028
|
+
this._request(
|
|
1029
|
+
"GET",
|
|
1030
|
+
`/api/v1/users/${userId}/data/${encodeURIComponent(key)}`,
|
|
1031
|
+
),
|
|
1032
|
+
/**
|
|
1033
|
+
* Create or replace one private data entry. `value` is any JSON (up to
|
|
1034
|
+
* 1 MB), stored as sent. Not audited, not batchable.
|
|
1035
|
+
*
|
|
1036
|
+
* The server stores it verbatim, but this client recases object keys on
|
|
1037
|
+
* the way out and back like any other body (`myKey` <-> `my-key`), so a
|
|
1038
|
+
* value whose keys are camelCase round-trips unchanged while one keyed
|
|
1039
|
+
* by arbitrary strings does not. Put such a map under a `metadata` key,
|
|
1040
|
+
* which both clients pass through untouched.
|
|
1041
|
+
* @param {string} userId
|
|
1042
|
+
* @param {string} key
|
|
1043
|
+
* @param {*} value
|
|
1044
|
+
*/
|
|
1045
|
+
put: (userId, key, value) =>
|
|
1046
|
+
this._request(
|
|
1047
|
+
"PUT",
|
|
1048
|
+
`/api/v1/users/${userId}/data/${encodeURIComponent(key)}`,
|
|
1049
|
+
{
|
|
1050
|
+
body: value,
|
|
1051
|
+
noBatch: true,
|
|
1052
|
+
},
|
|
1053
|
+
),
|
|
1054
|
+
/**
|
|
1055
|
+
* Delete one private data entry; 404 if absent.
|
|
1056
|
+
* @param {string} userId
|
|
1057
|
+
* @param {string} key
|
|
1058
|
+
*/
|
|
1059
|
+
delete: (userId, key) =>
|
|
1060
|
+
this._request(
|
|
1061
|
+
"DELETE",
|
|
1062
|
+
`/api/v1/users/${userId}/data/${encodeURIComponent(key)}`,
|
|
1063
|
+
{
|
|
1064
|
+
noBatch: true,
|
|
1065
|
+
},
|
|
1066
|
+
),
|
|
1067
|
+
};
|
|
1068
|
+
|
|
733
1069
|
this.apiTokens = {
|
|
734
1070
|
/**
|
|
735
1071
|
* List a user's named API tokens. Never includes the signed token
|
|
@@ -738,8 +1074,7 @@ class PlaidClient {
|
|
|
738
1074
|
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
739
1075
|
* @param {string} userId - The user ID who owns the tokens
|
|
740
1076
|
*/
|
|
741
|
-
list: (userId) =>
|
|
742
|
-
listAll(this, `/api/v1/users/${userId}/tokens`),
|
|
1077
|
+
list: (userId) => listAll(this, `/api/v1/users/${userId}/tokens`),
|
|
743
1078
|
/**
|
|
744
1079
|
* Fetch a single page of a user's named API tokens.
|
|
745
1080
|
* @param {string} userId - The user ID who owns the tokens
|
|
@@ -770,7 +1105,8 @@ class PlaidClient {
|
|
|
770
1105
|
* @returns {Promise<{id: string, name: string, token: string}>}
|
|
771
1106
|
*/
|
|
772
1107
|
create: (userId, name, auditMessage) =>
|
|
773
|
-
this._request(
|
|
1108
|
+
this._request("POST", `/api/v1/users/${userId}/tokens`, {
|
|
1109
|
+
auditMessage,
|
|
774
1110
|
body: bodyOf({ name }),
|
|
775
1111
|
}),
|
|
776
1112
|
/**
|
|
@@ -779,7 +1115,9 @@ class PlaidClient {
|
|
|
779
1115
|
* @param {string} tokenId - The token ID to revoke
|
|
780
1116
|
*/
|
|
781
1117
|
revoke: (userId, tokenId, auditMessage) =>
|
|
782
|
-
this._request(
|
|
1118
|
+
this._request("DELETE", `/api/v1/users/${userId}/tokens/${tokenId}`, {
|
|
1119
|
+
auditMessage,
|
|
1120
|
+
}),
|
|
783
1121
|
};
|
|
784
1122
|
|
|
785
1123
|
this.invites = {
|
|
@@ -792,29 +1130,41 @@ class PlaidClient {
|
|
|
792
1130
|
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
793
1131
|
* @param {object} [opts]
|
|
794
1132
|
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1133
|
+
* @param {boolean} [opts.all] - List every invite on the server (admin only)
|
|
795
1134
|
*/
|
|
796
|
-
list: ({ projectId } = {}) =>
|
|
797
|
-
listAll(this,
|
|
1135
|
+
list: ({ projectId, all } = {}) =>
|
|
1136
|
+
listAll(this, "/api/v1/invites", {
|
|
1137
|
+
query: { "project-id": projectId, all },
|
|
1138
|
+
}),
|
|
798
1139
|
/**
|
|
799
1140
|
* Fetch a single page of invites.
|
|
800
1141
|
* @param {object} [opts]
|
|
801
1142
|
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1143
|
+
* @param {boolean} [opts.all] - List every invite on the server (admin only)
|
|
802
1144
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
803
1145
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
804
1146
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
805
1147
|
*/
|
|
806
|
-
listPage: ({ projectId, limit, cursor } = {}) =>
|
|
807
|
-
listPage(this,
|
|
1148
|
+
listPage: ({ projectId, all, limit, cursor } = {}) =>
|
|
1149
|
+
listPage(this, "/api/v1/invites", {
|
|
1150
|
+
limit,
|
|
1151
|
+
cursor,
|
|
1152
|
+
query: { "project-id": projectId, all },
|
|
1153
|
+
}),
|
|
808
1154
|
/**
|
|
809
1155
|
* Async-iterate invites page by page; yields each page's entries array.
|
|
810
1156
|
* @param {object} [opts]
|
|
811
1157
|
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1158
|
+
* @param {boolean} [opts.all] - List every invite on the server (admin only)
|
|
812
1159
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
813
1160
|
* 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.
|
|
814
1161
|
* @returns {AsyncGenerator<Array>}
|
|
815
1162
|
*/
|
|
816
|
-
iterPages: ({ projectId, pageSize } = {}) =>
|
|
817
|
-
iterPages(this,
|
|
1163
|
+
iterPages: ({ projectId, all, pageSize } = {}) =>
|
|
1164
|
+
iterPages(this, "/api/v1/invites", {
|
|
1165
|
+
pageSize,
|
|
1166
|
+
query: { "project-id": projectId, all },
|
|
1167
|
+
}),
|
|
818
1168
|
/**
|
|
819
1169
|
* Mint an invite. The returned `code` is shown ONLY here — it is never
|
|
820
1170
|
* stored and cannot be recovered, so build and hand off the link now.
|
|
@@ -822,23 +1172,46 @@ class PlaidClient {
|
|
|
822
1172
|
*
|
|
823
1173
|
* Admins may mint anything. A project maintainer may mint role grants on
|
|
824
1174
|
* projects they maintain, and nothing else: no admin grant, no grantless
|
|
825
|
-
* invite, no password resets
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
1175
|
+
* invite, no password resets — so for a non-admin, projectId/projectRole
|
|
1176
|
+
* are required in practice (403 without them).
|
|
1177
|
+
*
|
|
1178
|
+
* EVERY option is optional; `create()` with no arguments mints a
|
|
1179
|
+
* single-use signup link granting nothing but an account. Two pairing
|
|
1180
|
+
* rules the server enforces with a 400: projectId and projectRole must be
|
|
1181
|
+
* given TOGETHER, and targetUserId may not be combined with projectId,
|
|
1182
|
+
* grantAdmin, or a maxUses above 1.
|
|
1183
|
+
* @param {object} [opts]
|
|
1184
|
+
* @param {string} [opts.projectId] - Project the redeemer joins (requires projectRole)
|
|
1185
|
+
* @param {string} [opts.projectRole] - "reader" | "writer" | "maintainer" (requires projectId)
|
|
829
1186
|
* @param {boolean} [opts.grantAdmin] - Make the new account a global admin (admin only)
|
|
830
|
-
* @param {string} [opts.targetUserId] -
|
|
1187
|
+
* @param {string} [opts.targetUserId] - Password reset for that user instead of a signup; admin only, single-use, grants nothing
|
|
831
1188
|
* @param {number} [opts.maxUses] - How many accounts this link may create (default 1)
|
|
832
1189
|
* @param {number} [opts.ttlDays] - Days until it expires (default 14, max 365)
|
|
833
1190
|
* @param {string} [opts.note] - Human label shown in your invite list
|
|
834
1191
|
* @returns {Promise<{id: string, code: string, kind: string, status: string}>}
|
|
835
1192
|
*/
|
|
836
|
-
create: (
|
|
837
|
-
|
|
1193
|
+
create: (
|
|
1194
|
+
{
|
|
1195
|
+
projectId,
|
|
1196
|
+
projectRole,
|
|
1197
|
+
grantAdmin,
|
|
1198
|
+
targetUserId,
|
|
1199
|
+
maxUses,
|
|
1200
|
+
ttlDays,
|
|
1201
|
+
note,
|
|
1202
|
+
} = {},
|
|
1203
|
+
auditMessage,
|
|
1204
|
+
) =>
|
|
1205
|
+
this._request("POST", "/api/v1/invites", {
|
|
1206
|
+
auditMessage,
|
|
838
1207
|
body: bodyOf({
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1208
|
+
"project-id": projectId,
|
|
1209
|
+
"project-role": projectRole,
|
|
1210
|
+
"grant-admin": grantAdmin,
|
|
1211
|
+
"target-user-id": targetUserId,
|
|
1212
|
+
"max-uses": maxUses,
|
|
1213
|
+
"ttl-days": ttlDays,
|
|
1214
|
+
note,
|
|
842
1215
|
}),
|
|
843
1216
|
}),
|
|
844
1217
|
/**
|
|
@@ -847,7 +1220,207 @@ class PlaidClient {
|
|
|
847
1220
|
* @param {string} id - The invite ID
|
|
848
1221
|
*/
|
|
849
1222
|
revoke: (id, auditMessage) =>
|
|
850
|
-
this._request(
|
|
1223
|
+
this._request("DELETE", `/api/v1/invites/${id}`, { auditMessage }),
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
this.admin = {
|
|
1227
|
+
/**
|
|
1228
|
+
* Everything about the server in one read: version and JVM uptime,
|
|
1229
|
+
* database size and per-table row counts, media directory usage, backup
|
|
1230
|
+
* configuration and the backups on disk, and the settings an operator
|
|
1231
|
+
* gets asked to confirm. Carries no secrets. Runs a count per table and
|
|
1232
|
+
* walks the media directory, so open it, do not poll it. Admin only.
|
|
1233
|
+
* @returns {Promise<{version: string, jvm: object, database: object, media: object, backup: object, settings: object}>}
|
|
1234
|
+
*/
|
|
1235
|
+
server: () => this._request("GET", "/api/v1/admin/server"),
|
|
1236
|
+
/**
|
|
1237
|
+
* Take a database backup right now, outside the nightly schedule.
|
|
1238
|
+
* Resolves to the backup block with `ok` reporting whether the snapshot
|
|
1239
|
+
* succeeded. Uses VACUUM INTO, which only reads, so it is safe while
|
|
1240
|
+
* people are working. Admin only.
|
|
1241
|
+
* @returns {Promise<{ok: boolean, directory: string, backups: Array}>}
|
|
1242
|
+
*/
|
|
1243
|
+
backup: () => this._request("POST", "/api/v1/admin/backup"),
|
|
1244
|
+
/**
|
|
1245
|
+
* Documents currently held by an editing lock, with who holds each and
|
|
1246
|
+
* when it expires on its own. Admin only.
|
|
1247
|
+
* @returns {Promise<{entries: Array<{documentId: string, userId: string, expiresAt: number}>}>}
|
|
1248
|
+
*/
|
|
1249
|
+
locks: () => this._request("GET", "/api/v1/admin/locks"),
|
|
1250
|
+
/**
|
|
1251
|
+
* Drop the lock on a document whoever holds it. Idempotent. For a client
|
|
1252
|
+
* that went away without releasing one. Admin only.
|
|
1253
|
+
* @param {string} documentId - The document ID
|
|
1254
|
+
*/
|
|
1255
|
+
releaseLock: (documentId) =>
|
|
1256
|
+
this._request("DELETE", `/api/v1/admin/locks/${documentId}`),
|
|
1257
|
+
/**
|
|
1258
|
+
* Live login and invite rate-limit buckets: the address, the account
|
|
1259
|
+
* where there is one, failures inside the window, the limit, and whether
|
|
1260
|
+
* it is currently blocking. Admin only.
|
|
1261
|
+
* @returns {Promise<{windowMs: number, logins: Array, ips: Array, invites: Array}>}
|
|
1262
|
+
*/
|
|
1263
|
+
rateLimits: () => this._request("GET", "/api/v1/admin/rate-limits"),
|
|
1264
|
+
/**
|
|
1265
|
+
* Forget recorded rate-limit failures. With `ip`, clears that address,
|
|
1266
|
+
* narrowed to one account with `userId`. With neither, clears every
|
|
1267
|
+
* bucket. Only ever unblocks. Admin only.
|
|
1268
|
+
* @param {object} [opts]
|
|
1269
|
+
* @param {string} [opts.ip] - Clear only this address
|
|
1270
|
+
* @param {string} [opts.userId] - Narrow to one account on that address
|
|
1271
|
+
*/
|
|
1272
|
+
clearRateLimits: ({ ip, userId } = {}) =>
|
|
1273
|
+
this._request("DELETE", "/api/v1/admin/rate-limits", {
|
|
1274
|
+
queryParams: { ip, "user-id": userId },
|
|
1275
|
+
}),
|
|
1276
|
+
/**
|
|
1277
|
+
* The tail of the configured log file. Resolves with an `error` string
|
|
1278
|
+
* instead of lines when no log file is configured or it does not exist
|
|
1279
|
+
* yet. Admin only.
|
|
1280
|
+
* @param {object} [opts]
|
|
1281
|
+
* @param {number} [opts.lines] - How many lines (default 200, max 2000)
|
|
1282
|
+
* @returns {Promise<{file: (string|null), lines: string[], error?: string}>}
|
|
1283
|
+
*/
|
|
1284
|
+
logs: ({ lines } = {}) =>
|
|
1285
|
+
this._request("GET", "/api/v1/admin/logs", {
|
|
1286
|
+
queryParams: { lines },
|
|
1287
|
+
}),
|
|
1288
|
+
/**
|
|
1289
|
+
* Private user-data entries across every account, each carrying its
|
|
1290
|
+
* `userId`. `/users/:userId/data` has always been owner-or-admin; this
|
|
1291
|
+
* is the same reach across accounts at once. Transparently follows
|
|
1292
|
+
* pagination cursors and returns the full flat array. Admin only.
|
|
1293
|
+
*
|
|
1294
|
+
* Narrow with `prefix` (the literal head of a key) and/or `pattern`, a
|
|
1295
|
+
* GLOB over the whole key (`*` any run, `?` one character) — the way to
|
|
1296
|
+
* ask for a key convention identified by a segment in the middle, e.g.
|
|
1297
|
+
* `igt:assistant:*:meta:*`. Values come only with `includeValues`, and
|
|
1298
|
+
* are recased like any other body (see `userData.put`).
|
|
1299
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use userDataPage() for a single page in a batch.
|
|
1300
|
+
* @param {object} [opts]
|
|
1301
|
+
* @param {string} [opts.prefix] - Only keys starting with this
|
|
1302
|
+
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
1303
|
+
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
1304
|
+
* @returns {Promise<Array<{userId: string, key: string, updatedAt: string, value?: *}>>}
|
|
1305
|
+
*/
|
|
1306
|
+
userData: ({ prefix, pattern, includeValues } = {}) =>
|
|
1307
|
+
listAll(this, "/api/v1/admin/user-data", {
|
|
1308
|
+
query: {
|
|
1309
|
+
prefix,
|
|
1310
|
+
pattern,
|
|
1311
|
+
"include-values": includeValues,
|
|
1312
|
+
},
|
|
1313
|
+
}),
|
|
1314
|
+
/**
|
|
1315
|
+
* One page of private user-data entries across accounts, ordered by
|
|
1316
|
+
* (user, key). Admin only.
|
|
1317
|
+
* @param {object} [opts]
|
|
1318
|
+
* @param {string} [opts.prefix] - Only keys starting with this
|
|
1319
|
+
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
1320
|
+
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
1321
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1322
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1323
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1324
|
+
*/
|
|
1325
|
+
userDataPage: ({ prefix, pattern, includeValues, limit, cursor } = {}) =>
|
|
1326
|
+
listPage(this, "/api/v1/admin/user-data", {
|
|
1327
|
+
limit,
|
|
1328
|
+
cursor,
|
|
1329
|
+
query: {
|
|
1330
|
+
prefix,
|
|
1331
|
+
pattern,
|
|
1332
|
+
"include-values": includeValues,
|
|
1333
|
+
},
|
|
1334
|
+
}),
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
this.audit = {
|
|
1338
|
+
/**
|
|
1339
|
+
* The audit log across every project, oldest first. Same fold, window
|
|
1340
|
+
* and op-type filter as the per-project read, with the entity scope
|
|
1341
|
+
* dropped. Admin only. Transparently follows pagination cursors and
|
|
1342
|
+
* returns the full flat array.
|
|
1343
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
1344
|
+
* @param {object} [opts]
|
|
1345
|
+
* @param {string} [opts.startTime] - Only operations at or after this instant
|
|
1346
|
+
* @param {string} [opts.endTime] - Only operations at or before this instant
|
|
1347
|
+
* @param {string[]|string} [opts.opTypes] - Only these op types (e.g.
|
|
1348
|
+
* `['span-layer/create']`). An entry appears when one of its operations
|
|
1349
|
+
* matches, carrying only the ones that did.
|
|
1350
|
+
*/
|
|
1351
|
+
list: ({ startTime, endTime, opTypes } = {}) =>
|
|
1352
|
+
listAll(this, "/api/v1/audit", {
|
|
1353
|
+
query: {
|
|
1354
|
+
"start-time": startTime,
|
|
1355
|
+
"end-time": endTime,
|
|
1356
|
+
"op-types": opTypesParam(opTypes),
|
|
1357
|
+
},
|
|
1358
|
+
}),
|
|
1359
|
+
/**
|
|
1360
|
+
* Fetch a single page of the instance-wide audit log. Admin only.
|
|
1361
|
+
* @param {object} [opts]
|
|
1362
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first, which
|
|
1363
|
+
* is what a feed wants. A cursor belongs to the direction that made it.
|
|
1364
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1365
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1366
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1367
|
+
*/
|
|
1368
|
+
listPage: ({ startTime, endTime, opTypes, order, limit, cursor } = {}) =>
|
|
1369
|
+
listPage(this, "/api/v1/audit", {
|
|
1370
|
+
limit,
|
|
1371
|
+
cursor,
|
|
1372
|
+
query: {
|
|
1373
|
+
"start-time": startTime,
|
|
1374
|
+
"end-time": endTime,
|
|
1375
|
+
"op-types": opTypesParam(opTypes),
|
|
1376
|
+
order,
|
|
1377
|
+
},
|
|
1378
|
+
}),
|
|
1379
|
+
/**
|
|
1380
|
+
* Async-iterate the instance-wide audit log page by page. Admin only.
|
|
1381
|
+
* 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.
|
|
1382
|
+
* @returns {AsyncGenerator<Array>}
|
|
1383
|
+
*/
|
|
1384
|
+
iterPages: ({ startTime, endTime, opTypes, pageSize } = {}) =>
|
|
1385
|
+
iterPages(this, "/api/v1/audit", {
|
|
1386
|
+
pageSize,
|
|
1387
|
+
query: {
|
|
1388
|
+
"start-time": startTime,
|
|
1389
|
+
"end-time": endTime,
|
|
1390
|
+
"op-types": opTypesParam(opTypes),
|
|
1391
|
+
},
|
|
1392
|
+
}),
|
|
1393
|
+
/**
|
|
1394
|
+
* Per-user activity counts. `changes` is the number of logical actions,
|
|
1395
|
+
* folded the way the audit feed folds them, so one "Confirm word
|
|
1396
|
+
* analysis" counts once however many rows it wrote; `operations` is the
|
|
1397
|
+
* unfolded row count. Only users who did something appear — subtract
|
|
1398
|
+
* from the roster you already hold to find the ones who did not.
|
|
1399
|
+
*
|
|
1400
|
+
* With `projectId`, scoped to that project and open to its maintainers.
|
|
1401
|
+
* Without one, instance-wide and admin only.
|
|
1402
|
+
* @param {object} [opts]
|
|
1403
|
+
* @param {string} [opts.projectId] - Scope to one project
|
|
1404
|
+
* @param {string} [opts.startTime] - Only count at or after this instant
|
|
1405
|
+
* @param {string} [opts.endTime] - Only count at or before this instant
|
|
1406
|
+
* @param {boolean} [opts.daily] - Also return `byDay`, a list of
|
|
1407
|
+
* `{date, changes}` per user, oldest first, at the cost of a second
|
|
1408
|
+
* grouped scan
|
|
1409
|
+
* @returns {Promise<Array<{user: object, operations: number, changes: number, documents: number, firstTs: string, lastTs: string, byDay?: object}>>}
|
|
1410
|
+
*/
|
|
1411
|
+
tally: async ({ projectId, startTime, endTime, daily } = {}) => {
|
|
1412
|
+
const path = projectId
|
|
1413
|
+
? `/api/v1/projects/${projectId}/audit/tally`
|
|
1414
|
+
: "/api/v1/audit/tally";
|
|
1415
|
+
const result = await this._request("GET", path, {
|
|
1416
|
+
queryParams: {
|
|
1417
|
+
"start-time": startTime,
|
|
1418
|
+
"end-time": endTime,
|
|
1419
|
+
daily,
|
|
1420
|
+
},
|
|
1421
|
+
});
|
|
1422
|
+
return result.entries;
|
|
1423
|
+
},
|
|
851
1424
|
};
|
|
852
1425
|
|
|
853
1426
|
this.tokenLayers = {
|
|
@@ -857,7 +1430,8 @@ class PlaidClient {
|
|
|
857
1430
|
* @param {string} direction - The direction ("up" or "down")
|
|
858
1431
|
*/
|
|
859
1432
|
shift: (tokenLayerId, direction, auditMessage) =>
|
|
860
|
-
this._request(
|
|
1433
|
+
this._request("POST", `/api/v1/token-layers/${tokenLayerId}/shift`, {
|
|
1434
|
+
auditMessage,
|
|
861
1435
|
body: bodyOf({ direction }),
|
|
862
1436
|
}),
|
|
863
1437
|
/**
|
|
@@ -867,9 +1441,21 @@ class PlaidClient {
|
|
|
867
1441
|
* @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.
|
|
868
1442
|
* @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).
|
|
869
1443
|
*/
|
|
870
|
-
create: (
|
|
871
|
-
|
|
872
|
-
|
|
1444
|
+
create: (
|
|
1445
|
+
textLayerId,
|
|
1446
|
+
name,
|
|
1447
|
+
overlapMode,
|
|
1448
|
+
parentTokenLayerId,
|
|
1449
|
+
auditMessage,
|
|
1450
|
+
) =>
|
|
1451
|
+
this._request("POST", "/api/v1/token-layers", {
|
|
1452
|
+
auditMessage,
|
|
1453
|
+
body: bodyOf({
|
|
1454
|
+
"text-layer-id": textLayerId,
|
|
1455
|
+
name,
|
|
1456
|
+
"overlap-mode": overlapMode,
|
|
1457
|
+
"parent-token-layer-id": parentTokenLayerId,
|
|
1458
|
+
}),
|
|
873
1459
|
}),
|
|
874
1460
|
/**
|
|
875
1461
|
* Set a configuration value for a layer in an editor namespace.
|
|
@@ -878,10 +1464,18 @@ class PlaidClient {
|
|
|
878
1464
|
* @param {string} configKey - The config key
|
|
879
1465
|
* @param {any} configValue - Configuration value to set
|
|
880
1466
|
*/
|
|
881
|
-
setConfig: (
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1467
|
+
setConfig: (
|
|
1468
|
+
tokenLayerId,
|
|
1469
|
+
namespace,
|
|
1470
|
+
configKey,
|
|
1471
|
+
configValue,
|
|
1472
|
+
auditMessage,
|
|
1473
|
+
) =>
|
|
1474
|
+
this._request(
|
|
1475
|
+
"PUT",
|
|
1476
|
+
`/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`,
|
|
1477
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
1478
|
+
),
|
|
885
1479
|
/**
|
|
886
1480
|
* Remove a configuration value for a layer.
|
|
887
1481
|
* @param {string} tokenLayerId - The token layer ID
|
|
@@ -889,31 +1483,36 @@ class PlaidClient {
|
|
|
889
1483
|
* @param {string} configKey - The config key
|
|
890
1484
|
*/
|
|
891
1485
|
deleteConfig: (tokenLayerId, namespace, configKey, auditMessage) =>
|
|
892
|
-
this._request(
|
|
893
|
-
|
|
894
|
-
|
|
1486
|
+
this._request(
|
|
1487
|
+
"DELETE",
|
|
1488
|
+
`/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`,
|
|
1489
|
+
{ auditMessage, skipResponseTransform: true },
|
|
1490
|
+
),
|
|
895
1491
|
/**
|
|
896
1492
|
* Get a token layer by ID.
|
|
897
1493
|
* @param {string} tokenLayerId - The token layer ID
|
|
898
1494
|
* @param {string} [asOf] - Temporal query timestamp
|
|
899
1495
|
*/
|
|
900
1496
|
get: (tokenLayerId, asOf) =>
|
|
901
|
-
this._request(
|
|
902
|
-
queryParams: {
|
|
1497
|
+
this._request("GET", `/api/v1/token-layers/${tokenLayerId}`, {
|
|
1498
|
+
queryParams: { "as-of": asOf },
|
|
903
1499
|
}),
|
|
904
1500
|
/**
|
|
905
1501
|
* Delete a token layer.
|
|
906
1502
|
* @param {string} tokenLayerId - The token layer ID
|
|
907
1503
|
*/
|
|
908
1504
|
delete: (tokenLayerId, auditMessage) =>
|
|
909
|
-
this._request(
|
|
1505
|
+
this._request("DELETE", `/api/v1/token-layers/${tokenLayerId}`, {
|
|
1506
|
+
auditMessage,
|
|
1507
|
+
}),
|
|
910
1508
|
/**
|
|
911
1509
|
* Update a token layer's name.
|
|
912
1510
|
* @param {string} tokenLayerId - The token layer ID
|
|
913
1511
|
* @param {string} name - The name
|
|
914
1512
|
*/
|
|
915
1513
|
update: (tokenLayerId, name, auditMessage) =>
|
|
916
|
-
this._request(
|
|
1514
|
+
this._request("PATCH", `/api/v1/token-layers/${tokenLayerId}`, {
|
|
1515
|
+
auditMessage,
|
|
917
1516
|
body: bodyOf({ name }),
|
|
918
1517
|
}),
|
|
919
1518
|
};
|
|
@@ -925,29 +1524,33 @@ class PlaidClient {
|
|
|
925
1524
|
* @param {string} [asOf] - Temporal query timestamp
|
|
926
1525
|
*/
|
|
927
1526
|
checkLock: (documentId, asOf) =>
|
|
928
|
-
this._request(
|
|
929
|
-
queryParams: {
|
|
1527
|
+
this._request("GET", `/api/v1/documents/${documentId}/lock`, {
|
|
1528
|
+
queryParams: { "as-of": asOf },
|
|
930
1529
|
}),
|
|
931
1530
|
/**
|
|
932
1531
|
* Acquire or refresh a document lock
|
|
933
1532
|
* @param {string} documentId - The document ID
|
|
934
1533
|
*/
|
|
935
1534
|
acquireLock: (documentId, auditMessage) =>
|
|
936
|
-
this._request(
|
|
1535
|
+
this._request("POST", `/api/v1/documents/${documentId}/lock`, {
|
|
1536
|
+
auditMessage,
|
|
1537
|
+
}),
|
|
937
1538
|
/**
|
|
938
1539
|
* Release a document lock
|
|
939
1540
|
* @param {string} documentId - The document ID
|
|
940
1541
|
*/
|
|
941
1542
|
releaseLock: (documentId, auditMessage) =>
|
|
942
|
-
this._request(
|
|
1543
|
+
this._request("DELETE", `/api/v1/documents/${documentId}/lock`, {
|
|
1544
|
+
auditMessage,
|
|
1545
|
+
}),
|
|
943
1546
|
/**
|
|
944
|
-
* Get media file for a document
|
|
1547
|
+
* Get the media file for a document. Media is not versioned, so there is
|
|
1548
|
+
* no as-of form: the route refuses the parameter. Prefer the document's
|
|
1549
|
+
* own `mediaUrl`, which carries the file's version for caching.
|
|
945
1550
|
* @param {string} documentId - The document ID
|
|
946
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
947
1551
|
*/
|
|
948
|
-
getMedia: (documentId
|
|
949
|
-
this._request(
|
|
950
|
-
queryParams: { 'as-of': asOf },
|
|
1552
|
+
getMedia: (documentId) =>
|
|
1553
|
+
this._request("GET", `/api/v1/documents/${documentId}/media`, {
|
|
951
1554
|
noBatch: true,
|
|
952
1555
|
binaryResponse: true,
|
|
953
1556
|
}),
|
|
@@ -955,12 +1558,18 @@ class PlaidClient {
|
|
|
955
1558
|
* Upload a media file for a document. Uses Apache Tika for content validation.
|
|
956
1559
|
* @param {string} documentId - The document ID
|
|
957
1560
|
* @param {File} file - The file to upload
|
|
1561
|
+
* @param {string} [auditMessage] - Custom audit-log message for this write
|
|
1562
|
+
* @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.
|
|
958
1563
|
*/
|
|
959
|
-
uploadMedia: (documentId, file, auditMessage) => {
|
|
1564
|
+
uploadMedia: (documentId, file, auditMessage, { onProgress } = {}) => {
|
|
960
1565
|
const fd = new FormData();
|
|
961
|
-
fd.append(
|
|
962
|
-
return this._request(
|
|
963
|
-
|
|
1566
|
+
fd.append("file", file);
|
|
1567
|
+
return this._request("PUT", `/api/v1/documents/${documentId}/media`, {
|
|
1568
|
+
auditMessage,
|
|
1569
|
+
body: fd,
|
|
1570
|
+
formData: true,
|
|
1571
|
+
noBatch: true,
|
|
1572
|
+
onUploadProgress: onProgress,
|
|
964
1573
|
});
|
|
965
1574
|
},
|
|
966
1575
|
/**
|
|
@@ -968,7 +1577,8 @@ class PlaidClient {
|
|
|
968
1577
|
* @param {string} documentId - The document ID
|
|
969
1578
|
*/
|
|
970
1579
|
deleteMedia: (documentId, auditMessage) =>
|
|
971
|
-
this._request(
|
|
1580
|
+
this._request("DELETE", `/api/v1/documents/${documentId}/media`, {
|
|
1581
|
+
auditMessage,
|
|
972
1582
|
noBatch: true,
|
|
973
1583
|
}),
|
|
974
1584
|
/**
|
|
@@ -977,15 +1587,18 @@ class PlaidClient {
|
|
|
977
1587
|
* @param {any} body - The request body
|
|
978
1588
|
*/
|
|
979
1589
|
setMetadata: (documentId, body, auditMessage) =>
|
|
980
|
-
this._request(
|
|
981
|
-
|
|
1590
|
+
this._request("PUT", `/api/v1/documents/${documentId}/metadata`, {
|
|
1591
|
+
auditMessage,
|
|
1592
|
+
rawBody: body,
|
|
1593
|
+
skipResponseTransform: true,
|
|
982
1594
|
}),
|
|
983
1595
|
/**
|
|
984
1596
|
* Remove all metadata from a document.
|
|
985
1597
|
* @param {string} documentId - The document ID
|
|
986
1598
|
*/
|
|
987
1599
|
deleteMetadata: (documentId, auditMessage) =>
|
|
988
|
-
this._request(
|
|
1600
|
+
this._request("DELETE", `/api/v1/documents/${documentId}/metadata`, {
|
|
1601
|
+
auditMessage,
|
|
989
1602
|
skipResponseTransform: true,
|
|
990
1603
|
}),
|
|
991
1604
|
/**
|
|
@@ -994,8 +1607,10 @@ class PlaidClient {
|
|
|
994
1607
|
* @param {any} body - The metadata patch
|
|
995
1608
|
*/
|
|
996
1609
|
patchMetadata: (documentId, body, auditMessage) =>
|
|
997
|
-
this._request(
|
|
998
|
-
|
|
1610
|
+
this._request("PATCH", `/api/v1/documents/${documentId}/metadata`, {
|
|
1611
|
+
auditMessage,
|
|
1612
|
+
rawBody: body,
|
|
1613
|
+
skipResponseTransform: true,
|
|
999
1614
|
}),
|
|
1000
1615
|
/**
|
|
1001
1616
|
* Get audit log for a document. Transparently follows pagination cursors
|
|
@@ -1005,34 +1620,106 @@ class PlaidClient {
|
|
|
1005
1620
|
* @param {string} [startTime] - Start of time range
|
|
1006
1621
|
* @param {string} [endTime] - End of time range
|
|
1007
1622
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1623
|
+
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
1624
|
+
* types, spelled as in an entry's `op/type` (e.g.
|
|
1625
|
+
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
1626
|
+
* one of its operations matches, carrying only the ones that did.
|
|
1008
1627
|
*/
|
|
1009
|
-
audit: (documentId, startTime, endTime, asOf) =>
|
|
1628
|
+
audit: (documentId, startTime, endTime, asOf, opTypes) =>
|
|
1010
1629
|
listAll(this, `/api/v1/documents/${documentId}/audit`, {
|
|
1011
|
-
query: {
|
|
1630
|
+
query: {
|
|
1631
|
+
"start-time": startTime,
|
|
1632
|
+
"end-time": endTime,
|
|
1633
|
+
"as-of": asOf,
|
|
1634
|
+
"op-types": opTypesParam(opTypes),
|
|
1635
|
+
},
|
|
1636
|
+
}),
|
|
1637
|
+
/**
|
|
1638
|
+
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
1639
|
+
* rather than audit() wherever the caller wants the recent end of a log
|
|
1640
|
+
* that may be long: audit() walks every page before it resolves.
|
|
1641
|
+
* @param {object} [opts]
|
|
1642
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
1643
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1644
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1645
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1646
|
+
*/
|
|
1647
|
+
auditPage: (
|
|
1648
|
+
documentId,
|
|
1649
|
+
{ startTime, endTime, asOf, opTypes, order, limit, cursor } = {},
|
|
1650
|
+
) =>
|
|
1651
|
+
listPage(this, `/api/v1/documents/${documentId}/audit`, {
|
|
1652
|
+
limit,
|
|
1653
|
+
cursor,
|
|
1654
|
+
query: {
|
|
1655
|
+
"start-time": startTime,
|
|
1656
|
+
"end-time": endTime,
|
|
1657
|
+
"as-of": asOf,
|
|
1658
|
+
"op-types": opTypesParam(opTypes),
|
|
1659
|
+
order,
|
|
1660
|
+
},
|
|
1661
|
+
}),
|
|
1662
|
+
/**
|
|
1663
|
+
* Restore a document to its state at an earlier time, as one
|
|
1664
|
+
* operation: what was deleted since then comes back under its original
|
|
1665
|
+
* id, what was added since is removed, and what changed is set back,
|
|
1666
|
+
* across every layer. A layer deleted since then, or a vocabulary entry
|
|
1667
|
+
* that no longer exists, is skipped and reported under `skipped`.
|
|
1668
|
+
* Resolves to a summary of the changes. Maintainers only.
|
|
1669
|
+
* @param {string} documentId - The document ID
|
|
1670
|
+
* @param {string} asOf - The moment to go back to (ISO-8601 instant),
|
|
1671
|
+
* typically a history entry's `endTime`
|
|
1672
|
+
* @param {object} [options] - `{ dryRun }`: with `dryRun` true nothing
|
|
1673
|
+
* is written and the summary says what would change
|
|
1674
|
+
* @param {string} [auditMessage] - Custom audit message for this operation
|
|
1675
|
+
*/
|
|
1676
|
+
restore: (documentId, asOf, { dryRun } = {}, auditMessage) =>
|
|
1677
|
+
this._request("POST", `/api/v1/documents/${documentId}/restore`, {
|
|
1678
|
+
queryParams: {
|
|
1679
|
+
"as-of": asOf,
|
|
1680
|
+
"dry-run": dryRun ? "true" : undefined,
|
|
1681
|
+
},
|
|
1682
|
+
auditMessage,
|
|
1012
1683
|
}),
|
|
1013
1684
|
/**
|
|
1014
1685
|
* Get a document. Set `includeBody` to true to include all data.
|
|
1686
|
+
*
|
|
1687
|
+
* `layers` narrows a body read to the layers you name (ids of any kind:
|
|
1688
|
+
* text, token, span, or relation). A layer comes back when it is named
|
|
1689
|
+
* or is an ancestor of a named layer, and carries its own
|
|
1690
|
+
* texts/tokens/spans/relations/vocabs only when it is itself named — so
|
|
1691
|
+
* name the text layer too if you also want the text body. An id that is
|
|
1692
|
+
* not a layer of this document's project is an error, not a quietly
|
|
1693
|
+
* smaller response. Requires `includeBody`.
|
|
1015
1694
|
* @param {string} documentId - The document ID
|
|
1016
1695
|
* @param {boolean} [includeBody] - Include document body data
|
|
1017
1696
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1697
|
+
* @param {string[]|string} [layers] - Layer ids to restrict a body read to
|
|
1018
1698
|
*/
|
|
1019
|
-
get: (documentId, includeBody, asOf) =>
|
|
1020
|
-
this._request(
|
|
1021
|
-
queryParams: {
|
|
1699
|
+
get: (documentId, includeBody, asOf, layers) =>
|
|
1700
|
+
this._request("GET", `/api/v1/documents/${documentId}`, {
|
|
1701
|
+
queryParams: {
|
|
1702
|
+
"include-body": includeBody,
|
|
1703
|
+
"as-of": asOf,
|
|
1704
|
+
layers: layersParam(layers),
|
|
1705
|
+
},
|
|
1022
1706
|
}),
|
|
1023
1707
|
/**
|
|
1024
1708
|
* Delete a document and all data contained.
|
|
1025
1709
|
* @param {string} documentId - The document ID
|
|
1026
1710
|
*/
|
|
1027
1711
|
delete: (documentId, auditMessage) =>
|
|
1028
|
-
this._request(
|
|
1712
|
+
this._request("DELETE", `/api/v1/documents/${documentId}`, {
|
|
1713
|
+
auditMessage,
|
|
1714
|
+
}),
|
|
1029
1715
|
/**
|
|
1030
1716
|
* Update a document's name.
|
|
1031
1717
|
* @param {string} documentId - The document ID
|
|
1032
1718
|
* @param {string} name - The name
|
|
1033
1719
|
*/
|
|
1034
1720
|
update: (documentId, name, auditMessage) =>
|
|
1035
|
-
this._request(
|
|
1721
|
+
this._request("PATCH", `/api/v1/documents/${documentId}`, {
|
|
1722
|
+
auditMessage,
|
|
1036
1723
|
body: bodyOf({ name }),
|
|
1037
1724
|
}),
|
|
1038
1725
|
/**
|
|
@@ -1042,8 +1729,28 @@ class PlaidClient {
|
|
|
1042
1729
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
1043
1730
|
*/
|
|
1044
1731
|
create: (projectId, name, metadata, auditMessage) =>
|
|
1045
|
-
this._request(
|
|
1046
|
-
|
|
1732
|
+
this._request("POST", "/api/v1/documents", {
|
|
1733
|
+
auditMessage,
|
|
1734
|
+
body: bodyOf({ "project-id": projectId, name, metadata }),
|
|
1735
|
+
}),
|
|
1736
|
+
/**
|
|
1737
|
+
* Copy a document and everything in it into a new document of the same
|
|
1738
|
+
* project, as one operation. The copy shares the source's layers and
|
|
1739
|
+
* the vocabulary entries its links name, and holds the source's texts,
|
|
1740
|
+
* tokens, spans, relations and vocab links under fresh ids, with their
|
|
1741
|
+
* metadata. Comments do not travel. The media file does, unless
|
|
1742
|
+
* `includeMedia` is false. Resolves to `{ id }`, plus `mediaError` when
|
|
1743
|
+
* the source had media the copy could not take with it.
|
|
1744
|
+
* @param {string} documentId - The document to copy
|
|
1745
|
+
* @param {string} name - The new document's name
|
|
1746
|
+
* @param {object} [options] - `{ includeMedia }`: false leaves the media
|
|
1747
|
+
* file behind
|
|
1748
|
+
* @param {string} [auditMessage] - Custom audit message for this operation
|
|
1749
|
+
*/
|
|
1750
|
+
copy: (documentId, name, { includeMedia } = {}, auditMessage) =>
|
|
1751
|
+
this._request("POST", `/api/v1/documents/${documentId}/copy`, {
|
|
1752
|
+
auditMessage,
|
|
1753
|
+
body: bodyOf({ name, "include-media": includeMedia }),
|
|
1047
1754
|
}),
|
|
1048
1755
|
};
|
|
1049
1756
|
|
|
@@ -1054,28 +1761,36 @@ class PlaidClient {
|
|
|
1054
1761
|
* @param {string} userId - The user ID
|
|
1055
1762
|
*/
|
|
1056
1763
|
addWriter: (id, userId, auditMessage) =>
|
|
1057
|
-
this._request(
|
|
1764
|
+
this._request("POST", `/api/v1/projects/${id}/writers/${userId}`, {
|
|
1765
|
+
auditMessage,
|
|
1766
|
+
}),
|
|
1058
1767
|
/**
|
|
1059
1768
|
* Remove a user's writer privileges for this project.
|
|
1060
1769
|
* @param {string} id - The resource ID
|
|
1061
1770
|
* @param {string} userId - The user ID
|
|
1062
1771
|
*/
|
|
1063
1772
|
removeWriter: (id, userId, auditMessage) =>
|
|
1064
|
-
this._request(
|
|
1773
|
+
this._request("DELETE", `/api/v1/projects/${id}/writers/${userId}`, {
|
|
1774
|
+
auditMessage,
|
|
1775
|
+
}),
|
|
1065
1776
|
/**
|
|
1066
1777
|
* Set a user's access level to read-only for this project.
|
|
1067
1778
|
* @param {string} id - The resource ID
|
|
1068
1779
|
* @param {string} userId - The user ID
|
|
1069
1780
|
*/
|
|
1070
1781
|
addReader: (id, userId, auditMessage) =>
|
|
1071
|
-
this._request(
|
|
1782
|
+
this._request("POST", `/api/v1/projects/${id}/readers/${userId}`, {
|
|
1783
|
+
auditMessage,
|
|
1784
|
+
}),
|
|
1072
1785
|
/**
|
|
1073
1786
|
* Remove a user's reader privileges for this project.
|
|
1074
1787
|
* @param {string} id - The resource ID
|
|
1075
1788
|
* @param {string} userId - The user ID
|
|
1076
1789
|
*/
|
|
1077
1790
|
removeReader: (id, userId, auditMessage) =>
|
|
1078
|
-
this._request(
|
|
1791
|
+
this._request("DELETE", `/api/v1/projects/${id}/readers/${userId}`, {
|
|
1792
|
+
auditMessage,
|
|
1793
|
+
}),
|
|
1079
1794
|
/**
|
|
1080
1795
|
* Set a configuration value for a project in an editor namespace.
|
|
1081
1796
|
* @param {string} id - The resource ID
|
|
@@ -1084,9 +1799,11 @@ class PlaidClient {
|
|
|
1084
1799
|
* @param {any} configValue - Configuration value to set
|
|
1085
1800
|
*/
|
|
1086
1801
|
setConfig: (id, namespace, configKey, configValue, auditMessage) =>
|
|
1087
|
-
this._request(
|
|
1088
|
-
|
|
1089
|
-
|
|
1802
|
+
this._request(
|
|
1803
|
+
"PUT",
|
|
1804
|
+
`/api/v1/projects/${id}/config/${namespace}/${configKey}`,
|
|
1805
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
1806
|
+
),
|
|
1090
1807
|
/**
|
|
1091
1808
|
* Remove a configuration value for a project.
|
|
1092
1809
|
* @param {string} id - The resource ID
|
|
@@ -1094,23 +1811,31 @@ class PlaidClient {
|
|
|
1094
1811
|
* @param {string} configKey - The config key
|
|
1095
1812
|
*/
|
|
1096
1813
|
deleteConfig: (id, namespace, configKey, auditMessage) =>
|
|
1097
|
-
this._request(
|
|
1098
|
-
|
|
1099
|
-
|
|
1814
|
+
this._request(
|
|
1815
|
+
"DELETE",
|
|
1816
|
+
`/api/v1/projects/${id}/config/${namespace}/${configKey}`,
|
|
1817
|
+
{ auditMessage, skipResponseTransform: true },
|
|
1818
|
+
),
|
|
1100
1819
|
/**
|
|
1101
1820
|
* Assign a user as a maintainer for this project.
|
|
1102
1821
|
* @param {string} id - The resource ID
|
|
1103
1822
|
* @param {string} userId - The user ID
|
|
1104
1823
|
*/
|
|
1105
1824
|
addMaintainer: (id, userId, auditMessage) =>
|
|
1106
|
-
this._request(
|
|
1825
|
+
this._request("POST", `/api/v1/projects/${id}/maintainers/${userId}`, {
|
|
1826
|
+
auditMessage,
|
|
1827
|
+
}),
|
|
1107
1828
|
/**
|
|
1108
1829
|
* Remove a user's maintainer privileges for this project.
|
|
1109
1830
|
* @param {string} id - The resource ID
|
|
1110
1831
|
* @param {string} userId - The user ID
|
|
1111
1832
|
*/
|
|
1112
1833
|
removeMaintainer: (id, userId, auditMessage) =>
|
|
1113
|
-
this._request(
|
|
1834
|
+
this._request(
|
|
1835
|
+
"DELETE",
|
|
1836
|
+
`/api/v1/projects/${id}/maintainers/${userId}`,
|
|
1837
|
+
{ auditMessage },
|
|
1838
|
+
),
|
|
1114
1839
|
/**
|
|
1115
1840
|
* Get audit log for a project. Transparently follows pagination cursors
|
|
1116
1841
|
* and returns the full flat array.
|
|
@@ -1119,10 +1844,58 @@ class PlaidClient {
|
|
|
1119
1844
|
* @param {string} [startTime] - Start of time range
|
|
1120
1845
|
* @param {string} [endTime] - End of time range
|
|
1121
1846
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1847
|
+
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
1848
|
+
* types, spelled as in an entry's `op/type` (e.g.
|
|
1849
|
+
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
1850
|
+
* one of its operations matches, carrying only the ones that did.
|
|
1122
1851
|
*/
|
|
1123
|
-
audit: (projectId, startTime, endTime, asOf) =>
|
|
1852
|
+
audit: (projectId, startTime, endTime, asOf, opTypes) =>
|
|
1124
1853
|
listAll(this, `/api/v1/projects/${projectId}/audit`, {
|
|
1125
|
-
query: {
|
|
1854
|
+
query: {
|
|
1855
|
+
"start-time": startTime,
|
|
1856
|
+
"end-time": endTime,
|
|
1857
|
+
"as-of": asOf,
|
|
1858
|
+
"op-types": opTypesParam(opTypes),
|
|
1859
|
+
},
|
|
1860
|
+
}),
|
|
1861
|
+
/**
|
|
1862
|
+
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
1863
|
+
* rather than audit() wherever the caller wants the recent end of a log
|
|
1864
|
+
* that may be long: audit() walks every page before it resolves.
|
|
1865
|
+
* @param {object} [opts]
|
|
1866
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
1867
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1868
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1869
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1870
|
+
*/
|
|
1871
|
+
auditPage: (
|
|
1872
|
+
projectId,
|
|
1873
|
+
{ startTime, endTime, asOf, opTypes, order, limit, cursor } = {},
|
|
1874
|
+
) =>
|
|
1875
|
+
listPage(this, `/api/v1/projects/${projectId}/audit`, {
|
|
1876
|
+
limit,
|
|
1877
|
+
cursor,
|
|
1878
|
+
query: {
|
|
1879
|
+
"start-time": startTime,
|
|
1880
|
+
"end-time": endTime,
|
|
1881
|
+
"as-of": asOf,
|
|
1882
|
+
"op-types": opTypesParam(opTypes),
|
|
1883
|
+
order,
|
|
1884
|
+
},
|
|
1885
|
+
}),
|
|
1886
|
+
/**
|
|
1887
|
+
* When the calling user last wrote to each document in a project, as a
|
|
1888
|
+
* `{documentId: timestamp}` map. Documents they have never written to
|
|
1889
|
+
* are absent. One request covers a whole document list.
|
|
1890
|
+
*
|
|
1891
|
+
* The response is NOT key-transformed: its keys are document ids, and
|
|
1892
|
+
* camelCasing would mangle the hyphens in a UUID.
|
|
1893
|
+
* @param {string} projectId - The project ID
|
|
1894
|
+
* @returns {Promise<Object<string, string>>}
|
|
1895
|
+
*/
|
|
1896
|
+
myLastEdits: (projectId) =>
|
|
1897
|
+
this._request("GET", `/api/v1/projects/${projectId}/audit/last-edits`, {
|
|
1898
|
+
skipResponseTransform: true,
|
|
1126
1899
|
}),
|
|
1127
1900
|
/**
|
|
1128
1901
|
* Link a vocabulary to a project.
|
|
@@ -1130,14 +1903,18 @@ class PlaidClient {
|
|
|
1130
1903
|
* @param {string} vocabId - The vocab layer ID
|
|
1131
1904
|
*/
|
|
1132
1905
|
linkVocab: (id, vocabId, auditMessage) =>
|
|
1133
|
-
this._request(
|
|
1906
|
+
this._request("POST", `/api/v1/projects/${id}/vocabs/${vocabId}`, {
|
|
1907
|
+
auditMessage,
|
|
1908
|
+
}),
|
|
1134
1909
|
/**
|
|
1135
1910
|
* Unlink a vocabulary from a project.
|
|
1136
1911
|
* @param {string} id - The resource ID
|
|
1137
1912
|
* @param {string} vocabId - The vocab layer ID
|
|
1138
1913
|
*/
|
|
1139
1914
|
unlinkVocab: (id, vocabId, auditMessage) =>
|
|
1140
|
-
this._request(
|
|
1915
|
+
this._request("DELETE", `/api/v1/projects/${id}/vocabs/${vocabId}`, {
|
|
1916
|
+
auditMessage,
|
|
1917
|
+
}),
|
|
1141
1918
|
/**
|
|
1142
1919
|
* Get a project by ID. To fetch the project's documents, use
|
|
1143
1920
|
* listDocuments(id) — the include-documents flag has been removed.
|
|
@@ -1145,8 +1922,8 @@ class PlaidClient {
|
|
|
1145
1922
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1146
1923
|
*/
|
|
1147
1924
|
get: (id, asOf) =>
|
|
1148
|
-
this._request(
|
|
1149
|
-
queryParams: {
|
|
1925
|
+
this._request("GET", `/api/v1/projects/${id}`, {
|
|
1926
|
+
queryParams: { "as-of": asOf },
|
|
1150
1927
|
}),
|
|
1151
1928
|
/**
|
|
1152
1929
|
* List all documents in a project. Transparently follows pagination
|
|
@@ -1157,8 +1934,7 @@ class PlaidClient {
|
|
|
1157
1934
|
* server rejects `?as-of=` on the documents-list route with a 400.
|
|
1158
1935
|
* @param {string} id - The project ID
|
|
1159
1936
|
*/
|
|
1160
|
-
listDocuments: (id) =>
|
|
1161
|
-
listAll(this, `/api/v1/projects/${id}/documents`),
|
|
1937
|
+
listDocuments: (id) => listAll(this, `/api/v1/projects/${id}/documents`),
|
|
1162
1938
|
/**
|
|
1163
1939
|
* Fetch a single page of a project's documents.
|
|
1164
1940
|
*
|
|
@@ -1198,14 +1974,18 @@ class PlaidClient {
|
|
|
1198
1974
|
* @param {number} [options.timeout=0] - Per-request timeout in ms (0/null disables)
|
|
1199
1975
|
*/
|
|
1200
1976
|
delete: (id, auditMessage, { timeout = 0 } = {}) =>
|
|
1201
|
-
this._request(
|
|
1977
|
+
this._request("DELETE", `/api/v1/projects/${id}`, {
|
|
1978
|
+
auditMessage,
|
|
1979
|
+
timeout,
|
|
1980
|
+
}),
|
|
1202
1981
|
/**
|
|
1203
1982
|
* Update a project's name.
|
|
1204
1983
|
* @param {string} id - The resource ID
|
|
1205
1984
|
* @param {string} name - The name
|
|
1206
1985
|
*/
|
|
1207
1986
|
update: (id, name, auditMessage) =>
|
|
1208
|
-
this._request(
|
|
1987
|
+
this._request("PATCH", `/api/v1/projects/${id}`, {
|
|
1988
|
+
auditMessage,
|
|
1209
1989
|
body: bodyOf({ name }),
|
|
1210
1990
|
}),
|
|
1211
1991
|
/**
|
|
@@ -1215,7 +1995,7 @@ class PlaidClient {
|
|
|
1215
1995
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1216
1996
|
*/
|
|
1217
1997
|
list: (asOf) =>
|
|
1218
|
-
listAll(this,
|
|
1998
|
+
listAll(this, "/api/v1/projects", { query: { "as-of": asOf } }),
|
|
1219
1999
|
/**
|
|
1220
2000
|
* Fetch a single page of projects.
|
|
1221
2001
|
* @param {object} [opts]
|
|
@@ -1225,7 +2005,11 @@ class PlaidClient {
|
|
|
1225
2005
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1226
2006
|
*/
|
|
1227
2007
|
listPage: ({ limit, cursor, asOf } = {}) =>
|
|
1228
|
-
listPage(this,
|
|
2008
|
+
listPage(this, "/api/v1/projects", {
|
|
2009
|
+
limit,
|
|
2010
|
+
cursor,
|
|
2011
|
+
query: { "as-of": asOf },
|
|
2012
|
+
}),
|
|
1229
2013
|
/**
|
|
1230
2014
|
* Async-iterate projects page by page; yields each page's entries array.
|
|
1231
2015
|
* @param {object} [opts]
|
|
@@ -1235,13 +2019,17 @@ class PlaidClient {
|
|
|
1235
2019
|
* @returns {AsyncGenerator<Array>}
|
|
1236
2020
|
*/
|
|
1237
2021
|
iterPages: ({ pageSize, asOf } = {}) =>
|
|
1238
|
-
iterPages(this,
|
|
2022
|
+
iterPages(this, "/api/v1/projects", {
|
|
2023
|
+
pageSize,
|
|
2024
|
+
query: { "as-of": asOf },
|
|
2025
|
+
}),
|
|
1239
2026
|
/**
|
|
1240
2027
|
* Create a new project. Note: this also registers the user as a maintainer.
|
|
1241
2028
|
* @param {string} name - The name
|
|
1242
2029
|
*/
|
|
1243
2030
|
create: (name, auditMessage) =>
|
|
1244
|
-
this._request(
|
|
2031
|
+
this._request("POST", "/api/v1/projects", {
|
|
2032
|
+
auditMessage,
|
|
1245
2033
|
body: bodyOf({ name }),
|
|
1246
2034
|
}),
|
|
1247
2035
|
};
|
|
@@ -1254,10 +2042,18 @@ class PlaidClient {
|
|
|
1254
2042
|
* @param {string} configKey - The config key
|
|
1255
2043
|
* @param {any} configValue - Configuration value to set
|
|
1256
2044
|
*/
|
|
1257
|
-
setConfig: (
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
2045
|
+
setConfig: (
|
|
2046
|
+
textLayerId,
|
|
2047
|
+
namespace,
|
|
2048
|
+
configKey,
|
|
2049
|
+
configValue,
|
|
2050
|
+
auditMessage,
|
|
2051
|
+
) =>
|
|
2052
|
+
this._request(
|
|
2053
|
+
"PUT",
|
|
2054
|
+
`/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`,
|
|
2055
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
2056
|
+
),
|
|
1261
2057
|
/**
|
|
1262
2058
|
* Remove a configuration value for a layer.
|
|
1263
2059
|
* @param {string} textLayerId - The text layer ID
|
|
@@ -1265,31 +2061,36 @@ class PlaidClient {
|
|
|
1265
2061
|
* @param {string} configKey - The config key
|
|
1266
2062
|
*/
|
|
1267
2063
|
deleteConfig: (textLayerId, namespace, configKey, auditMessage) =>
|
|
1268
|
-
this._request(
|
|
1269
|
-
|
|
1270
|
-
|
|
2064
|
+
this._request(
|
|
2065
|
+
"DELETE",
|
|
2066
|
+
`/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`,
|
|
2067
|
+
{ auditMessage, skipResponseTransform: true },
|
|
2068
|
+
),
|
|
1271
2069
|
/**
|
|
1272
2070
|
* Get a text layer by ID.
|
|
1273
2071
|
* @param {string} textLayerId - The text layer ID
|
|
1274
2072
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1275
2073
|
*/
|
|
1276
2074
|
get: (textLayerId, asOf) =>
|
|
1277
|
-
this._request(
|
|
1278
|
-
queryParams: {
|
|
2075
|
+
this._request("GET", `/api/v1/text-layers/${textLayerId}`, {
|
|
2076
|
+
queryParams: { "as-of": asOf },
|
|
1279
2077
|
}),
|
|
1280
2078
|
/**
|
|
1281
2079
|
* Delete a text layer.
|
|
1282
2080
|
* @param {string} textLayerId - The text layer ID
|
|
1283
2081
|
*/
|
|
1284
2082
|
delete: (textLayerId, auditMessage) =>
|
|
1285
|
-
this._request(
|
|
2083
|
+
this._request("DELETE", `/api/v1/text-layers/${textLayerId}`, {
|
|
2084
|
+
auditMessage,
|
|
2085
|
+
}),
|
|
1286
2086
|
/**
|
|
1287
2087
|
* Update a text layer's name.
|
|
1288
2088
|
* @param {string} textLayerId - The text layer ID
|
|
1289
2089
|
* @param {string} name - The name
|
|
1290
2090
|
*/
|
|
1291
2091
|
update: (textLayerId, name, auditMessage) =>
|
|
1292
|
-
this._request(
|
|
2092
|
+
this._request("PATCH", `/api/v1/text-layers/${textLayerId}`, {
|
|
2093
|
+
auditMessage,
|
|
1293
2094
|
body: bodyOf({ name }),
|
|
1294
2095
|
}),
|
|
1295
2096
|
/**
|
|
@@ -1298,7 +2099,8 @@ class PlaidClient {
|
|
|
1298
2099
|
* @param {string} direction - The direction ("up" or "down")
|
|
1299
2100
|
*/
|
|
1300
2101
|
shift: (textLayerId, direction, auditMessage) =>
|
|
1301
|
-
this._request(
|
|
2102
|
+
this._request("POST", `/api/v1/text-layers/${textLayerId}/shift`, {
|
|
2103
|
+
auditMessage,
|
|
1302
2104
|
body: bodyOf({ direction }),
|
|
1303
2105
|
}),
|
|
1304
2106
|
/**
|
|
@@ -1307,8 +2109,9 @@ class PlaidClient {
|
|
|
1307
2109
|
* @param {string} name - The name
|
|
1308
2110
|
*/
|
|
1309
2111
|
create: (projectId, name, auditMessage) =>
|
|
1310
|
-
this._request(
|
|
1311
|
-
|
|
2112
|
+
this._request("POST", "/api/v1/text-layers", {
|
|
2113
|
+
auditMessage,
|
|
2114
|
+
body: bodyOf({ "project-id": projectId, name }),
|
|
1312
2115
|
}),
|
|
1313
2116
|
};
|
|
1314
2117
|
|
|
@@ -1319,15 +2122,18 @@ class PlaidClient {
|
|
|
1319
2122
|
* @param {any} body - The request body
|
|
1320
2123
|
*/
|
|
1321
2124
|
setMetadata: (id, body, auditMessage) =>
|
|
1322
|
-
this._request(
|
|
1323
|
-
|
|
2125
|
+
this._request("PUT", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2126
|
+
auditMessage,
|
|
2127
|
+
rawBody: body,
|
|
2128
|
+
skipResponseTransform: true,
|
|
1324
2129
|
}),
|
|
1325
2130
|
/**
|
|
1326
2131
|
* Remove all metadata from a vocab item.
|
|
1327
2132
|
* @param {string} id - The resource ID
|
|
1328
2133
|
*/
|
|
1329
2134
|
deleteMetadata: (id, auditMessage) =>
|
|
1330
|
-
this._request(
|
|
2135
|
+
this._request("DELETE", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2136
|
+
auditMessage,
|
|
1331
2137
|
skipResponseTransform: true,
|
|
1332
2138
|
}),
|
|
1333
2139
|
/**
|
|
@@ -1336,8 +2142,10 @@ class PlaidClient {
|
|
|
1336
2142
|
* @param {any} body - The metadata patch
|
|
1337
2143
|
*/
|
|
1338
2144
|
patchMetadata: (id, body, auditMessage) =>
|
|
1339
|
-
this._request(
|
|
1340
|
-
|
|
2145
|
+
this._request("PATCH", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2146
|
+
auditMessage,
|
|
2147
|
+
rawBody: body,
|
|
2148
|
+
skipResponseTransform: true,
|
|
1341
2149
|
}),
|
|
1342
2150
|
/**
|
|
1343
2151
|
* Create a new vocab item
|
|
@@ -1346,8 +2154,9 @@ class PlaidClient {
|
|
|
1346
2154
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
1347
2155
|
*/
|
|
1348
2156
|
create: (vocabLayerId, form, metadata, auditMessage) =>
|
|
1349
|
-
this._request(
|
|
1350
|
-
|
|
2157
|
+
this._request("POST", "/api/v1/vocab-items", {
|
|
2158
|
+
auditMessage,
|
|
2159
|
+
body: bodyOf({ "vocab-layer-id": vocabLayerId, form, metadata }),
|
|
1351
2160
|
}),
|
|
1352
2161
|
/**
|
|
1353
2162
|
* Create multiple vocab items in a single operation. Entries may target
|
|
@@ -1355,36 +2164,43 @@ class PlaidClient {
|
|
|
1355
2164
|
* @param {Array<{vocabLayerId: string, form: string, metadata?: any}>} body - The vocab items to create
|
|
1356
2165
|
*/
|
|
1357
2166
|
bulkCreate: (body, auditMessage) =>
|
|
1358
|
-
this._request(
|
|
2167
|
+
this._request("POST", "/api/v1/vocab-items/bulk", {
|
|
2168
|
+
auditMessage,
|
|
2169
|
+
body,
|
|
2170
|
+
}),
|
|
1359
2171
|
/**
|
|
1360
2172
|
* Delete multiple vocab items in a single operation. Each item's
|
|
1361
2173
|
* descendant vocab links are deleted too. Provide an array of IDs.
|
|
1362
2174
|
* @param {string[]} body - The vocab item IDs to delete
|
|
1363
2175
|
*/
|
|
1364
2176
|
bulkDelete: (body, auditMessage) =>
|
|
1365
|
-
this._request(
|
|
2177
|
+
this._request("DELETE", "/api/v1/vocab-items/bulk", {
|
|
2178
|
+
auditMessage,
|
|
2179
|
+
body,
|
|
2180
|
+
}),
|
|
1366
2181
|
/**
|
|
1367
2182
|
* Get a vocab item by ID
|
|
1368
2183
|
* @param {string} id - The resource ID
|
|
1369
2184
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1370
2185
|
*/
|
|
1371
2186
|
get: (id, asOf) =>
|
|
1372
|
-
this._request(
|
|
1373
|
-
queryParams: {
|
|
2187
|
+
this._request("GET", `/api/v1/vocab-items/${id}`, {
|
|
2188
|
+
queryParams: { "as-of": asOf },
|
|
1374
2189
|
}),
|
|
1375
2190
|
/**
|
|
1376
2191
|
* Delete a vocab item
|
|
1377
2192
|
* @param {string} id - The resource ID
|
|
1378
2193
|
*/
|
|
1379
2194
|
delete: (id, auditMessage) =>
|
|
1380
|
-
this._request(
|
|
2195
|
+
this._request("DELETE", `/api/v1/vocab-items/${id}`, { auditMessage }),
|
|
1381
2196
|
/**
|
|
1382
2197
|
* Update a vocab item's form
|
|
1383
2198
|
* @param {string} id - The resource ID
|
|
1384
2199
|
* @param {string} form - The vocab item form
|
|
1385
2200
|
*/
|
|
1386
2201
|
update: (id, form, auditMessage) =>
|
|
1387
|
-
this._request(
|
|
2202
|
+
this._request("PATCH", `/api/v1/vocab-items/${id}`, {
|
|
2203
|
+
auditMessage,
|
|
1388
2204
|
body: bodyOf({ form }),
|
|
1389
2205
|
}),
|
|
1390
2206
|
};
|
|
@@ -1396,17 +2212,20 @@ class PlaidClient {
|
|
|
1396
2212
|
* @param {string} direction - The direction ("up" or "down")
|
|
1397
2213
|
*/
|
|
1398
2214
|
shift: (relationLayerId, direction, auditMessage) =>
|
|
1399
|
-
this._request(
|
|
1400
|
-
|
|
1401
|
-
|
|
2215
|
+
this._request(
|
|
2216
|
+
"POST",
|
|
2217
|
+
`/api/v1/relation-layers/${relationLayerId}/shift`,
|
|
2218
|
+
{ auditMessage, body: bodyOf({ direction }) },
|
|
2219
|
+
),
|
|
1402
2220
|
/**
|
|
1403
2221
|
* Create a new relation layer.
|
|
1404
2222
|
* @param {string} spanLayerId - The span layer ID
|
|
1405
2223
|
* @param {string} name - The name
|
|
1406
2224
|
*/
|
|
1407
2225
|
create: (spanLayerId, name, auditMessage) =>
|
|
1408
|
-
this._request(
|
|
1409
|
-
|
|
2226
|
+
this._request("POST", "/api/v1/relation-layers", {
|
|
2227
|
+
auditMessage,
|
|
2228
|
+
body: bodyOf({ "span-layer-id": spanLayerId, name }),
|
|
1410
2229
|
}),
|
|
1411
2230
|
/**
|
|
1412
2231
|
* Set a configuration value for a layer in an editor namespace.
|
|
@@ -1415,10 +2234,18 @@ class PlaidClient {
|
|
|
1415
2234
|
* @param {string} configKey - The config key
|
|
1416
2235
|
* @param {any} configValue - Configuration value to set
|
|
1417
2236
|
*/
|
|
1418
|
-
setConfig: (
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
2237
|
+
setConfig: (
|
|
2238
|
+
relationLayerId,
|
|
2239
|
+
namespace,
|
|
2240
|
+
configKey,
|
|
2241
|
+
configValue,
|
|
2242
|
+
auditMessage,
|
|
2243
|
+
) =>
|
|
2244
|
+
this._request(
|
|
2245
|
+
"PUT",
|
|
2246
|
+
`/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`,
|
|
2247
|
+
{ auditMessage, rawBody: configValue, skipResponseTransform: true },
|
|
2248
|
+
),
|
|
1422
2249
|
/**
|
|
1423
2250
|
* Remove a configuration value for a layer.
|
|
1424
2251
|
* @param {string} relationLayerId - The relation layer ID
|
|
@@ -1426,31 +2253,36 @@ class PlaidClient {
|
|
|
1426
2253
|
* @param {string} configKey - The config key
|
|
1427
2254
|
*/
|
|
1428
2255
|
deleteConfig: (relationLayerId, namespace, configKey, auditMessage) =>
|
|
1429
|
-
this._request(
|
|
1430
|
-
|
|
1431
|
-
|
|
2256
|
+
this._request(
|
|
2257
|
+
"DELETE",
|
|
2258
|
+
`/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`,
|
|
2259
|
+
{ auditMessage, skipResponseTransform: true },
|
|
2260
|
+
),
|
|
1432
2261
|
/**
|
|
1433
2262
|
* Get a relation layer by ID.
|
|
1434
2263
|
* @param {string} relationLayerId - The relation layer ID
|
|
1435
2264
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1436
2265
|
*/
|
|
1437
2266
|
get: (relationLayerId, asOf) =>
|
|
1438
|
-
this._request(
|
|
1439
|
-
queryParams: {
|
|
2267
|
+
this._request("GET", `/api/v1/relation-layers/${relationLayerId}`, {
|
|
2268
|
+
queryParams: { "as-of": asOf },
|
|
1440
2269
|
}),
|
|
1441
2270
|
/**
|
|
1442
2271
|
* Delete a relation layer.
|
|
1443
2272
|
* @param {string} relationLayerId - The relation layer ID
|
|
1444
2273
|
*/
|
|
1445
2274
|
delete: (relationLayerId, auditMessage) =>
|
|
1446
|
-
this._request(
|
|
2275
|
+
this._request("DELETE", `/api/v1/relation-layers/${relationLayerId}`, {
|
|
2276
|
+
auditMessage,
|
|
2277
|
+
}),
|
|
1447
2278
|
/**
|
|
1448
2279
|
* Update a relation layer's name.
|
|
1449
2280
|
* @param {string} relationLayerId - The relation layer ID
|
|
1450
2281
|
* @param {string} name - The name
|
|
1451
2282
|
*/
|
|
1452
2283
|
update: (relationLayerId, name, auditMessage) =>
|
|
1453
|
-
this._request(
|
|
2284
|
+
this._request("PATCH", `/api/v1/relation-layers/${relationLayerId}`, {
|
|
2285
|
+
auditMessage,
|
|
1454
2286
|
body: bodyOf({ name }),
|
|
1455
2287
|
}),
|
|
1456
2288
|
};
|
|
@@ -1474,9 +2306,25 @@ class PlaidClient {
|
|
|
1474
2306
|
* @param {number} [precedence] - Ordering precedence
|
|
1475
2307
|
* @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
|
|
1476
2308
|
*/
|
|
1477
|
-
create: (
|
|
1478
|
-
|
|
1479
|
-
|
|
2309
|
+
create: (
|
|
2310
|
+
tokenLayerId,
|
|
2311
|
+
text,
|
|
2312
|
+
begin,
|
|
2313
|
+
end,
|
|
2314
|
+
precedence,
|
|
2315
|
+
metadata,
|
|
2316
|
+
auditMessage,
|
|
2317
|
+
) =>
|
|
2318
|
+
this._request("POST", "/api/v1/tokens", {
|
|
2319
|
+
auditMessage,
|
|
2320
|
+
body: bodyOf({
|
|
2321
|
+
"token-layer-id": tokenLayerId,
|
|
2322
|
+
text,
|
|
2323
|
+
begin,
|
|
2324
|
+
end,
|
|
2325
|
+
precedence,
|
|
2326
|
+
metadata,
|
|
2327
|
+
}),
|
|
1480
2328
|
}),
|
|
1481
2329
|
/**
|
|
1482
2330
|
* Get a token.
|
|
@@ -1484,8 +2332,8 @@ class PlaidClient {
|
|
|
1484
2332
|
* @param {string} [asOf] - Temporal query timestamp
|
|
1485
2333
|
*/
|
|
1486
2334
|
get: (tokenId, asOf) =>
|
|
1487
|
-
this._request(
|
|
1488
|
-
queryParams: {
|
|
2335
|
+
this._request("GET", `/api/v1/tokens/${tokenId}`, {
|
|
2336
|
+
queryParams: { "as-of": asOf },
|
|
1489
2337
|
}),
|
|
1490
2338
|
/**
|
|
1491
2339
|
* Delete a token and remove it from any spans. If this causes a span to
|
|
@@ -1493,7 +2341,7 @@ class PlaidClient {
|
|
|
1493
2341
|
* @param {string} tokenId - The token ID
|
|
1494
2342
|
*/
|
|
1495
2343
|
delete: (tokenId, auditMessage) =>
|
|
1496
|
-
this._request(
|
|
2344
|
+
this._request("DELETE", `/api/v1/tokens/${tokenId}`, { auditMessage }),
|
|
1497
2345
|
/**
|
|
1498
2346
|
* Update a token.
|
|
1499
2347
|
* @param {string} tokenId - The token ID
|
|
@@ -1505,7 +2353,8 @@ class PlaidClient {
|
|
|
1505
2353
|
* drops undefined, so the three cases map correctly to the server.
|
|
1506
2354
|
*/
|
|
1507
2355
|
update: (tokenId, begin, end, precedence, auditMessage) =>
|
|
1508
|
-
this._request(
|
|
2356
|
+
this._request("PATCH", `/api/v1/tokens/${tokenId}`, {
|
|
2357
|
+
auditMessage,
|
|
1509
2358
|
body: bodyOf({ begin, end, precedence }),
|
|
1510
2359
|
}),
|
|
1511
2360
|
/**
|
|
@@ -1514,13 +2363,13 @@ class PlaidClient {
|
|
|
1514
2363
|
* @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
|
|
1515
2364
|
*/
|
|
1516
2365
|
bulkCreate: (body, auditMessage) =>
|
|
1517
|
-
this._request(
|
|
2366
|
+
this._request("POST", "/api/v1/tokens/bulk", { auditMessage, body }),
|
|
1518
2367
|
/**
|
|
1519
2368
|
* Delete multiple tokens in a single operation. Provide an array of IDs.
|
|
1520
2369
|
* @param {Array} body - The request body
|
|
1521
2370
|
*/
|
|
1522
2371
|
bulkDelete: (body, auditMessage) =>
|
|
1523
|
-
this._request(
|
|
2372
|
+
this._request("DELETE", "/api/v1/tokens/bulk", { auditMessage, body }),
|
|
1524
2373
|
/**
|
|
1525
2374
|
* Split a token at a Unicode code-point offset. The original token becomes the
|
|
1526
2375
|
* left half (keeps its ID, spans, vocab-links); the new right token's ID is returned.
|
|
@@ -1528,7 +2377,8 @@ class PlaidClient {
|
|
|
1528
2377
|
* @param {number} position - Code-point offset to split at (strictly between begin and end)
|
|
1529
2378
|
*/
|
|
1530
2379
|
split: (tokenId, position, auditMessage) =>
|
|
1531
|
-
this._request(
|
|
2380
|
+
this._request("POST", `/api/v1/tokens/${tokenId}/split`, {
|
|
2381
|
+
auditMessage,
|
|
1532
2382
|
body: bodyOf({ position }),
|
|
1533
2383
|
}),
|
|
1534
2384
|
/**
|
|
@@ -1540,8 +2390,9 @@ class PlaidClient {
|
|
|
1540
2390
|
* @param {string} otherTokenId - The other token to merge in
|
|
1541
2391
|
*/
|
|
1542
2392
|
merge: (tokenId, otherTokenId, auditMessage) =>
|
|
1543
|
-
this._request(
|
|
1544
|
-
|
|
2393
|
+
this._request("POST", `/api/v1/tokens/${tokenId}/merge`, {
|
|
2394
|
+
auditMessage,
|
|
2395
|
+
body: bodyOf({ "other-token-id": otherTokenId }),
|
|
1545
2396
|
}),
|
|
1546
2397
|
/**
|
|
1547
2398
|
* Shift a token's boundary. On partitioning layers the adjacent token is
|
|
@@ -1552,7 +2403,8 @@ class PlaidClient {
|
|
|
1552
2403
|
* @param {number} [end] - New end offset, exclusive (Unicode code points)
|
|
1553
2404
|
*/
|
|
1554
2405
|
shift: (tokenId, begin, end, auditMessage) =>
|
|
1555
|
-
this._request(
|
|
2406
|
+
this._request("POST", `/api/v1/tokens/${tokenId}/shift`, {
|
|
2407
|
+
auditMessage,
|
|
1556
2408
|
body: bodyOf({ begin, end }),
|
|
1557
2409
|
}),
|
|
1558
2410
|
/**
|
|
@@ -1561,15 +2413,18 @@ class PlaidClient {
|
|
|
1561
2413
|
* @param {any} body - The request body
|
|
1562
2414
|
*/
|
|
1563
2415
|
setMetadata: (tokenId, body, auditMessage) =>
|
|
1564
|
-
this._request(
|
|
1565
|
-
|
|
2416
|
+
this._request("PUT", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2417
|
+
auditMessage,
|
|
2418
|
+
rawBody: body,
|
|
2419
|
+
skipResponseTransform: true,
|
|
1566
2420
|
}),
|
|
1567
2421
|
/**
|
|
1568
2422
|
* Remove all metadata from a token.
|
|
1569
2423
|
* @param {string} tokenId - The token ID
|
|
1570
2424
|
*/
|
|
1571
2425
|
deleteMetadata: (tokenId, auditMessage) =>
|
|
1572
|
-
this._request(
|
|
2426
|
+
this._request("DELETE", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2427
|
+
auditMessage,
|
|
1573
2428
|
skipResponseTransform: true,
|
|
1574
2429
|
}),
|
|
1575
2430
|
/**
|
|
@@ -1578,8 +2433,10 @@ class PlaidClient {
|
|
|
1578
2433
|
* @param {any} body - The metadata patch
|
|
1579
2434
|
*/
|
|
1580
2435
|
patchMetadata: (tokenId, body, auditMessage) =>
|
|
1581
|
-
this._request(
|
|
1582
|
-
|
|
2436
|
+
this._request("PATCH", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2437
|
+
auditMessage,
|
|
2438
|
+
rawBody: body,
|
|
2439
|
+
skipResponseTransform: true,
|
|
1583
2440
|
}),
|
|
1584
2441
|
};
|
|
1585
2442
|
|
|
@@ -1595,14 +2452,22 @@ class PlaidClient {
|
|
|
1595
2452
|
createSSEConnection(this, projectId, onEvent, path),
|
|
1596
2453
|
|
|
1597
2454
|
/**
|
|
1598
|
-
* Send a message to project listeners
|
|
2455
|
+
* Send a message to project listeners.
|
|
2456
|
+
*
|
|
2457
|
+
* `data` may be any JSON value and is sent VERBATIM (`rawBody`): a
|
|
2458
|
+
* message payload is opaque application data, like `metadata` and
|
|
2459
|
+
* `config`, so its keys must not be re-cased on the way out. Without
|
|
2460
|
+
* this a key such as `case-marker` would reach listeners as
|
|
2461
|
+
* `caseMarker` in JS and `case_marker` in Python. `listen` restores it
|
|
2462
|
+
* verbatim on the way in.
|
|
1599
2463
|
* @param {string} projectId - The UUID of the project to send to
|
|
1600
2464
|
* @param {any} data - The message data to send
|
|
1601
2465
|
* @returns {Promise<any>} Response from the send operation
|
|
1602
2466
|
*/
|
|
1603
2467
|
sendMessage: (projectId, data, auditMessage) =>
|
|
1604
|
-
this._request(
|
|
1605
|
-
|
|
2468
|
+
this._request("POST", `/api/v1/projects/${projectId}/message`, {
|
|
2469
|
+
auditMessage,
|
|
2470
|
+
rawBody: { body: data },
|
|
1606
2471
|
}),
|
|
1607
2472
|
|
|
1608
2473
|
/**
|
|
@@ -1612,8 +2477,7 @@ class PlaidClient {
|
|
|
1612
2477
|
* @param {string} projectId - The UUID of the project to query
|
|
1613
2478
|
* @returns {Promise<Array>} Array of discovered service information
|
|
1614
2479
|
*/
|
|
1615
|
-
discoverServices: (projectId) =>
|
|
1616
|
-
discoverServices(this, projectId),
|
|
2480
|
+
discoverServices: (projectId) => discoverServices(this, projectId),
|
|
1617
2481
|
|
|
1618
2482
|
/**
|
|
1619
2483
|
* Forget a previously-seen (offline) service. Maintainer-only; 409 if
|
|
@@ -1627,26 +2491,100 @@ class PlaidClient {
|
|
|
1627
2491
|
|
|
1628
2492
|
/**
|
|
1629
2493
|
* Register as a service and handle incoming work requests.
|
|
2494
|
+
*
|
|
2495
|
+
* The registration reopens its channel whenever it drops, so a server
|
|
2496
|
+
* restart needs no service restart.
|
|
1630
2497
|
* @param {string} projectId - The UUID of the project to serve
|
|
1631
2498
|
* @param {Object} serviceInfo - Service information {serviceId, serviceName, description}
|
|
1632
2499
|
* @param {function} onServiceRequest - Callback (data, responseHelper)
|
|
1633
2500
|
* @param {Object} [extras] - Optional additional service metadata
|
|
1634
|
-
* @
|
|
2501
|
+
* @param {function} [onStatus] - Optional callback (event, projectId, detail) for
|
|
2502
|
+
* connection transitions: 'registered', 'reconnected', 'disconnected'
|
|
2503
|
+
* @returns {Object} Service registration with .stop(), .isRunning(), .isConnected()
|
|
1635
2504
|
*/
|
|
1636
|
-
serve: (projectId, serviceInfo, onServiceRequest, extras) =>
|
|
1637
|
-
serve(this, projectId, serviceInfo, onServiceRequest, extras),
|
|
2505
|
+
serve: (projectId, serviceInfo, onServiceRequest, extras, onStatus) =>
|
|
2506
|
+
serve(this, projectId, serviceInfo, onServiceRequest, extras, onStatus),
|
|
1638
2507
|
|
|
1639
2508
|
/**
|
|
1640
2509
|
* Request a service to perform work and await its result.
|
|
2510
|
+
*
|
|
2511
|
+
* The request outlives this call: after a timeout, an abort, or a
|
|
2512
|
+
* dropped connection the service goes on, and `attachServiceRequest`
|
|
2513
|
+
* collects the result given the request id, which `opts.onAccepted`
|
|
2514
|
+
* receives as soon as the server has taken the request. Pass
|
|
2515
|
+
* `opts.requestId` (a UUID you mint) to know the id before submitting;
|
|
2516
|
+
* submitting an id that names a request you already made rejoins it
|
|
2517
|
+
* instead of starting another.
|
|
1641
2518
|
* @param {string} projectId - The UUID of the project
|
|
1642
2519
|
* @param {string} serviceId - The ID of the service to request
|
|
1643
2520
|
* @param {any} data - The request data
|
|
1644
|
-
* @param {number} [timeout] - Timeout in
|
|
2521
|
+
* @param {number} [timeout=10000] - Timeout in ms
|
|
1645
2522
|
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
1646
|
-
* @
|
|
2523
|
+
* @param {AbortSignal} [signal] - Abort to stop waiting; rejects with an AbortError
|
|
2524
|
+
* @param {Object} [opts] - {requestId, onAccepted}
|
|
2525
|
+
* @returns {Promise<any>} The service's result
|
|
2526
|
+
*/
|
|
2527
|
+
requestService: (
|
|
2528
|
+
projectId,
|
|
2529
|
+
serviceId,
|
|
2530
|
+
data,
|
|
2531
|
+
timeout,
|
|
2532
|
+
onProgress,
|
|
2533
|
+
signal,
|
|
2534
|
+
opts,
|
|
2535
|
+
) =>
|
|
2536
|
+
requestService(
|
|
2537
|
+
this,
|
|
2538
|
+
projectId,
|
|
2539
|
+
serviceId,
|
|
2540
|
+
data,
|
|
2541
|
+
timeout,
|
|
2542
|
+
onProgress,
|
|
2543
|
+
signal,
|
|
2544
|
+
opts,
|
|
2545
|
+
),
|
|
2546
|
+
|
|
2547
|
+
/**
|
|
2548
|
+
* Rejoin a service request made earlier and await its result: the
|
|
2549
|
+
* latest progress is replayed, then the result comes, or at once if the
|
|
2550
|
+
* request already finished. Only the user who submitted it (or an
|
|
2551
|
+
* admin). Rejects with an error whose `status` is 404 when the request
|
|
2552
|
+
* is unknown or expired (a finished request's result is kept for a
|
|
2553
|
+
* while, not forever).
|
|
2554
|
+
* @param {string} projectId - The UUID of the project
|
|
2555
|
+
* @param {string} requestId - The request id
|
|
2556
|
+
* @param {number} [timeout=10000] - Timeout in ms
|
|
2557
|
+
* @param {function} [onProgress] - Called with each progress payload
|
|
2558
|
+
* @param {AbortSignal} [signal] - Abort to stop waiting
|
|
2559
|
+
* @returns {Promise<any>} The service's result
|
|
2560
|
+
*/
|
|
2561
|
+
attachServiceRequest: (
|
|
2562
|
+
projectId,
|
|
2563
|
+
requestId,
|
|
2564
|
+
timeout,
|
|
2565
|
+
onProgress,
|
|
2566
|
+
signal,
|
|
2567
|
+
) =>
|
|
2568
|
+
attachServiceRequest(
|
|
2569
|
+
this,
|
|
2570
|
+
projectId,
|
|
2571
|
+
requestId,
|
|
2572
|
+
timeout,
|
|
2573
|
+
onProgress,
|
|
2574
|
+
signal,
|
|
2575
|
+
),
|
|
2576
|
+
|
|
2577
|
+
/**
|
|
2578
|
+
* Ask the service to stop a request made earlier. The request still
|
|
2579
|
+
* ends with whatever the service then reports, on the stream of whoever
|
|
2580
|
+
* is awaiting it. Rejects with 404 if unknown or expired, 409 once
|
|
2581
|
+
* finished.
|
|
2582
|
+
* @param {string} projectId - The UUID of the project
|
|
2583
|
+
* @param {string} requestId - The request id
|
|
2584
|
+
* @returns {Promise<void>}
|
|
1647
2585
|
*/
|
|
1648
|
-
|
|
1649
|
-
|
|
2586
|
+
cancelServiceRequest: (projectId, requestId) =>
|
|
2587
|
+
cancelServiceRequest(this, projectId, requestId),
|
|
1650
2588
|
};
|
|
1651
2589
|
|
|
1652
2590
|
/**
|
|
@@ -1684,8 +2622,18 @@ class PlaidClient {
|
|
|
1684
2622
|
* For 'count': {return: 'count', count}. Entity cells are full entity objects
|
|
1685
2623
|
* (same shape as the GET endpoints).
|
|
1686
2624
|
*/
|
|
2625
|
+
// bypassBatch: a query is a read. `client.batched()` flips one flag on the
|
|
2626
|
+
// whole client, so a query issued by unrelated code while a batch is open
|
|
2627
|
+
// (the IGT island fetching precedent while a document reconciles) used to
|
|
2628
|
+
// be queued into it: the caller got `{batched: true}` instead of results,
|
|
2629
|
+
// and the server ran the query against the batch's tx Connection, which
|
|
2630
|
+
// 500s and rolls back every write in that batch.
|
|
1687
2631
|
this.query = (body, auditMessage) =>
|
|
1688
|
-
this._request(
|
|
2632
|
+
this._request("POST", "/api/v1/query", {
|
|
2633
|
+
auditMessage,
|
|
2634
|
+
body,
|
|
2635
|
+
bypassBatch: true,
|
|
2636
|
+
});
|
|
1689
2637
|
|
|
1690
2638
|
// Logical-operation groups (audit-log grouping). There is no create: a
|
|
1691
2639
|
// group row is made lazily by the first write carrying `?group-id=`
|
|
@@ -1695,15 +2643,193 @@ class PlaidClient {
|
|
|
1695
2643
|
* Get a logical-operation group (its label + creator).
|
|
1696
2644
|
* @param {string} id - The group id
|
|
1697
2645
|
*/
|
|
1698
|
-
get: (id) =>
|
|
1699
|
-
this._request('GET', `/api/v1/operation-groups/${id}`),
|
|
2646
|
+
get: (id) => this._request("GET", `/api/v1/operation-groups/${id}`),
|
|
1700
2647
|
/**
|
|
1701
2648
|
* Relabel a logical-operation group after the fact. Owner or admin only.
|
|
1702
2649
|
* @param {string} id - The group id
|
|
1703
2650
|
* @param {string|null} message - The new label
|
|
1704
2651
|
*/
|
|
1705
2652
|
update: (id, message) =>
|
|
1706
|
-
this._request(
|
|
2653
|
+
this._request("PATCH", `/api/v1/operation-groups/${id}`, {
|
|
2654
|
+
body: { message },
|
|
2655
|
+
}),
|
|
2656
|
+
};
|
|
2657
|
+
|
|
2658
|
+
this.comments = {
|
|
2659
|
+
/**
|
|
2660
|
+
* Post a comment on an entity. Requires write access to the entity's
|
|
2661
|
+
* project, or to the vocabulary for a vocab-item; the author is the
|
|
2662
|
+
* authenticated caller. Comments are not audited and do not bump the
|
|
2663
|
+
* document version.
|
|
2664
|
+
*
|
|
2665
|
+
* A comment outlives its anchor: deleting the entity does not delete
|
|
2666
|
+
* the comment. `anchorLabel` is the caption shown once that happens
|
|
2667
|
+
* ("Gloss of ktab, sentence 4"), so pass what the comment is about.
|
|
2668
|
+
* @param {'document'|'text'|'token'|'span'|'relation'|'vocab-item'} entityType - What kind of thing is being commented on
|
|
2669
|
+
* @param {string} entityId - The commented entity's id
|
|
2670
|
+
* @param {string} body - The comment text (1..10000 characters)
|
|
2671
|
+
* @param {object} [opts]
|
|
2672
|
+
* @param {string} [opts.anchorLabel] - What the comment is about, in words (at most 200 characters)
|
|
2673
|
+
*/
|
|
2674
|
+
create: (entityType, entityId, body, { anchorLabel } = {}) =>
|
|
2675
|
+
this._request("POST", "/api/v1/comments", {
|
|
2676
|
+
body: bodyOf({
|
|
2677
|
+
"entity-type": entityType,
|
|
2678
|
+
"entity-id": entityId,
|
|
2679
|
+
body,
|
|
2680
|
+
"anchor-label": anchorLabel,
|
|
2681
|
+
}),
|
|
2682
|
+
}),
|
|
2683
|
+
/**
|
|
2684
|
+
* Read one comment.
|
|
2685
|
+
* @param {string} id - The comment id
|
|
2686
|
+
*/
|
|
2687
|
+
get: (id) => this._request("GET", `/api/v1/comments/${id}`),
|
|
2688
|
+
/**
|
|
2689
|
+
* Edit a comment's body. Only the comment's AUTHOR may do this - not
|
|
2690
|
+
* maintainers, not admins. Sets `edited` on the comment.
|
|
2691
|
+
* @param {string} id - The comment id
|
|
2692
|
+
* @param {string} body - The replacement text
|
|
2693
|
+
*/
|
|
2694
|
+
update: (id, body) =>
|
|
2695
|
+
this._request("PATCH", `/api/v1/comments/${id}`, { body: { body } }),
|
|
2696
|
+
/**
|
|
2697
|
+
* Delete a comment. The author may delete their own; a project
|
|
2698
|
+
* maintainer (or admin) may delete any.
|
|
2699
|
+
* @param {string} id - The comment id
|
|
2700
|
+
*/
|
|
2701
|
+
delete: (id) => this._request("DELETE", `/api/v1/comments/${id}`),
|
|
2702
|
+
/**
|
|
2703
|
+
* List comments in a project, oldest first. Transparently follows
|
|
2704
|
+
* pagination cursors and returns the full flat array.
|
|
2705
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching - use listPage() for a single page in a batch.
|
|
2706
|
+
* @param {string} projectId - The project to read
|
|
2707
|
+
* @param {object} [filters] - Narrow the scope
|
|
2708
|
+
* @param {string} [filters.documentId] - Every comment anywhere in one document
|
|
2709
|
+
* @param {string} [filters.entityType] - With entityId, one entity's thread
|
|
2710
|
+
* @param {string} [filters.entityId] - With entityType, one entity's thread
|
|
2711
|
+
*/
|
|
2712
|
+
list: (projectId, { documentId, entityType, entityId } = {}) =>
|
|
2713
|
+
listAll(this, `/api/v1/projects/${projectId}/comments`, {
|
|
2714
|
+
query: {
|
|
2715
|
+
"document-id": documentId,
|
|
2716
|
+
"entity-type": entityType,
|
|
2717
|
+
"entity-id": entityId,
|
|
2718
|
+
},
|
|
2719
|
+
}),
|
|
2720
|
+
/**
|
|
2721
|
+
* Fetch a single page of a project's comments.
|
|
2722
|
+
* @param {string} projectId - The project to read
|
|
2723
|
+
* @param {object} [opts]
|
|
2724
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
2725
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
2726
|
+
* @param {string} [opts.documentId] - Every comment anywhere in one document
|
|
2727
|
+
* @param {string} [opts.entityType] - With entityId, one entity's thread
|
|
2728
|
+
* @param {string} [opts.entityId] - With entityType, one entity's thread
|
|
2729
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2730
|
+
*/
|
|
2731
|
+
listPage: (
|
|
2732
|
+
projectId,
|
|
2733
|
+
{ limit, cursor, documentId, entityType, entityId } = {},
|
|
2734
|
+
) =>
|
|
2735
|
+
listPage(this, `/api/v1/projects/${projectId}/comments`, {
|
|
2736
|
+
limit,
|
|
2737
|
+
cursor,
|
|
2738
|
+
query: {
|
|
2739
|
+
"document-id": documentId,
|
|
2740
|
+
"entity-type": entityType,
|
|
2741
|
+
"entity-id": entityId,
|
|
2742
|
+
},
|
|
2743
|
+
}),
|
|
2744
|
+
/**
|
|
2745
|
+
* Async-iterate a project's comments page by page; yields each page's entries array.
|
|
2746
|
+
* 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.
|
|
2747
|
+
* @param {string} projectId - The project to read
|
|
2748
|
+
* @param {object} [opts]
|
|
2749
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
2750
|
+
* @param {string} [opts.documentId] - Every comment anywhere in one document
|
|
2751
|
+
* @param {string} [opts.entityType] - With entityId, one entity's thread
|
|
2752
|
+
* @param {string} [opts.entityId] - With entityType, one entity's thread
|
|
2753
|
+
* @returns {AsyncGenerator<Array>}
|
|
2754
|
+
*/
|
|
2755
|
+
iterPages: (
|
|
2756
|
+
projectId,
|
|
2757
|
+
{ pageSize, documentId, entityType, entityId } = {},
|
|
2758
|
+
) =>
|
|
2759
|
+
iterPages(this, `/api/v1/projects/${projectId}/comments`, {
|
|
2760
|
+
pageSize,
|
|
2761
|
+
query: {
|
|
2762
|
+
"document-id": documentId,
|
|
2763
|
+
"entity-type": entityType,
|
|
2764
|
+
"entity-id": entityId,
|
|
2765
|
+
},
|
|
2766
|
+
}),
|
|
2767
|
+
/**
|
|
2768
|
+
* Comment counts per entity, as an `{entityId: n}` map, over the same
|
|
2769
|
+
* scope and filters as `list`. One cheap request paints a comment
|
|
2770
|
+
* indicator on every annotated item in a document without paging
|
|
2771
|
+
* through the bodies.
|
|
2772
|
+
*
|
|
2773
|
+
* The response is NOT key-transformed: its keys are entity ids, and
|
|
2774
|
+
* camelCasing would mangle the hyphens in a UUID.
|
|
2775
|
+
* @param {string} projectId - The project to read
|
|
2776
|
+
* @param {object} [filters] - Same filters as list()
|
|
2777
|
+
* @returns {Promise<Object<string, number>>}
|
|
2778
|
+
*/
|
|
2779
|
+
counts: (projectId, { documentId, entityType, entityId } = {}) =>
|
|
2780
|
+
this._request("GET", `/api/v1/projects/${projectId}/comments/counts`, {
|
|
2781
|
+
queryParams: {
|
|
2782
|
+
"document-id": documentId,
|
|
2783
|
+
"entity-type": entityType,
|
|
2784
|
+
"entity-id": entityId,
|
|
2785
|
+
},
|
|
2786
|
+
skipResponseTransform: true,
|
|
2787
|
+
}),
|
|
2788
|
+
/**
|
|
2789
|
+
* List the comments on a vocabulary's entries, oldest first. Requires
|
|
2790
|
+
* read access to the vocabulary. Transparently follows pagination
|
|
2791
|
+
* cursors and returns the full flat array.
|
|
2792
|
+
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching - use listInVocabPage() for a single page in a batch.
|
|
2793
|
+
* @param {string} vocabId - The vocab layer to read
|
|
2794
|
+
* @param {object} [filters]
|
|
2795
|
+
* @param {string} [filters.entityId] - One entry's thread
|
|
2796
|
+
*/
|
|
2797
|
+
listInVocab: (vocabId, { entityId } = {}) =>
|
|
2798
|
+
listAll(this, `/api/v1/vocab-layers/${vocabId}/comments`, {
|
|
2799
|
+
query: { "entity-id": entityId },
|
|
2800
|
+
}),
|
|
2801
|
+
/**
|
|
2802
|
+
* Fetch a single page of a vocabulary's comments.
|
|
2803
|
+
* @param {string} vocabId - The vocab layer to read
|
|
2804
|
+
* @param {object} [opts]
|
|
2805
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
2806
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
2807
|
+
* @param {string} [opts.entityId] - One entry's thread
|
|
2808
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2809
|
+
*/
|
|
2810
|
+
listInVocabPage: (vocabId, { limit, cursor, entityId } = {}) =>
|
|
2811
|
+
listPage(this, `/api/v1/vocab-layers/${vocabId}/comments`, {
|
|
2812
|
+
limit,
|
|
2813
|
+
cursor,
|
|
2814
|
+
query: { "entity-id": entityId },
|
|
2815
|
+
}),
|
|
2816
|
+
/**
|
|
2817
|
+
* Comment counts per entry of a vocabulary, as an `{entityId: n}` map.
|
|
2818
|
+
* The response is NOT key-transformed: its keys are entry ids.
|
|
2819
|
+
* @param {string} vocabId - The vocab layer to read
|
|
2820
|
+
* @param {object} [filters]
|
|
2821
|
+
* @param {string} [filters.entityId] - One entry only
|
|
2822
|
+
* @returns {Promise<Object<string, number>>}
|
|
2823
|
+
*/
|
|
2824
|
+
countsInVocab: (vocabId, { entityId } = {}) =>
|
|
2825
|
+
this._request(
|
|
2826
|
+
"GET",
|
|
2827
|
+
`/api/v1/vocab-layers/${vocabId}/comments/counts`,
|
|
2828
|
+
{
|
|
2829
|
+
queryParams: { "entity-id": entityId },
|
|
2830
|
+
skipResponseTransform: true,
|
|
2831
|
+
},
|
|
2832
|
+
),
|
|
1707
2833
|
};
|
|
1708
2834
|
}
|
|
1709
2835
|
|
|
@@ -1811,7 +2937,9 @@ class PlaidClient {
|
|
|
1811
2937
|
async withOperation(message, fn) {
|
|
1812
2938
|
this.beginOperation(message);
|
|
1813
2939
|
const group = this.operationGroup;
|
|
1814
|
-
const setMessage = (msg) => {
|
|
2940
|
+
const setMessage = (msg) => {
|
|
2941
|
+
if (group.depth === 1) group.refined = msg;
|
|
2942
|
+
};
|
|
1815
2943
|
try {
|
|
1816
2944
|
return await fn(setMessage);
|
|
1817
2945
|
} finally {
|
|
@@ -1835,7 +2963,7 @@ class PlaidClient {
|
|
|
1835
2963
|
*/
|
|
1836
2964
|
async submitBatch() {
|
|
1837
2965
|
if (!this.isBatching) {
|
|
1838
|
-
throw new Error(
|
|
2966
|
+
throw new Error("No active batch. Call beginBatch() first.");
|
|
1839
2967
|
}
|
|
1840
2968
|
|
|
1841
2969
|
if (this.batchOperations.length === 0) {
|
|
@@ -1844,55 +2972,86 @@ class PlaidClient {
|
|
|
1844
2972
|
}
|
|
1845
2973
|
|
|
1846
2974
|
try {
|
|
1847
|
-
|
|
1848
|
-
const
|
|
2975
|
+
const url = `${this.baseUrl}/api/v1/batch`;
|
|
2976
|
+
const ops = this.batchOperations.map((op) => ({
|
|
1849
2977
|
path: op.path,
|
|
1850
2978
|
method: op.method.toUpperCase(),
|
|
1851
2979
|
...(op.body && { body: op.body }),
|
|
1852
2980
|
}));
|
|
2981
|
+
// The server caps a batch at MAX_BATCH_OPS so one transaction cannot
|
|
2982
|
+
// hold the write lock without bound. A larger batch goes as consecutive
|
|
2983
|
+
// requests, results concatenated in queue order: it could not have been
|
|
2984
|
+
// one transaction anyway, and a repair or bulk edit over a big document
|
|
2985
|
+
// must not fail on its size alone.
|
|
2986
|
+
const results = [];
|
|
2987
|
+
for (let i = 0; i < ops.length; i += MAX_BATCH_OPS) {
|
|
2988
|
+
results.push(
|
|
2989
|
+
...(await this._postBatch(url, ops.slice(i, i + MAX_BATCH_OPS))),
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
return results;
|
|
2993
|
+
} finally {
|
|
2994
|
+
this.isBatching = false;
|
|
2995
|
+
this.batchOperations = [];
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
1853
2998
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
|
|
2999
|
+
/** POST one batch request (at most MAX_BATCH_OPS operations); resolves to its transformed results. */
|
|
3000
|
+
async _postBatch(url, body) {
|
|
3001
|
+
const fetchOptions = {
|
|
3002
|
+
method: "POST",
|
|
3003
|
+
headers: {
|
|
3004
|
+
Authorization: `Bearer ${this.token}`,
|
|
3005
|
+
"Content-Type": "application/json",
|
|
3006
|
+
},
|
|
3007
|
+
body: JSON.stringify(body),
|
|
3008
|
+
};
|
|
1864
3009
|
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
3010
|
+
try {
|
|
3011
|
+
// Retry a 503: the batch is atomic, so a refused one wrote nothing and
|
|
3012
|
+
// repeating it is safe. A fresh timeout signal per attempt — an
|
|
3013
|
+
// AbortSignal.timeout stays aborted once it fires.
|
|
3014
|
+
const response = await retryWhileBusy(async () => {
|
|
3015
|
+
const signal = timeoutSignal(this.batchTimeout);
|
|
3016
|
+
const res = await fetch(
|
|
3017
|
+
url,
|
|
3018
|
+
signal ? { ...fetchOptions, signal } : fetchOptions,
|
|
3019
|
+
);
|
|
3020
|
+
if (!res.ok) {
|
|
3021
|
+
throw makeHttpError(res, await parseErrorBody(res), url, "POST");
|
|
1869
3022
|
}
|
|
3023
|
+
return res;
|
|
3024
|
+
});
|
|
1870
3025
|
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
3026
|
+
const results = await response.json();
|
|
3027
|
+
|
|
3028
|
+
// Extract document versions from each batch response
|
|
3029
|
+
for (const result of results) {
|
|
3030
|
+
if (result.headers && result.headers["X-Document-Versions"]) {
|
|
3031
|
+
try {
|
|
3032
|
+
const versionsMap = JSON.parse(
|
|
3033
|
+
result.headers["X-Document-Versions"],
|
|
3034
|
+
);
|
|
3035
|
+
if (typeof versionsMap === "object" && versionsMap !== null) {
|
|
3036
|
+
// Clone once per response, then merge — not once per entry.
|
|
3037
|
+
this.documentVersions = {
|
|
3038
|
+
...this.documentVersions,
|
|
3039
|
+
...versionsMap,
|
|
3040
|
+
};
|
|
1884
3041
|
}
|
|
3042
|
+
} catch (e) {
|
|
3043
|
+
console.warn(
|
|
3044
|
+
"Failed to parse document versions header from batch response:",
|
|
3045
|
+
e,
|
|
3046
|
+
);
|
|
1885
3047
|
}
|
|
1886
3048
|
}
|
|
1887
|
-
|
|
1888
|
-
return results.map(result => transformResponse(result));
|
|
1889
|
-
} catch (error) {
|
|
1890
|
-
if (error.status) throw error;
|
|
1891
|
-
throw makeNetworkError(error, url, 'POST');
|
|
1892
3049
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
3050
|
+
|
|
3051
|
+
return results.map((result) => transformResponse(result));
|
|
3052
|
+
} catch (error) {
|
|
3053
|
+
if (error.status) throw error;
|
|
3054
|
+
throw makeNetworkError(error, url, "POST");
|
|
1896
3055
|
}
|
|
1897
3056
|
}
|
|
1898
3057
|
|
|
@@ -1951,6 +3110,27 @@ class PlaidClient {
|
|
|
1951
3110
|
* @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
|
|
1952
3111
|
* @returns {Promise<PlaidClient>} - Authenticated client instance
|
|
1953
3112
|
*/
|
|
3113
|
+
/**
|
|
3114
|
+
* Liveness, version and database size, with NO authentication and no client
|
|
3115
|
+
* instance — for a launcher or status page checking whether a server is up
|
|
3116
|
+
* before anyone logs in.
|
|
3117
|
+
* @param {string} baseUrl - The API base URL
|
|
3118
|
+
* @param {object} [options]
|
|
3119
|
+
*/
|
|
3120
|
+
static async health(baseUrl, options = {}) {
|
|
3121
|
+
return anonymousGet(baseUrl, "/health", options);
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
/**
|
|
3125
|
+
* The limits this server enforces, with NO authentication and no client
|
|
3126
|
+
* instance. See the instance method for the shape.
|
|
3127
|
+
* @param {string} baseUrl - The API base URL
|
|
3128
|
+
* @param {object} [options]
|
|
3129
|
+
*/
|
|
3130
|
+
static async info(baseUrl, options = {}) {
|
|
3131
|
+
return anonymousGet(baseUrl, "/api/v1/info", options);
|
|
3132
|
+
}
|
|
3133
|
+
|
|
1954
3134
|
/**
|
|
1955
3135
|
* Build the link to hand someone for an invite code. The server never sees
|
|
1956
3136
|
* an app URL, so the app that minted the invite is the one that names it.
|
|
@@ -1961,7 +3141,7 @@ class PlaidClient {
|
|
|
1961
3141
|
* @returns {string}
|
|
1962
3142
|
*/
|
|
1963
3143
|
static inviteUrl(appUrl, code) {
|
|
1964
|
-
return `${appUrl.replace(/#.*$/,
|
|
3144
|
+
return `${appUrl.replace(/#.*$/, "").replace(/\/$/, "")}/#/invite/${encodeURIComponent(code)}`;
|
|
1965
3145
|
}
|
|
1966
3146
|
|
|
1967
3147
|
/**
|
|
@@ -1975,63 +3155,85 @@ class PlaidClient {
|
|
|
1975
3155
|
* @param {string} baseUrl - The API base URL
|
|
1976
3156
|
* @param {string} code - The invite code
|
|
1977
3157
|
* @param {object} [options]
|
|
1978
|
-
* @returns {Promise<{kind: string, status: string, expiresAt: string, projectName?: string, projectRole?: string,
|
|
3158
|
+
* @returns {Promise<{kind: string, status: string, expiresAt: string, projectName?: string, projectRole?: string, email?: string, grantAdmin: boolean}>}
|
|
1979
3159
|
*/
|
|
1980
3160
|
static async lookupInvite(baseUrl, code, options = {}) {
|
|
1981
|
-
return anonymousPost(baseUrl,
|
|
3161
|
+
return anonymousPost(baseUrl, "/api/v1/invites/lookup", { code }, options);
|
|
1982
3162
|
}
|
|
1983
3163
|
|
|
1984
3164
|
/**
|
|
1985
3165
|
* Redeem an invite code, with NO authentication.
|
|
1986
3166
|
*
|
|
1987
|
-
* For a signup invite, pass `
|
|
1988
|
-
* the invite's grants are applied in the
|
|
1989
|
-
* reset link, pass `password` only.
|
|
1990
|
-
* like login() — the redeemer just
|
|
1991
|
-
* reason to send them to a login
|
|
3167
|
+
* For a signup invite, pass `email` and `password` to create the account
|
|
3168
|
+
* (and optionally `displayName`); the invite's grants are applied in the
|
|
3169
|
+
* same transaction. For a password reset link, pass `password` only.
|
|
3170
|
+
* Resolves to a logged-in client, exactly like login() — the redeemer just
|
|
3171
|
+
* chose these credentials, so there is no reason to send them to a login
|
|
3172
|
+
* form to retype them.
|
|
1992
3173
|
* @param {string} baseUrl - The API base URL
|
|
1993
3174
|
* @param {string} code - The invite code
|
|
1994
3175
|
* @param {object} credentials
|
|
1995
|
-
* @param {string} [credentials.
|
|
3176
|
+
* @param {string} [credentials.email] - The new account's email address,
|
|
3177
|
+
* which becomes its id and login (signup only)
|
|
3178
|
+
* @param {string} [credentials.displayName] - How the new user is shown in
|
|
3179
|
+
* the UI; defaults to the local part of the email (signup only)
|
|
1996
3180
|
* @param {string} credentials.password - Desired password (min 8 characters)
|
|
1997
3181
|
* @param {object} [options]
|
|
1998
3182
|
* @returns {Promise<{client: PlaidClient, userId: string, kind: string}>}
|
|
1999
3183
|
*/
|
|
2000
|
-
static async redeemInvite(
|
|
3184
|
+
static async redeemInvite(
|
|
3185
|
+
baseUrl,
|
|
3186
|
+
code,
|
|
3187
|
+
{ email, password, displayName } = {},
|
|
3188
|
+
options = {},
|
|
3189
|
+
) {
|
|
2001
3190
|
const data = await anonymousPost(
|
|
2002
|
-
baseUrl,
|
|
2003
|
-
|
|
3191
|
+
baseUrl,
|
|
3192
|
+
"/api/v1/invites/redeem",
|
|
3193
|
+
bodyOf({ code, email, password, "display-name": displayName }),
|
|
3194
|
+
options,
|
|
2004
3195
|
);
|
|
2005
3196
|
return {
|
|
2006
|
-
client: new PlaidClient(
|
|
3197
|
+
client: new PlaidClient(
|
|
3198
|
+
baseUrl.replace(/\/$/, ""),
|
|
3199
|
+
data.token || "",
|
|
3200
|
+
options,
|
|
3201
|
+
),
|
|
2007
3202
|
userId: data.userId,
|
|
2008
3203
|
kind: data.kind,
|
|
2009
3204
|
};
|
|
2010
3205
|
}
|
|
2011
3206
|
|
|
2012
3207
|
static async login(baseUrl, userId, password, options = {}) {
|
|
2013
|
-
baseUrl = baseUrl.replace(/\/$/,
|
|
3208
|
+
baseUrl = baseUrl.replace(/\/$/, "");
|
|
2014
3209
|
const url = `${baseUrl}/api/v1/login`;
|
|
2015
3210
|
try {
|
|
2016
3211
|
const fetchOptions = {
|
|
2017
|
-
method:
|
|
2018
|
-
headers: {
|
|
2019
|
-
body: JSON.stringify({
|
|
3212
|
+
method: "POST",
|
|
3213
|
+
headers: { "Content-Type": "application/json" },
|
|
3214
|
+
body: JSON.stringify({ "user-id": userId, password }),
|
|
2020
3215
|
};
|
|
2021
|
-
const signal = timeoutSignal(
|
|
3216
|
+
const signal = timeoutSignal(
|
|
3217
|
+
options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS,
|
|
3218
|
+
);
|
|
2022
3219
|
if (signal) fetchOptions.signal = signal;
|
|
2023
3220
|
|
|
2024
3221
|
const response = await fetch(url, fetchOptions);
|
|
2025
3222
|
if (!response.ok) {
|
|
2026
|
-
throw makeHttpError(
|
|
3223
|
+
throw makeHttpError(
|
|
3224
|
+
response,
|
|
3225
|
+
await parseErrorBody(response),
|
|
3226
|
+
url,
|
|
3227
|
+
"POST",
|
|
3228
|
+
);
|
|
2027
3229
|
}
|
|
2028
3230
|
|
|
2029
3231
|
const data = await response.json();
|
|
2030
|
-
const token = data.token ||
|
|
3232
|
+
const token = data.token || "";
|
|
2031
3233
|
return new PlaidClient(baseUrl, token, options);
|
|
2032
3234
|
} catch (error) {
|
|
2033
3235
|
if (error.status) throw error;
|
|
2034
|
-
throw makeNetworkError(error, url,
|
|
3236
|
+
throw makeNetworkError(error, url, "POST");
|
|
2035
3237
|
}
|
|
2036
3238
|
}
|
|
2037
3239
|
}
|
|
@@ -2041,8 +3243,22 @@ export { PlaidClient };
|
|
|
2041
3243
|
|
|
2042
3244
|
// Unicode code-point helpers for text offsets (token begin/end are code-point
|
|
2043
3245
|
// indices). See ./codepoint.js.
|
|
2044
|
-
export {
|
|
2045
|
-
|
|
3246
|
+
export {
|
|
3247
|
+
cpLength,
|
|
3248
|
+
cpSlice,
|
|
3249
|
+
cpSlicer,
|
|
3250
|
+
utf16ToCp,
|
|
3251
|
+
cpToUtf16,
|
|
3252
|
+
cpIndexOf,
|
|
3253
|
+
} from "./codepoint.js";
|
|
3254
|
+
export {
|
|
3255
|
+
PLAID_NAMESPACE,
|
|
3256
|
+
ROLE_KEY,
|
|
3257
|
+
PRESERVE_ON_SPLIT_KEY,
|
|
3258
|
+
ROLES,
|
|
3259
|
+
readRole,
|
|
3260
|
+
findByRole,
|
|
3261
|
+
} from "./roles.js";
|
|
2046
3262
|
// Service self-description helpers: filter discovered services by task, read a
|
|
2047
3263
|
// service's parameter schema/summary, and build/coerce form values. See
|
|
2048
3264
|
// ./serviceSchema.js.
|
|
@@ -2054,20 +3270,40 @@ export {
|
|
|
2054
3270
|
getServiceSummary,
|
|
2055
3271
|
buildDefaultValues,
|
|
2056
3272
|
coerceParamValues,
|
|
2057
|
-
} from
|
|
2058
|
-
//
|
|
2059
|
-
//
|
|
2060
|
-
//
|
|
2061
|
-
// "
|
|
3273
|
+
} from "./serviceSchema.js";
|
|
3274
|
+
// Cooperative cancellation for a service AUTHORED in JS: `progress()` is a
|
|
3275
|
+
// checkpoint that throws ServiceCancelled once the requester stops the
|
|
3276
|
+
// request, and `critical()` holds that off around writes. See ./services.js
|
|
3277
|
+
// and the manual, "Stopping a request".
|
|
3278
|
+
export { ServiceCancelled, createCancelScope } from "./services.js";
|
|
3279
|
+
// Provenance: the cross-app convention for who made an annotation (flat
|
|
3280
|
+
// prov/provSource/provConfirmed metadata; absence = a verifier; 'inferred' =
|
|
3281
|
+
// a machine; 'contributed' = a person whose work is reviewed), plus the
|
|
3282
|
+
// machine-writer contract. See ./provenance.js and the manual, "Provenance".
|
|
2062
3283
|
export {
|
|
2063
3284
|
PROV,
|
|
2064
3285
|
PROV_STATES,
|
|
2065
3286
|
PROV_CONFIRMED,
|
|
3287
|
+
PROVENANCE_KEYS,
|
|
2066
3288
|
stampInferred,
|
|
2067
3289
|
confirmedInferred,
|
|
3290
|
+
stampContributed,
|
|
2068
3291
|
provState,
|
|
3292
|
+
provOrigin,
|
|
2069
3293
|
isMachine,
|
|
2070
3294
|
isProtected,
|
|
3295
|
+
needsReview,
|
|
2071
3296
|
verifyOnEdit,
|
|
3297
|
+
contributeOnEdit,
|
|
3298
|
+
mergeMetadata,
|
|
2072
3299
|
serviceSource,
|
|
2073
|
-
|
|
3300
|
+
userSource,
|
|
3301
|
+
REVIEW_KEY,
|
|
3302
|
+
PROJECT_ROLES,
|
|
3303
|
+
readReview,
|
|
3304
|
+
projectRole,
|
|
3305
|
+
isReviewed,
|
|
3306
|
+
withReviewedUser,
|
|
3307
|
+
writerPolicy,
|
|
3308
|
+
} from "./provenance.js";
|
|
3309
|
+
export { MAX_BATCH_OPS };
|