@larc-iu/plaid-client 0.2.0-alpha.14 → 0.2.0-alpha.17
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 +373 -79
- package/package.json +1 -1
- package/src/created.js +29 -0
- package/src/http.js +230 -116
- package/src/index.js +551 -371
- package/src/metadataOps.js +117 -0
- package/src/pagination.js +3 -7
- package/src/provenance.js +2 -16
- package/src/serviceSchema.js +27 -1
- package/src/services.js +56 -27
- package/src/sse.js +29 -3
- package/src/transforms.js +7 -1
package/src/index.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { transformRequest, transformResponse } from "./transforms.js";
|
|
6
6
|
import {
|
|
7
7
|
makeRequest,
|
|
8
|
+
queueRequest,
|
|
9
|
+
restampDocumentVersion,
|
|
8
10
|
extractDocumentVersions,
|
|
9
11
|
parseErrorBody,
|
|
10
12
|
makeHttpError,
|
|
@@ -117,6 +119,93 @@ async function anonymousGet(baseUrl, path, options = {}) {
|
|
|
117
119
|
*/
|
|
118
120
|
const MAX_BATCH_OPS = 1000;
|
|
119
121
|
|
|
122
|
+
/**
|
|
123
|
+
* A batch is the client with a different `_request` (see `queueRequest`) and
|
|
124
|
+
* its own copy of the bundles. Everything else resolves to the client through
|
|
125
|
+
* the prototype: the token and base URL, strict mode and the document versions
|
|
126
|
+
* it tracks, the open logical operation.
|
|
127
|
+
*/
|
|
128
|
+
function openBatch(client) {
|
|
129
|
+
const batch = Object.create(client);
|
|
130
|
+
batch.client = client;
|
|
131
|
+
batch.operations = [];
|
|
132
|
+
// The document each queued op's strict-mode stamp is for (null for none),
|
|
133
|
+
// index for index with `operations`.
|
|
134
|
+
batch.stampedDocuments = [];
|
|
135
|
+
batch.open = true;
|
|
136
|
+
batch._request = (method, path, options = {}) =>
|
|
137
|
+
queueRequest(batch, method, path, options);
|
|
138
|
+
batch.batch = () => {
|
|
139
|
+
throw new Error("A batch is not nestable: queue on the batch you have");
|
|
140
|
+
};
|
|
141
|
+
batch.batched = batch.batch;
|
|
142
|
+
batch.submit = () => submitBatch(batch);
|
|
143
|
+
batch.abort = () => {
|
|
144
|
+
batch.operations = [];
|
|
145
|
+
batch.stampedDocuments = [];
|
|
146
|
+
batch.open = false;
|
|
147
|
+
};
|
|
148
|
+
batch._installResources();
|
|
149
|
+
return batch;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function submitBatch(batch) {
|
|
153
|
+
if (!batch.open) {
|
|
154
|
+
throw new Error("This batch was already submitted or aborted");
|
|
155
|
+
}
|
|
156
|
+
batch.open = false;
|
|
157
|
+
const ops = batch.operations;
|
|
158
|
+
const stamps = batch.stampedDocuments;
|
|
159
|
+
batch.operations = [];
|
|
160
|
+
batch.stampedDocuments = [];
|
|
161
|
+
if (ops.length === 0) return [];
|
|
162
|
+
const client = batch.client;
|
|
163
|
+
const url = `${client.baseUrl}/api/v1/batch`;
|
|
164
|
+
// The server caps a batch at MAX_BATCH_OPS so one transaction cannot hold
|
|
165
|
+
// the write lock without bound. A larger batch goes as consecutive
|
|
166
|
+
// requests, results concatenated in queue order: it could not have been one
|
|
167
|
+
// transaction anyway, and a repair or bulk edit over a big document must
|
|
168
|
+
// not fail on its size alone. Past the first request, a strict-mode stamp
|
|
169
|
+
// claims the version the requests before it left (learned from their
|
|
170
|
+
// results), not the one the document had when the op was queued, which the
|
|
171
|
+
// first request has already moved on.
|
|
172
|
+
//
|
|
173
|
+
// Each request is atomic, the whole is not: a failure leaves the requests
|
|
174
|
+
// before it saved. The error says so, as `committed` (how many operations
|
|
175
|
+
// were saved) and `committedResults` (their results, in queue order), so a
|
|
176
|
+
// caller can tell a partial write from a batch that saved nothing. The
|
|
177
|
+
// failed request itself counts as unsaved, even when its answer was lost.
|
|
178
|
+
const results = [];
|
|
179
|
+
try {
|
|
180
|
+
await sendChunks(client, url, ops, stamps, results);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error && typeof error === "object") {
|
|
183
|
+
error.committed = results.length;
|
|
184
|
+
error.committedResults = results;
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
return results;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function sendChunks(client, url, ops, stamps, results) {
|
|
192
|
+
for (let i = 0; i < ops.length; i += MAX_BATCH_OPS) {
|
|
193
|
+
let chunk = ops.slice(i, i + MAX_BATCH_OPS);
|
|
194
|
+
if (i > 0) {
|
|
195
|
+
chunk = chunk.map((op, j) => {
|
|
196
|
+
const docId = stamps[i + j];
|
|
197
|
+
if (!docId) return op;
|
|
198
|
+
const path = restampDocumentVersion(
|
|
199
|
+
op.path,
|
|
200
|
+
client.documentVersions[docId],
|
|
201
|
+
);
|
|
202
|
+
return { ...op, path };
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
results.push(...(await client._postBatch(url, chunk)));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
120
209
|
class PlaidClient {
|
|
121
210
|
/**
|
|
122
211
|
* Create a new PlaidClient instance
|
|
@@ -142,8 +231,6 @@ class PlaidClient {
|
|
|
142
231
|
: options.timeout !== undefined
|
|
143
232
|
? options.timeout
|
|
144
233
|
: DEFAULT_BATCH_TIMEOUT_MS;
|
|
145
|
-
this.isBatching = false;
|
|
146
|
-
this.batchOperations = [];
|
|
147
234
|
this.documentVersions = {};
|
|
148
235
|
this.strictModeDocumentId = null;
|
|
149
236
|
// Set to a DocumentLockLost while a `documents.locked()` block's keep-alive
|
|
@@ -161,8 +248,16 @@ class PlaidClient {
|
|
|
161
248
|
this.onAuthError = options.onAuthError || null;
|
|
162
249
|
this._authErrorFired = false;
|
|
163
250
|
|
|
164
|
-
|
|
251
|
+
this._installResources();
|
|
252
|
+
}
|
|
165
253
|
|
|
254
|
+
/**
|
|
255
|
+
* The API bundles (`client.documents`, `client.tokens`, ...). Installed on
|
|
256
|
+
* the client, and again on every batch it opens (see `batch()`): a batch is
|
|
257
|
+
* a view of the client with its own `_request`, so the same bundles built
|
|
258
|
+
* on it queue instead of sending.
|
|
259
|
+
*/
|
|
260
|
+
_installResources() {
|
|
166
261
|
this.vocabLinks = {
|
|
167
262
|
/**
|
|
168
263
|
* Create a new vocab link between tokens and a vocab item.
|
|
@@ -205,7 +300,6 @@ class PlaidClient {
|
|
|
205
300
|
this._request("PUT", `/api/v1/vocab-links/${id}/metadata`, {
|
|
206
301
|
auditMessage,
|
|
207
302
|
rawBody: body,
|
|
208
|
-
skipResponseTransform: true,
|
|
209
303
|
}),
|
|
210
304
|
/**
|
|
211
305
|
* Remove all metadata from a vocab link.
|
|
@@ -214,29 +308,23 @@ class PlaidClient {
|
|
|
214
308
|
deleteMetadata: (id, auditMessage) =>
|
|
215
309
|
this._request("DELETE", `/api/v1/vocab-links/${id}/metadata`, {
|
|
216
310
|
auditMessage,
|
|
217
|
-
skipResponseTransform: true,
|
|
218
311
|
}),
|
|
219
312
|
/**
|
|
220
|
-
*
|
|
313
|
+
* Edit metadata for a vocab link with a list of ops applied in order, in one operation. `{op: 'set', path, value}` writes value at path, creating missing objects along it; `{op: 'delete', path}` removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
|
|
221
314
|
* @param {string} id - The resource ID
|
|
222
|
-
* @param {any} body - The metadata
|
|
315
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
223
316
|
*/
|
|
224
317
|
patchMetadata: (id, body, auditMessage) =>
|
|
225
318
|
this._request("PATCH", `/api/v1/vocab-links/${id}/metadata`, {
|
|
226
319
|
auditMessage,
|
|
227
320
|
rawBody: body,
|
|
228
|
-
skipResponseTransform: true,
|
|
229
321
|
}),
|
|
230
322
|
/**
|
|
231
323
|
* Get a vocab link by ID
|
|
232
324
|
* @param {string} id - The resource ID
|
|
233
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
234
325
|
*/
|
|
235
|
-
get: (id
|
|
236
|
-
this._request("GET", `/api/v1/vocab-links/${id}
|
|
237
|
-
bypassBatch: true,
|
|
238
|
-
queryParams: { "as-of": asOf },
|
|
239
|
-
}),
|
|
326
|
+
get: (id) =>
|
|
327
|
+
this._request("GET", `/api/v1/vocab-links/${id}`),
|
|
240
328
|
/**
|
|
241
329
|
* Delete a vocab link
|
|
242
330
|
* @param {string} id - The resource ID
|
|
@@ -247,16 +335,113 @@ class PlaidClient {
|
|
|
247
335
|
|
|
248
336
|
this.vocabLayers = {
|
|
249
337
|
/**
|
|
250
|
-
* Get a vocab layer by ID
|
|
338
|
+
* Get a vocab layer by ID. With `asOf`, the vocabulary as it was at that
|
|
339
|
+
* instant, read from its history: the same shape as the live read, its
|
|
340
|
+
* entries with `includeItems`. A vocabulary that did not exist then is a
|
|
341
|
+
* 404, a malformed or pruned time a 400.
|
|
251
342
|
* @param {string} id - The resource ID
|
|
252
343
|
* @param {boolean} [includeItems] - Include vocab items
|
|
253
|
-
* @param {string} [asOf] -
|
|
344
|
+
* @param {string} [asOf] - The moment to read at (ISO-8601 instant)
|
|
254
345
|
*/
|
|
255
346
|
get: (id, includeItems, asOf) =>
|
|
256
347
|
this._request("GET", `/api/v1/vocab-layers/${id}`, {
|
|
257
|
-
bypassBatch: true,
|
|
258
348
|
queryParams: { "include-items": includeItems, "as-of": asOf },
|
|
259
349
|
}),
|
|
350
|
+
/**
|
|
351
|
+
* One entry of the vocabulary as it was at `asOf`, also when it has
|
|
352
|
+
* been deleted since. The same shape as `vocabItems.get`. A 404 when
|
|
353
|
+
* the entry was not in this vocabulary at that time.
|
|
354
|
+
* @param {string} id - The vocabulary ID
|
|
355
|
+
* @param {string} itemId - The entry ID
|
|
356
|
+
* @param {string} asOf - The moment to read at (ISO-8601 instant)
|
|
357
|
+
*/
|
|
358
|
+
getItemAt: (id, itemId, asOf) =>
|
|
359
|
+
this._request("GET", `/api/v1/vocab-layers/${id}/items/${itemId}`, {
|
|
360
|
+
queryParams: { "as-of": asOf },
|
|
361
|
+
}),
|
|
362
|
+
/**
|
|
363
|
+
* Get the audit log of a vocabulary: every change to it or to its
|
|
364
|
+
* entries, folded into entries as the document log is. Links are not
|
|
365
|
+
* listed, they belong to the document they annotate. Transparently
|
|
366
|
+
* follows pagination cursors and returns the full flat array.
|
|
367
|
+
* @param {string} id - The vocabulary ID
|
|
368
|
+
* @param {string} [startTime] - Start of time range
|
|
369
|
+
* @param {string} [endTime] - End of time range
|
|
370
|
+
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
371
|
+
* types, spelled as in an entry's `op/type` (e.g.
|
|
372
|
+
* `['vocab-item/delete', 'vocab-item/restore']`)
|
|
373
|
+
* @param {string} [itemId] - Only the changes that wrote this one entry,
|
|
374
|
+
* each with only its operations that did
|
|
375
|
+
*/
|
|
376
|
+
audit: (id, startTime, endTime, opTypes, itemId) =>
|
|
377
|
+
listAll(this, `/api/v1/vocab-layers/${id}/audit`, {
|
|
378
|
+
query: {
|
|
379
|
+
"start-time": startTime,
|
|
380
|
+
"end-time": endTime,
|
|
381
|
+
"op-types": opTypesParam(opTypes),
|
|
382
|
+
"item-id": itemId,
|
|
383
|
+
},
|
|
384
|
+
}),
|
|
385
|
+
/**
|
|
386
|
+
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
387
|
+
* rather than audit() wherever the caller wants the recent end of a log
|
|
388
|
+
* that may be long: audit() walks every page before it resolves.
|
|
389
|
+
* @param {string} id - The vocabulary ID
|
|
390
|
+
* @param {object} [opts]
|
|
391
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
392
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
393
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
394
|
+
* @param {string} [opts.itemId] - Only the changes that wrote this one
|
|
395
|
+
* entry, each with only its operations that did
|
|
396
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
397
|
+
*/
|
|
398
|
+
auditPage: (
|
|
399
|
+
id,
|
|
400
|
+
{ startTime, endTime, opTypes, order, limit, cursor, itemId } = {},
|
|
401
|
+
) =>
|
|
402
|
+
listPage(this, `/api/v1/vocab-layers/${id}/audit`, {
|
|
403
|
+
limit,
|
|
404
|
+
cursor,
|
|
405
|
+
query: {
|
|
406
|
+
"start-time": startTime,
|
|
407
|
+
"end-time": endTime,
|
|
408
|
+
"op-types": opTypesParam(opTypes),
|
|
409
|
+
order,
|
|
410
|
+
"item-id": itemId,
|
|
411
|
+
},
|
|
412
|
+
}),
|
|
413
|
+
/**
|
|
414
|
+
* Put one entry of the vocabulary back as it was at `asOf`, as one
|
|
415
|
+
* operation. A deleted entry comes back under its original id with its
|
|
416
|
+
* form and fields, and a living one has its form and fields set back.
|
|
417
|
+
* Links are not part of an entry: a deleted entry's links come back
|
|
418
|
+
* through each document's own `documents.restore`. Resolves to
|
|
419
|
+
* `{inserted, form, metadata, total}`, `total` 0 when nothing changes.
|
|
420
|
+
* A form set back bumps every linking document, and the new versions
|
|
421
|
+
* come back in X-Document-Versions (or, past fifty documents,
|
|
422
|
+
* X-Document-Versions-Omitted), which the client takes up as on any
|
|
423
|
+
* write. A time when the entry did not exist is a 400. Maintainers of
|
|
424
|
+
* the vocabulary only.
|
|
425
|
+
* @param {string} id - The vocabulary ID
|
|
426
|
+
* @param {string} itemId - The entry ID
|
|
427
|
+
* @param {string} asOf - The moment to go back to (ISO-8601 instant),
|
|
428
|
+
* typically a history entry's `endTime`
|
|
429
|
+
* @param {object} [options] - `{ dryRun }`: with `dryRun` true nothing
|
|
430
|
+
* is written and the summary says what would change
|
|
431
|
+
* @param {string} [auditMessage] - Custom audit message for this operation
|
|
432
|
+
*/
|
|
433
|
+
restoreItem: (id, itemId, asOf, { dryRun } = {}, auditMessage) =>
|
|
434
|
+
this._request(
|
|
435
|
+
"POST",
|
|
436
|
+
`/api/v1/vocab-layers/${id}/items/${itemId}/restore`,
|
|
437
|
+
{
|
|
438
|
+
queryParams: {
|
|
439
|
+
"as-of": asOf,
|
|
440
|
+
"dry-run": dryRun ? "true" : undefined,
|
|
441
|
+
},
|
|
442
|
+
auditMessage,
|
|
443
|
+
},
|
|
444
|
+
),
|
|
260
445
|
/**
|
|
261
446
|
* Delete a vocab layer.
|
|
262
447
|
* @param {string} id - The resource ID
|
|
@@ -301,35 +486,29 @@ class PlaidClient {
|
|
|
301
486
|
/**
|
|
302
487
|
* List all vocab layers accessible to user. Transparently follows
|
|
303
488
|
* pagination cursors and returns the full flat array.
|
|
304
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
305
489
|
*/
|
|
306
|
-
list: (
|
|
307
|
-
listAll(this, "/api/v1/vocab-layers", { query: { "as-of": asOf } }),
|
|
490
|
+
list: () => listAll(this, "/api/v1/vocab-layers"),
|
|
308
491
|
/**
|
|
309
492
|
* Fetch a single page of vocab layers.
|
|
310
493
|
* @param {object} [opts]
|
|
311
494
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
312
495
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
313
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
314
496
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
315
497
|
*/
|
|
316
|
-
listPage: ({ limit, cursor
|
|
498
|
+
listPage: ({ limit, cursor } = {}) =>
|
|
317
499
|
listPage(this, "/api/v1/vocab-layers", {
|
|
318
500
|
limit,
|
|
319
501
|
cursor,
|
|
320
|
-
query: { "as-of": asOf },
|
|
321
502
|
}),
|
|
322
503
|
/**
|
|
323
504
|
* Async-iterate vocab layers page by page; yields each page's entries array.
|
|
324
505
|
* @param {object} [opts]
|
|
325
506
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
326
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
327
507
|
* @returns {AsyncGenerator<Array>}
|
|
328
508
|
*/
|
|
329
|
-
iterPages: ({ pageSize
|
|
509
|
+
iterPages: ({ pageSize } = {}) =>
|
|
330
510
|
iterPages(this, "/api/v1/vocab-layers", {
|
|
331
511
|
pageSize,
|
|
332
|
-
query: { "as-of": asOf },
|
|
333
512
|
}),
|
|
334
513
|
/**
|
|
335
514
|
* Create a new vocab layer. Note: this also registers the user as a maintainer.
|
|
@@ -374,7 +553,6 @@ class PlaidClient {
|
|
|
374
553
|
this._request("PUT", `/api/v1/relations/${relationId}/metadata`, {
|
|
375
554
|
auditMessage,
|
|
376
555
|
rawBody: body,
|
|
377
|
-
skipResponseTransform: true,
|
|
378
556
|
}),
|
|
379
557
|
/**
|
|
380
558
|
* Remove all metadata from a relation.
|
|
@@ -383,18 +561,16 @@ class PlaidClient {
|
|
|
383
561
|
deleteMetadata: (relationId, auditMessage) =>
|
|
384
562
|
this._request("DELETE", `/api/v1/relations/${relationId}/metadata`, {
|
|
385
563
|
auditMessage,
|
|
386
|
-
skipResponseTransform: true,
|
|
387
564
|
}),
|
|
388
565
|
/**
|
|
389
|
-
*
|
|
566
|
+
* Edit metadata for a relation with a list of ops applied in order, in one operation. `{op: 'set', path, value}` writes value at path, creating missing objects along it; `{op: 'delete', path}` removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
|
|
390
567
|
* @param {string} relationId - The relation ID
|
|
391
|
-
* @param {any} body - The metadata
|
|
568
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
392
569
|
*/
|
|
393
570
|
patchMetadata: (relationId, body, auditMessage) =>
|
|
394
571
|
this._request("PATCH", `/api/v1/relations/${relationId}/metadata`, {
|
|
395
572
|
auditMessage,
|
|
396
573
|
rawBody: body,
|
|
397
|
-
skipResponseTransform: true,
|
|
398
574
|
}),
|
|
399
575
|
/**
|
|
400
576
|
* Update the target span of a relation.
|
|
@@ -409,13 +585,9 @@ class PlaidClient {
|
|
|
409
585
|
/**
|
|
410
586
|
* Get a relation by ID.
|
|
411
587
|
* @param {string} relationId - The relation ID
|
|
412
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
413
588
|
*/
|
|
414
|
-
get: (relationId
|
|
415
|
-
this._request("GET", `/api/v1/relations/${relationId}
|
|
416
|
-
bypassBatch: true,
|
|
417
|
-
queryParams: { "as-of": asOf },
|
|
418
|
-
}),
|
|
589
|
+
get: (relationId) =>
|
|
590
|
+
this._request("GET", `/api/v1/relations/${relationId}`),
|
|
419
591
|
/**
|
|
420
592
|
* Delete a relation.
|
|
421
593
|
* @param {string} relationId - The relation ID
|
|
@@ -483,7 +655,7 @@ class PlaidClient {
|
|
|
483
655
|
}),
|
|
484
656
|
/**
|
|
485
657
|
* Update many relations in a single operation: set values and/or patch metadata.
|
|
486
|
-
* @param {Array} body - Objects of the shape `{id, value?, metadata?}`. `value` is set only when the key is present (null sends JSON null); `metadata` is a
|
|
658
|
+
* @param {Array} body - Objects of the shape `{id, value?, metadata?}`. `value` is set only when the key is present (null sends JSON null); `metadata` is a list of metadata ops, as for patchMetadata. The relations may lie in several documents of one project; every document touched has its version bumped and every new version comes back in `X-Document-Versions` (past fifty documents, only their number, in `X-Document-Versions-Omitted`). A `document-version` precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update.
|
|
487
659
|
* @returns {Promise<{count: number}>} How many relations were updated.
|
|
488
660
|
*/
|
|
489
661
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -528,13 +700,9 @@ class PlaidClient {
|
|
|
528
700
|
/**
|
|
529
701
|
* Get a span layer by ID.
|
|
530
702
|
* @param {string} spanLayerId - The span layer ID
|
|
531
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
532
703
|
*/
|
|
533
|
-
get: (spanLayerId
|
|
534
|
-
this._request("GET", `/api/v1/span-layers/${spanLayerId}
|
|
535
|
-
bypassBatch: true,
|
|
536
|
-
queryParams: { "as-of": asOf },
|
|
537
|
-
}),
|
|
704
|
+
get: (spanLayerId) =>
|
|
705
|
+
this._request("GET", `/api/v1/span-layers/${spanLayerId}`),
|
|
538
706
|
/**
|
|
539
707
|
* Delete a span layer.
|
|
540
708
|
* @param {string} spanLayerId - The span layer ID
|
|
@@ -607,13 +775,9 @@ class PlaidClient {
|
|
|
607
775
|
/**
|
|
608
776
|
* Get a span by ID.
|
|
609
777
|
* @param {string} spanId - The span ID
|
|
610
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
611
778
|
*/
|
|
612
|
-
get: (spanId
|
|
613
|
-
this._request("GET", `/api/v1/spans/${spanId}
|
|
614
|
-
bypassBatch: true,
|
|
615
|
-
queryParams: { "as-of": asOf },
|
|
616
|
-
}),
|
|
779
|
+
get: (spanId) =>
|
|
780
|
+
this._request("GET", `/api/v1/spans/${spanId}`),
|
|
617
781
|
/**
|
|
618
782
|
* Delete a span.
|
|
619
783
|
* @param {string} spanId - The span ID
|
|
@@ -645,7 +809,7 @@ class PlaidClient {
|
|
|
645
809
|
this._request("DELETE", "/api/v1/spans/bulk", { auditMessage, body }),
|
|
646
810
|
/**
|
|
647
811
|
* Update many spans in a single operation: set values and/or patch metadata.
|
|
648
|
-
* @param {Array} body - Objects of the shape `{id, value?, metadata?}`. `value` is set only when the key is present (null sends JSON null); `metadata` is a
|
|
812
|
+
* @param {Array} body - Objects of the shape `{id, value?, metadata?}`. `value` is set only when the key is present (null sends JSON null); `metadata` is a list of metadata ops, as for patchMetadata. The spans may lie in several documents of one project; every document touched has its version bumped and every new version comes back in `X-Document-Versions` (past fifty documents, only their number, in `X-Document-Versions-Omitted`). A `document-version` precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update.
|
|
649
813
|
* @returns {Promise<{count: number}>} How many spans were updated.
|
|
650
814
|
*/
|
|
651
815
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -662,7 +826,6 @@ class PlaidClient {
|
|
|
662
826
|
this._request("PUT", `/api/v1/spans/${spanId}/metadata`, {
|
|
663
827
|
auditMessage,
|
|
664
828
|
rawBody: body,
|
|
665
|
-
skipResponseTransform: true,
|
|
666
829
|
}),
|
|
667
830
|
/**
|
|
668
831
|
* Remove all metadata from a span.
|
|
@@ -671,18 +834,16 @@ class PlaidClient {
|
|
|
671
834
|
deleteMetadata: (spanId, auditMessage) =>
|
|
672
835
|
this._request("DELETE", `/api/v1/spans/${spanId}/metadata`, {
|
|
673
836
|
auditMessage,
|
|
674
|
-
skipResponseTransform: true,
|
|
675
837
|
}),
|
|
676
838
|
/**
|
|
677
|
-
*
|
|
839
|
+
* Edit metadata for a span with a list of ops applied in order, in one operation. `{op: 'set', path, value}` writes value at path, creating missing objects along it; `{op: 'delete', path}` removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
|
|
678
840
|
* @param {string} spanId - The span ID
|
|
679
|
-
* @param {any} body - The metadata
|
|
841
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
680
842
|
*/
|
|
681
843
|
patchMetadata: (spanId, body, auditMessage) =>
|
|
682
844
|
this._request("PATCH", `/api/v1/spans/${spanId}/metadata`, {
|
|
683
845
|
auditMessage,
|
|
684
846
|
rawBody: body,
|
|
685
|
-
skipResponseTransform: true,
|
|
686
847
|
}),
|
|
687
848
|
};
|
|
688
849
|
|
|
@@ -698,11 +859,7 @@ class PlaidClient {
|
|
|
698
859
|
*/
|
|
699
860
|
info: () => {
|
|
700
861
|
if (!infoPromise) {
|
|
701
|
-
|
|
702
|
-
// whatever batch happens to be open on this shared client.
|
|
703
|
-
infoPromise = this._request("GET", "/api/v1/info", {
|
|
704
|
-
bypassBatch: true,
|
|
705
|
-
}).catch((err) => {
|
|
862
|
+
infoPromise = this._request("GET", "/api/v1/info").catch((err) => {
|
|
706
863
|
// A failure must not be cached: a client that starts before the
|
|
707
864
|
// server is up would never see the limits at all.
|
|
708
865
|
infoPromise = null;
|
|
@@ -722,18 +879,22 @@ class PlaidClient {
|
|
|
722
879
|
health: () => PlaidClient.health(this.baseUrl, { timeout: this.timeout }),
|
|
723
880
|
};
|
|
724
881
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
*
|
|
882
|
+
// The signed-in user's sign-ins. Signing in is `PlaidClient.login`, which
|
|
883
|
+
// makes the client.
|
|
884
|
+
this.auth = {
|
|
885
|
+
/**
|
|
886
|
+
* End every sign-in of this user on every device: each browser tab,
|
|
887
|
+
* script and service holding one of the user's sign-in tokens is
|
|
888
|
+
* refused from now on, this client included. Named API tokens are not
|
|
889
|
+
* affected (revoke one with `apiTokens.revoke`). To sign out of one tab
|
|
890
|
+
* only, discard its token instead. A signal rather than project data:
|
|
891
|
+
* made on a batch it still goes out at once, and it never joins an
|
|
892
|
+
* operation.
|
|
893
|
+
* @returns {Promise<void>}
|
|
730
894
|
*/
|
|
731
|
-
|
|
732
|
-
this._request("POST", "/api/v1/
|
|
733
|
-
|
|
734
|
-
body,
|
|
735
|
-
noBatch: true,
|
|
736
|
-
}),
|
|
895
|
+
logoutEverywhere: async () => {
|
|
896
|
+
await this._request("POST", "/api/v1/logout", { outOfBand: true });
|
|
897
|
+
},
|
|
737
898
|
};
|
|
738
899
|
|
|
739
900
|
this.texts = {
|
|
@@ -746,7 +907,6 @@ class PlaidClient {
|
|
|
746
907
|
this._request("PUT", `/api/v1/texts/${textId}/metadata`, {
|
|
747
908
|
auditMessage,
|
|
748
909
|
rawBody: body,
|
|
749
|
-
skipResponseTransform: true,
|
|
750
910
|
}),
|
|
751
911
|
/**
|
|
752
912
|
* Remove all metadata from a text.
|
|
@@ -755,18 +915,16 @@ class PlaidClient {
|
|
|
755
915
|
deleteMetadata: (textId, auditMessage) =>
|
|
756
916
|
this._request("DELETE", `/api/v1/texts/${textId}/metadata`, {
|
|
757
917
|
auditMessage,
|
|
758
|
-
skipResponseTransform: true,
|
|
759
918
|
}),
|
|
760
919
|
/**
|
|
761
|
-
*
|
|
920
|
+
* Edit metadata for a text with a list of ops applied in order, in one operation. `{op: 'set', path, value}` writes value at path, creating missing objects along it; `{op: 'delete', path}` removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
|
|
762
921
|
* @param {string} textId - The text ID
|
|
763
|
-
* @param {any} body - The metadata
|
|
922
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
764
923
|
*/
|
|
765
924
|
patchMetadata: (textId, body, auditMessage) =>
|
|
766
925
|
this._request("PATCH", `/api/v1/texts/${textId}/metadata`, {
|
|
767
926
|
auditMessage,
|
|
768
927
|
rawBody: body,
|
|
769
|
-
skipResponseTransform: true,
|
|
770
928
|
}),
|
|
771
929
|
/**
|
|
772
930
|
* Create a new text in a document's text layer. A text is a container for
|
|
@@ -789,13 +947,9 @@ class PlaidClient {
|
|
|
789
947
|
/**
|
|
790
948
|
* Get a text.
|
|
791
949
|
* @param {string} textId - The text ID
|
|
792
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
793
950
|
*/
|
|
794
|
-
get: (textId
|
|
795
|
-
this._request("GET", `/api/v1/texts/${textId}
|
|
796
|
-
bypassBatch: true,
|
|
797
|
-
queryParams: { "as-of": asOf },
|
|
798
|
-
}),
|
|
951
|
+
get: (textId) =>
|
|
952
|
+
this._request("GET", `/api/v1/texts/${textId}`),
|
|
799
953
|
/**
|
|
800
954
|
* Delete a text and all dependent data.
|
|
801
955
|
* @param {string} textId - The text ID
|
|
@@ -827,37 +981,34 @@ class PlaidClient {
|
|
|
827
981
|
* returns the full flat array. Admin-or-maintainer only.
|
|
828
982
|
* @param {object} [opts]
|
|
829
983
|
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
830
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
831
984
|
*/
|
|
832
|
-
list: ({ q
|
|
833
|
-
listAll(this, "/api/v1/users", { query: { q
|
|
985
|
+
list: ({ q } = {}) =>
|
|
986
|
+
listAll(this, "/api/v1/users", { query: { q } }),
|
|
834
987
|
/**
|
|
835
988
|
* Fetch a single page of users (optionally filtered by `q`).
|
|
836
989
|
* @param {object} [opts]
|
|
837
990
|
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
838
991
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
839
992
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
840
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
841
993
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
842
994
|
*/
|
|
843
|
-
listPage: ({ q, limit, cursor
|
|
995
|
+
listPage: ({ q, limit, cursor } = {}) =>
|
|
844
996
|
listPage(this, "/api/v1/users", {
|
|
845
997
|
limit,
|
|
846
998
|
cursor,
|
|
847
|
-
query: { q
|
|
999
|
+
query: { q },
|
|
848
1000
|
}),
|
|
849
1001
|
/**
|
|
850
1002
|
* Async-iterate users page by page; yields each page's entries array.
|
|
851
1003
|
* @param {object} [opts]
|
|
852
1004
|
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
853
1005
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
854
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
855
1006
|
* @returns {AsyncGenerator<Array>}
|
|
856
1007
|
*/
|
|
857
|
-
iterPages: ({ q, pageSize
|
|
1008
|
+
iterPages: ({ q, pageSize } = {}) =>
|
|
858
1009
|
iterPages(this, "/api/v1/users", {
|
|
859
1010
|
pageSize,
|
|
860
|
-
query: { q
|
|
1011
|
+
query: { q },
|
|
861
1012
|
}),
|
|
862
1013
|
/**
|
|
863
1014
|
* Create a new user.
|
|
@@ -875,7 +1026,9 @@ class PlaidClient {
|
|
|
875
1026
|
email,
|
|
876
1027
|
password,
|
|
877
1028
|
"is-admin": isAdmin,
|
|
878
|
-
|
|
1029
|
+
// Left out when not given (null too), so core gives the user the
|
|
1030
|
+
// local part of the email. Core refuses a null display name.
|
|
1031
|
+
"display-name": displayName ?? undefined,
|
|
879
1032
|
}),
|
|
880
1033
|
}),
|
|
881
1034
|
/**
|
|
@@ -884,18 +1037,16 @@ class PlaidClient {
|
|
|
884
1037
|
* @param {string} userId - The user ID
|
|
885
1038
|
* @param {string} [startTime] - Start of time range
|
|
886
1039
|
* @param {string} [endTime] - End of time range
|
|
887
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
888
1040
|
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
889
1041
|
* types, spelled as in an entry's `op/type` (e.g.
|
|
890
1042
|
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
891
1043
|
* one of its operations matches, carrying only the ones that did.
|
|
892
1044
|
*/
|
|
893
|
-
audit: (userId, startTime, endTime,
|
|
1045
|
+
audit: (userId, startTime, endTime, opTypes) =>
|
|
894
1046
|
listAll(this, `/api/v1/users/${userId}/audit`, {
|
|
895
1047
|
query: {
|
|
896
1048
|
"start-time": startTime,
|
|
897
1049
|
"end-time": endTime,
|
|
898
|
-
"as-of": asOf,
|
|
899
1050
|
"op-types": opTypesParam(opTypes),
|
|
900
1051
|
},
|
|
901
1052
|
}),
|
|
@@ -903,6 +1054,7 @@ class PlaidClient {
|
|
|
903
1054
|
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
904
1055
|
* rather than audit() wherever the caller wants the recent end of a log
|
|
905
1056
|
* that may be long: audit() walks every page before it resolves.
|
|
1057
|
+
* @param {string} userId - The user ID
|
|
906
1058
|
* @param {object} [opts]
|
|
907
1059
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
908
1060
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
@@ -911,7 +1063,7 @@ class PlaidClient {
|
|
|
911
1063
|
*/
|
|
912
1064
|
auditPage: (
|
|
913
1065
|
userId,
|
|
914
|
-
{ startTime, endTime,
|
|
1066
|
+
{ startTime, endTime, opTypes, order, limit, cursor } = {},
|
|
915
1067
|
) =>
|
|
916
1068
|
listPage(this, `/api/v1/users/${userId}/audit`, {
|
|
917
1069
|
limit,
|
|
@@ -919,7 +1071,6 @@ class PlaidClient {
|
|
|
919
1071
|
query: {
|
|
920
1072
|
"start-time": startTime,
|
|
921
1073
|
"end-time": endTime,
|
|
922
|
-
"as-of": asOf,
|
|
923
1074
|
"op-types": opTypesParam(opTypes),
|
|
924
1075
|
order,
|
|
925
1076
|
},
|
|
@@ -927,13 +1078,9 @@ class PlaidClient {
|
|
|
927
1078
|
/**
|
|
928
1079
|
* Get a user by ID
|
|
929
1080
|
* @param {string} id - The resource ID
|
|
930
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
931
1081
|
*/
|
|
932
|
-
get: (id
|
|
933
|
-
this._request("GET", `/api/v1/users/${id}
|
|
934
|
-
bypassBatch: true,
|
|
935
|
-
queryParams: { "as-of": asOf },
|
|
936
|
-
}),
|
|
1082
|
+
get: (id) =>
|
|
1083
|
+
this._request("GET", `/api/v1/users/${id}`),
|
|
937
1084
|
/**
|
|
938
1085
|
* Deactivate a user. Users are never hard-deleted: deactivation
|
|
939
1086
|
* rejects their logins and tokens, strips their project memberships
|
|
@@ -1001,7 +1148,6 @@ class PlaidClient {
|
|
|
1001
1148
|
*/
|
|
1002
1149
|
getAvatar: (id) =>
|
|
1003
1150
|
this._request("GET", `/api/v1/users/${id}/avatar`, {
|
|
1004
|
-
bypassBatch: true,
|
|
1005
1151
|
binaryResponse: true,
|
|
1006
1152
|
}),
|
|
1007
1153
|
/**
|
|
@@ -1059,13 +1205,6 @@ class PlaidClient {
|
|
|
1059
1205
|
* @param {number} [opts.pageSize=100] - Entries per request (1..1000)
|
|
1060
1206
|
*/
|
|
1061
1207
|
list: (userId, { prefix, pattern, includeValues, pageSize = 100 } = {}) =>
|
|
1062
|
-
// Every page carries bypassBatch (the pagination helpers set it): a
|
|
1063
|
-
// read belongs to whoever asked for it, not to whatever batch happens
|
|
1064
|
-
// to be open on this shared client. The assistant panel is app chrome
|
|
1065
|
-
// in both SPAs, so it reads this store while an import or a bulk edit
|
|
1066
|
-
// holds a batch open. Queued, the read answered `{batched: true}` (the
|
|
1067
|
-
// sidebar then threw on `.map`) and took a slot in the batch's
|
|
1068
|
-
// results, shifting every index the caller counted on.
|
|
1069
1208
|
listAll(this, `/api/v1/users/${userId}/data`, {
|
|
1070
1209
|
pageSize,
|
|
1071
1210
|
query: { prefix, pattern, "include-values": includeValues },
|
|
@@ -1113,15 +1252,13 @@ class PlaidClient {
|
|
|
1113
1252
|
* @param {string} key
|
|
1114
1253
|
*/
|
|
1115
1254
|
get: (userId, key) =>
|
|
1116
|
-
// bypassBatch for the same reason as list() above.
|
|
1117
1255
|
this._request(
|
|
1118
1256
|
"GET",
|
|
1119
1257
|
`/api/v1/users/${userId}/data/${encodeURIComponent(key)}`,
|
|
1120
|
-
{ bypassBatch: true },
|
|
1121
1258
|
),
|
|
1122
1259
|
/**
|
|
1123
1260
|
* Create or replace one private data entry. `value` is any JSON (up to
|
|
1124
|
-
* 1 MB)
|
|
1261
|
+
* 1 MB). Not audited, not batchable.
|
|
1125
1262
|
*
|
|
1126
1263
|
* The server stores it verbatim, but this client recases object keys on
|
|
1127
1264
|
* the way out and back like any other body (`myKey` <-> `my-key`), so a
|
|
@@ -1189,7 +1326,7 @@ class PlaidClient {
|
|
|
1189
1326
|
* credential and is shown ONLY here — store it immediately. API tokens
|
|
1190
1327
|
* do not expire and survive password changes / logout; revoke to kill.
|
|
1191
1328
|
* @param {string} userId - The user ID who will own the token
|
|
1192
|
-
* @param {string} name - A human label, e.g. "Stanza
|
|
1329
|
+
* @param {string} name - A human label, e.g. "Stanza parser"
|
|
1193
1330
|
* @returns {Promise<{id: string, name: string, token: string}>}
|
|
1194
1331
|
*/
|
|
1195
1332
|
create: (userId, name, auditMessage) =>
|
|
@@ -1318,8 +1455,7 @@ class PlaidClient {
|
|
|
1318
1455
|
* walks the media directory, so open it, do not poll it. Admin only.
|
|
1319
1456
|
* @returns {Promise<{version: string, jvm: object, database: object, media: object, backup: object, settings: object}>}
|
|
1320
1457
|
*/
|
|
1321
|
-
server: () =>
|
|
1322
|
-
this._request("GET", "/api/v1/admin/server", { bypassBatch: true }),
|
|
1458
|
+
server: () => this._request("GET", "/api/v1/admin/server"),
|
|
1323
1459
|
/**
|
|
1324
1460
|
* Take a database backup right now, outside the nightly schedule.
|
|
1325
1461
|
* Resolves to the backup block with `ok` reporting whether the snapshot
|
|
@@ -1327,20 +1463,17 @@ class PlaidClient {
|
|
|
1327
1463
|
* people are working. Admin only.
|
|
1328
1464
|
* @returns {Promise<{ok: boolean, directory: string, backups: Array}>}
|
|
1329
1465
|
*
|
|
1330
|
-
*
|
|
1331
|
-
*
|
|
1332
|
-
* backup taken inside somebody's open write transaction is not what the
|
|
1333
|
-
* caller asked for.
|
|
1466
|
+
* outOfBand, as every admin action on the server itself is: none of them
|
|
1467
|
+
* writes project data (see the note at the top of http.js).
|
|
1334
1468
|
*/
|
|
1335
1469
|
backup: () =>
|
|
1336
|
-
this._request("POST", "/api/v1/admin/backup", {
|
|
1470
|
+
this._request("POST", "/api/v1/admin/backup", { outOfBand: true }),
|
|
1337
1471
|
/**
|
|
1338
1472
|
* Documents currently held by an editing lock, with who holds each and
|
|
1339
1473
|
* when it expires on its own. Admin only.
|
|
1340
1474
|
* @returns {Promise<{entries: Array<{documentId: string, userId: string, expiresAt: number}>}>}
|
|
1341
1475
|
*/
|
|
1342
|
-
locks: () =>
|
|
1343
|
-
this._request("GET", "/api/v1/admin/locks", { bypassBatch: true }),
|
|
1476
|
+
locks: () => this._request("GET", "/api/v1/admin/locks"),
|
|
1344
1477
|
/**
|
|
1345
1478
|
* Drop the lock on a document whoever holds it. Idempotent. For a client
|
|
1346
1479
|
* that went away without releasing one. Admin only.
|
|
@@ -1348,7 +1481,7 @@ class PlaidClient {
|
|
|
1348
1481
|
*/
|
|
1349
1482
|
releaseLock: (documentId) =>
|
|
1350
1483
|
this._request("DELETE", `/api/v1/admin/locks/${documentId}`, {
|
|
1351
|
-
|
|
1484
|
+
outOfBand: true,
|
|
1352
1485
|
}),
|
|
1353
1486
|
/**
|
|
1354
1487
|
* Live login and invite rate-limit buckets: the address, the account
|
|
@@ -1356,10 +1489,7 @@ class PlaidClient {
|
|
|
1356
1489
|
* it is currently blocking. Admin only.
|
|
1357
1490
|
* @returns {Promise<{windowMs: number, logins: Array, ips: Array, invites: Array}>}
|
|
1358
1491
|
*/
|
|
1359
|
-
rateLimits: () =>
|
|
1360
|
-
this._request("GET", "/api/v1/admin/rate-limits", {
|
|
1361
|
-
bypassBatch: true,
|
|
1362
|
-
}),
|
|
1492
|
+
rateLimits: () => this._request("GET", "/api/v1/admin/rate-limits"),
|
|
1363
1493
|
/**
|
|
1364
1494
|
* Forget recorded rate-limit failures. With `ip`, clears that address,
|
|
1365
1495
|
* narrowed to one account with `userId`. With neither, clears every
|
|
@@ -1370,7 +1500,7 @@ class PlaidClient {
|
|
|
1370
1500
|
*/
|
|
1371
1501
|
clearRateLimits: ({ ip, userId } = {}) =>
|
|
1372
1502
|
this._request("DELETE", "/api/v1/admin/rate-limits", {
|
|
1373
|
-
|
|
1503
|
+
outOfBand: true,
|
|
1374
1504
|
queryParams: { ip, "user-id": userId },
|
|
1375
1505
|
}),
|
|
1376
1506
|
/**
|
|
@@ -1394,7 +1524,6 @@ class PlaidClient {
|
|
|
1394
1524
|
*/
|
|
1395
1525
|
logs: ({ limit, q, level, status, user, method } = {}) =>
|
|
1396
1526
|
this._request("GET", "/api/v1/admin/logs", {
|
|
1397
|
-
bypassBatch: true,
|
|
1398
1527
|
queryParams: { limit, q, level, status, user, method },
|
|
1399
1528
|
}),
|
|
1400
1529
|
/**
|
|
@@ -1408,7 +1537,6 @@ class PlaidClient {
|
|
|
1408
1537
|
*/
|
|
1409
1538
|
logFile: ({ lines } = {}) =>
|
|
1410
1539
|
this._request("GET", "/api/v1/admin/logs/file", {
|
|
1411
|
-
bypassBatch: true,
|
|
1412
1540
|
queryParams: { lines },
|
|
1413
1541
|
}),
|
|
1414
1542
|
/**
|
|
@@ -1536,7 +1664,6 @@ class PlaidClient {
|
|
|
1536
1664
|
? `/api/v1/projects/${projectId}/audit/tally`
|
|
1537
1665
|
: "/api/v1/audit/tally";
|
|
1538
1666
|
const result = await this._request("GET", path, {
|
|
1539
|
-
bypassBatch: true,
|
|
1540
1667
|
queryParams: {
|
|
1541
1668
|
"start-time": startTime,
|
|
1542
1669
|
"end-time": endTime,
|
|
@@ -1615,13 +1742,9 @@ class PlaidClient {
|
|
|
1615
1742
|
/**
|
|
1616
1743
|
* Get a token layer by ID.
|
|
1617
1744
|
* @param {string} tokenLayerId - The token layer ID
|
|
1618
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
1619
1745
|
*/
|
|
1620
|
-
get: (tokenLayerId
|
|
1621
|
-
this._request("GET", `/api/v1/token-layers/${tokenLayerId}
|
|
1622
|
-
bypassBatch: true,
|
|
1623
|
-
queryParams: { "as-of": asOf },
|
|
1624
|
-
}),
|
|
1746
|
+
get: (tokenLayerId) =>
|
|
1747
|
+
this._request("GET", `/api/v1/token-layers/${tokenLayerId}`),
|
|
1625
1748
|
/**
|
|
1626
1749
|
* Delete a token layer.
|
|
1627
1750
|
* @param {string} tokenLayerId - The token layer ID
|
|
@@ -1646,38 +1769,34 @@ class PlaidClient {
|
|
|
1646
1769
|
/**
|
|
1647
1770
|
* Check the lock status of a document.
|
|
1648
1771
|
* @param {string} documentId - The document ID
|
|
1649
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
1650
1772
|
*/
|
|
1651
|
-
checkLock: (documentId
|
|
1652
|
-
this._request("GET", `/api/v1/documents/${documentId}/lock
|
|
1653
|
-
bypassBatch: true,
|
|
1654
|
-
queryParams: { "as-of": asOf },
|
|
1655
|
-
}),
|
|
1773
|
+
checkLock: (documentId) =>
|
|
1774
|
+
this._request("GET", `/api/v1/documents/${documentId}/lock`),
|
|
1656
1775
|
/**
|
|
1657
1776
|
* Acquire or refresh a document lock.
|
|
1658
1777
|
*
|
|
1659
|
-
*
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1662
|
-
*
|
|
1663
|
-
*
|
|
1778
|
+
* outOfBand: the lock is a signal, not project data (see the note at
|
|
1779
|
+
* the top of http.js). Queued on a batch it would be taken only at
|
|
1780
|
+
* submit, after every write it was meant to guard, and until then answer
|
|
1781
|
+
* success to a caller that does not hold it and cannot see the 423
|
|
1782
|
+
* saying somebody else does.
|
|
1664
1783
|
* @param {string} documentId - The document ID
|
|
1665
1784
|
*/
|
|
1666
1785
|
acquireLock: (documentId, auditMessage) =>
|
|
1667
1786
|
this._request("POST", `/api/v1/documents/${documentId}/lock`, {
|
|
1668
1787
|
auditMessage,
|
|
1669
|
-
|
|
1788
|
+
outOfBand: true,
|
|
1670
1789
|
}),
|
|
1671
1790
|
/**
|
|
1672
|
-
* Release a document lock.
|
|
1673
|
-
*
|
|
1674
|
-
*
|
|
1791
|
+
* Release a document lock. outOfBand, for the same reason as
|
|
1792
|
+
* acquireLock: queued, the lock would be held until the batch submits,
|
|
1793
|
+
* and not released at all if it aborts.
|
|
1675
1794
|
* @param {string} documentId - The document ID
|
|
1676
1795
|
*/
|
|
1677
1796
|
releaseLock: (documentId, auditMessage) =>
|
|
1678
1797
|
this._request("DELETE", `/api/v1/documents/${documentId}/lock`, {
|
|
1679
1798
|
auditMessage,
|
|
1680
|
-
|
|
1799
|
+
outOfBand: true,
|
|
1681
1800
|
}),
|
|
1682
1801
|
/**
|
|
1683
1802
|
* Hold this document's server-enforced lock for the length of `fn`,
|
|
@@ -1708,8 +1827,8 @@ class PlaidClient {
|
|
|
1708
1827
|
* NOT re-entrant: nesting two `locked()` blocks on one document would
|
|
1709
1828
|
* release on the inner exit and leave the outer unprotected. Lock at
|
|
1710
1829
|
* exactly one level per call path. A lost lock is recorded on the
|
|
1711
|
-
* CLIENT, like
|
|
1712
|
-
*
|
|
1830
|
+
* CLIENT, like strict mode, so it stops every write the client makes
|
|
1831
|
+
* (on any batch of it too) and not only the ones this block makes.
|
|
1713
1832
|
* @param {string} documentId - The document ID
|
|
1714
1833
|
* @param {(lock: DocumentLock) => any} fn - The work to run while holding it
|
|
1715
1834
|
* @param {object} [options] - `{ keepAlive }`: renew on a timer (default true)
|
|
@@ -1724,7 +1843,6 @@ class PlaidClient {
|
|
|
1724
1843
|
*/
|
|
1725
1844
|
getMedia: (documentId) =>
|
|
1726
1845
|
this._request("GET", `/api/v1/documents/${documentId}/media`, {
|
|
1727
|
-
bypassBatch: true,
|
|
1728
1846
|
binaryResponse: true,
|
|
1729
1847
|
}),
|
|
1730
1848
|
/**
|
|
@@ -1749,8 +1867,8 @@ class PlaidClient {
|
|
|
1749
1867
|
* Delete media file for a document
|
|
1750
1868
|
* @param {string} documentId - The document ID
|
|
1751
1869
|
*/
|
|
1752
|
-
//
|
|
1753
|
-
//
|
|
1870
|
+
// The upload above is multipart and cannot be batched, but a DELETE
|
|
1871
|
+
// carries no blob, so the batch transport takes it. It is a write
|
|
1754
1872
|
// of the document's own data and queues like any other. Note that the
|
|
1755
1873
|
// file removal happens outside the server's transaction, so a batch that
|
|
1756
1874
|
// aborts after this op does not bring the file back.
|
|
@@ -1767,7 +1885,6 @@ class PlaidClient {
|
|
|
1767
1885
|
this._request("PUT", `/api/v1/documents/${documentId}/metadata`, {
|
|
1768
1886
|
auditMessage,
|
|
1769
1887
|
rawBody: body,
|
|
1770
|
-
skipResponseTransform: true,
|
|
1771
1888
|
}),
|
|
1772
1889
|
/**
|
|
1773
1890
|
* Remove all metadata from a document.
|
|
@@ -1776,18 +1893,16 @@ class PlaidClient {
|
|
|
1776
1893
|
deleteMetadata: (documentId, auditMessage) =>
|
|
1777
1894
|
this._request("DELETE", `/api/v1/documents/${documentId}/metadata`, {
|
|
1778
1895
|
auditMessage,
|
|
1779
|
-
skipResponseTransform: true,
|
|
1780
1896
|
}),
|
|
1781
1897
|
/**
|
|
1782
|
-
*
|
|
1898
|
+
* Edit metadata for a document with a list of ops applied in order, in one operation. `{op: 'set', path, value}` writes value at path, creating missing objects along it; `{op: 'delete', path}` removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
|
|
1783
1899
|
* @param {string} documentId - The document ID
|
|
1784
|
-
* @param {any} body - The metadata
|
|
1900
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
1785
1901
|
*/
|
|
1786
1902
|
patchMetadata: (documentId, body, auditMessage) =>
|
|
1787
1903
|
this._request("PATCH", `/api/v1/documents/${documentId}/metadata`, {
|
|
1788
1904
|
auditMessage,
|
|
1789
1905
|
rawBody: body,
|
|
1790
|
-
skipResponseTransform: true,
|
|
1791
1906
|
}),
|
|
1792
1907
|
/**
|
|
1793
1908
|
* Get audit log for a document. Transparently follows pagination cursors
|
|
@@ -1795,18 +1910,16 @@ class PlaidClient {
|
|
|
1795
1910
|
* @param {string} documentId - The document ID
|
|
1796
1911
|
* @param {string} [startTime] - Start of time range
|
|
1797
1912
|
* @param {string} [endTime] - End of time range
|
|
1798
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
1799
1913
|
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
1800
1914
|
* types, spelled as in an entry's `op/type` (e.g.
|
|
1801
1915
|
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
1802
1916
|
* one of its operations matches, carrying only the ones that did.
|
|
1803
1917
|
*/
|
|
1804
|
-
audit: (documentId, startTime, endTime,
|
|
1918
|
+
audit: (documentId, startTime, endTime, opTypes) =>
|
|
1805
1919
|
listAll(this, `/api/v1/documents/${documentId}/audit`, {
|
|
1806
1920
|
query: {
|
|
1807
1921
|
"start-time": startTime,
|
|
1808
1922
|
"end-time": endTime,
|
|
1809
|
-
"as-of": asOf,
|
|
1810
1923
|
"op-types": opTypesParam(opTypes),
|
|
1811
1924
|
},
|
|
1812
1925
|
}),
|
|
@@ -1814,6 +1927,7 @@ class PlaidClient {
|
|
|
1814
1927
|
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
1815
1928
|
* rather than audit() wherever the caller wants the recent end of a log
|
|
1816
1929
|
* that may be long: audit() walks every page before it resolves.
|
|
1930
|
+
* @param {string} documentId - The document ID
|
|
1817
1931
|
* @param {object} [opts]
|
|
1818
1932
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
1819
1933
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
@@ -1822,7 +1936,7 @@ class PlaidClient {
|
|
|
1822
1936
|
*/
|
|
1823
1937
|
auditPage: (
|
|
1824
1938
|
documentId,
|
|
1825
|
-
{ startTime, endTime,
|
|
1939
|
+
{ startTime, endTime, opTypes, order, limit, cursor } = {},
|
|
1826
1940
|
) =>
|
|
1827
1941
|
listPage(this, `/api/v1/documents/${documentId}/audit`, {
|
|
1828
1942
|
limit,
|
|
@@ -1830,7 +1944,6 @@ class PlaidClient {
|
|
|
1830
1944
|
query: {
|
|
1831
1945
|
"start-time": startTime,
|
|
1832
1946
|
"end-time": endTime,
|
|
1833
|
-
"as-of": asOf,
|
|
1834
1947
|
"op-types": opTypesParam(opTypes),
|
|
1835
1948
|
order,
|
|
1836
1949
|
},
|
|
@@ -1874,7 +1987,6 @@ class PlaidClient {
|
|
|
1874
1987
|
*/
|
|
1875
1988
|
get: (documentId, includeBody, asOf, layers) =>
|
|
1876
1989
|
this._request("GET", `/api/v1/documents/${documentId}`, {
|
|
1877
|
-
bypassBatch: true,
|
|
1878
1990
|
queryParams: {
|
|
1879
1991
|
"include-body": includeBody,
|
|
1880
1992
|
"as-of": asOf,
|
|
@@ -2019,18 +2131,16 @@ class PlaidClient {
|
|
|
2019
2131
|
* @param {string} projectId - The project ID
|
|
2020
2132
|
* @param {string} [startTime] - Start of time range
|
|
2021
2133
|
* @param {string} [endTime] - End of time range
|
|
2022
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2023
2134
|
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
2024
2135
|
* types, spelled as in an entry's `op/type` (e.g.
|
|
2025
2136
|
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
2026
2137
|
* one of its operations matches, carrying only the ones that did.
|
|
2027
2138
|
*/
|
|
2028
|
-
audit: (projectId, startTime, endTime,
|
|
2139
|
+
audit: (projectId, startTime, endTime, opTypes) =>
|
|
2029
2140
|
listAll(this, `/api/v1/projects/${projectId}/audit`, {
|
|
2030
2141
|
query: {
|
|
2031
2142
|
"start-time": startTime,
|
|
2032
2143
|
"end-time": endTime,
|
|
2033
|
-
"as-of": asOf,
|
|
2034
2144
|
"op-types": opTypesParam(opTypes),
|
|
2035
2145
|
},
|
|
2036
2146
|
}),
|
|
@@ -2038,6 +2148,7 @@ class PlaidClient {
|
|
|
2038
2148
|
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
2039
2149
|
* rather than audit() wherever the caller wants the recent end of a log
|
|
2040
2150
|
* that may be long: audit() walks every page before it resolves.
|
|
2151
|
+
* @param {string} projectId - The project ID
|
|
2041
2152
|
* @param {object} [opts]
|
|
2042
2153
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
2043
2154
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
@@ -2046,7 +2157,7 @@ class PlaidClient {
|
|
|
2046
2157
|
*/
|
|
2047
2158
|
auditPage: (
|
|
2048
2159
|
projectId,
|
|
2049
|
-
{ startTime, endTime,
|
|
2160
|
+
{ startTime, endTime, opTypes, order, limit, cursor } = {},
|
|
2050
2161
|
) =>
|
|
2051
2162
|
listPage(this, `/api/v1/projects/${projectId}/audit`, {
|
|
2052
2163
|
limit,
|
|
@@ -2054,7 +2165,6 @@ class PlaidClient {
|
|
|
2054
2165
|
query: {
|
|
2055
2166
|
"start-time": startTime,
|
|
2056
2167
|
"end-time": endTime,
|
|
2057
|
-
"as-of": asOf,
|
|
2058
2168
|
"op-types": opTypesParam(opTypes),
|
|
2059
2169
|
order,
|
|
2060
2170
|
},
|
|
@@ -2071,7 +2181,6 @@ class PlaidClient {
|
|
|
2071
2181
|
*/
|
|
2072
2182
|
myLastEdits: (projectId) =>
|
|
2073
2183
|
this._request("GET", `/api/v1/projects/${projectId}/audit/last-edits`, {
|
|
2074
|
-
bypassBatch: true,
|
|
2075
2184
|
skipResponseTransform: true,
|
|
2076
2185
|
}),
|
|
2077
2186
|
/**
|
|
@@ -2096,13 +2205,9 @@ class PlaidClient {
|
|
|
2096
2205
|
* Get a project by ID. To fetch the project's documents, use
|
|
2097
2206
|
* listDocuments(id) — the include-documents flag has been removed.
|
|
2098
2207
|
* @param {string} id - The resource ID
|
|
2099
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2100
2208
|
*/
|
|
2101
|
-
get: (id
|
|
2102
|
-
this._request("GET", `/api/v1/projects/${id}
|
|
2103
|
-
bypassBatch: true,
|
|
2104
|
-
queryParams: { "as-of": asOf },
|
|
2105
|
-
}),
|
|
2209
|
+
get: (id) =>
|
|
2210
|
+
this._request("GET", `/api/v1/projects/${id}`),
|
|
2106
2211
|
/**
|
|
2107
2212
|
* List all documents in a project. Transparently follows pagination
|
|
2108
2213
|
* cursors and returns the full flat array.
|
|
@@ -2139,17 +2244,15 @@ class PlaidClient {
|
|
|
2139
2244
|
iterDocuments: (id, { pageSize } = {}) =>
|
|
2140
2245
|
iterPages(this, `/api/v1/projects/${id}/documents`, { pageSize }),
|
|
2141
2246
|
/**
|
|
2142
|
-
* Delete a project and everything in it. This is irrecoverable.
|
|
2143
|
-
*
|
|
2144
|
-
*
|
|
2145
|
-
* per-request timeout is DISABLED by default. Pass `{ timeout }` to
|
|
2146
|
-
* impose a finite bound.
|
|
2247
|
+
* Delete a project and everything in it. This is irrecoverable. The
|
|
2248
|
+
* project is gone when this returns, and what it holds is removed on
|
|
2249
|
+
* the server afterwards.
|
|
2147
2250
|
* @param {string} id - The resource ID
|
|
2148
2251
|
* @param {string} [auditMessage] - Custom audit-log message
|
|
2149
2252
|
* @param {object} [options]
|
|
2150
|
-
* @param {number} [options.timeout
|
|
2253
|
+
* @param {number} [options.timeout] - Per-request timeout in ms, the client's own by default (0/null disables)
|
|
2151
2254
|
*/
|
|
2152
|
-
delete: (id, auditMessage, { timeout
|
|
2255
|
+
delete: (id, auditMessage, { timeout } = {}) =>
|
|
2153
2256
|
this._request("DELETE", `/api/v1/projects/${id}`, {
|
|
2154
2257
|
auditMessage,
|
|
2155
2258
|
timeout,
|
|
@@ -2167,35 +2270,29 @@ class PlaidClient {
|
|
|
2167
2270
|
/**
|
|
2168
2271
|
* List all projects accessible to user. Transparently follows pagination
|
|
2169
2272
|
* cursors and returns the full flat array.
|
|
2170
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2171
2273
|
*/
|
|
2172
|
-
list: (
|
|
2173
|
-
listAll(this, "/api/v1/projects", { query: { "as-of": asOf } }),
|
|
2274
|
+
list: () => listAll(this, "/api/v1/projects"),
|
|
2174
2275
|
/**
|
|
2175
2276
|
* Fetch a single page of projects.
|
|
2176
2277
|
* @param {object} [opts]
|
|
2177
2278
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
2178
2279
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
2179
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
2180
2280
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2181
2281
|
*/
|
|
2182
|
-
listPage: ({ limit, cursor
|
|
2282
|
+
listPage: ({ limit, cursor } = {}) =>
|
|
2183
2283
|
listPage(this, "/api/v1/projects", {
|
|
2184
2284
|
limit,
|
|
2185
2285
|
cursor,
|
|
2186
|
-
query: { "as-of": asOf },
|
|
2187
2286
|
}),
|
|
2188
2287
|
/**
|
|
2189
2288
|
* Async-iterate projects page by page; yields each page's entries array.
|
|
2190
2289
|
* @param {object} [opts]
|
|
2191
2290
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
2192
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
2193
2291
|
* @returns {AsyncGenerator<Array>}
|
|
2194
2292
|
*/
|
|
2195
|
-
iterPages: ({ pageSize
|
|
2293
|
+
iterPages: ({ pageSize } = {}) =>
|
|
2196
2294
|
iterPages(this, "/api/v1/projects", {
|
|
2197
2295
|
pageSize,
|
|
2198
|
-
query: { "as-of": asOf },
|
|
2199
2296
|
}),
|
|
2200
2297
|
/**
|
|
2201
2298
|
* Create a new project. Note: this also registers the user as a maintainer.
|
|
@@ -2243,13 +2340,9 @@ class PlaidClient {
|
|
|
2243
2340
|
/**
|
|
2244
2341
|
* Get a text layer by ID.
|
|
2245
2342
|
* @param {string} textLayerId - The text layer ID
|
|
2246
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2247
2343
|
*/
|
|
2248
|
-
get: (textLayerId
|
|
2249
|
-
this._request("GET", `/api/v1/text-layers/${textLayerId}
|
|
2250
|
-
bypassBatch: true,
|
|
2251
|
-
queryParams: { "as-of": asOf },
|
|
2252
|
-
}),
|
|
2344
|
+
get: (textLayerId) =>
|
|
2345
|
+
this._request("GET", `/api/v1/text-layers/${textLayerId}`),
|
|
2253
2346
|
/**
|
|
2254
2347
|
* Delete a text layer.
|
|
2255
2348
|
* @param {string} textLayerId - The text layer ID
|
|
@@ -2300,7 +2393,6 @@ class PlaidClient {
|
|
|
2300
2393
|
this._request("PUT", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2301
2394
|
auditMessage,
|
|
2302
2395
|
rawBody: body,
|
|
2303
|
-
skipResponseTransform: true,
|
|
2304
2396
|
}),
|
|
2305
2397
|
/**
|
|
2306
2398
|
* Remove all metadata from a vocab item.
|
|
@@ -2309,18 +2401,16 @@ class PlaidClient {
|
|
|
2309
2401
|
deleteMetadata: (id, auditMessage) =>
|
|
2310
2402
|
this._request("DELETE", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2311
2403
|
auditMessage,
|
|
2312
|
-
skipResponseTransform: true,
|
|
2313
2404
|
}),
|
|
2314
2405
|
/**
|
|
2315
|
-
*
|
|
2406
|
+
* Edit metadata for a vocab item with a list of ops applied in order, in one operation. `{op: 'set', path, value}` writes value at path, creating missing objects along it; `{op: 'delete', path}` removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
|
|
2316
2407
|
* @param {string} id - The resource ID
|
|
2317
|
-
* @param {any} body - The metadata
|
|
2408
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
2318
2409
|
*/
|
|
2319
2410
|
patchMetadata: (id, body, auditMessage) =>
|
|
2320
2411
|
this._request("PATCH", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2321
2412
|
auditMessage,
|
|
2322
2413
|
rawBody: body,
|
|
2323
|
-
skipResponseTransform: true,
|
|
2324
2414
|
}),
|
|
2325
2415
|
/**
|
|
2326
2416
|
* Create a new vocab item
|
|
@@ -2343,9 +2433,23 @@ class PlaidClient {
|
|
|
2343
2433
|
auditMessage,
|
|
2344
2434
|
body,
|
|
2345
2435
|
}),
|
|
2436
|
+
/**
|
|
2437
|
+
* Update many vocab items in a single operation: set forms and/or patch
|
|
2438
|
+
* metadata.
|
|
2439
|
+
* @param {Array<{id: string, form?: string, metadata?: Array<object>}>} body - `form` is set only when the key is present; `metadata` is a list of metadata ops, as for patchMetadata. The entries may lie in several vocab layers; the user must have write access to each. An unknown id refuses the whole update, and an id may appear only once. Only an entry whose form really changes restates the documents linking it, and a strict-mode client picks up their new versions from the response.
|
|
2440
|
+
* @returns {Promise<{count: number}>} How many vocab items were updated.
|
|
2441
|
+
*/
|
|
2442
|
+
bulkUpdate: (body, auditMessage) =>
|
|
2443
|
+
this._request("PATCH", "/api/v1/vocab-items/bulk", {
|
|
2444
|
+
auditMessage,
|
|
2445
|
+
body,
|
|
2446
|
+
}),
|
|
2346
2447
|
/**
|
|
2347
2448
|
* Delete multiple vocab items in a single operation. Each item's
|
|
2348
2449
|
* descendant vocab links are deleted too. Provide an array of IDs.
|
|
2450
|
+
*
|
|
2451
|
+
* Every document holding one of those links has its version bumped, and
|
|
2452
|
+
* a strict-mode client picks up their new versions from the response.
|
|
2349
2453
|
* @param {string[]} body - The vocab item IDs to delete
|
|
2350
2454
|
*/
|
|
2351
2455
|
bulkDelete: (body, auditMessage) =>
|
|
@@ -2356,21 +2460,22 @@ class PlaidClient {
|
|
|
2356
2460
|
/**
|
|
2357
2461
|
* Get a vocab item by ID
|
|
2358
2462
|
* @param {string} id - The resource ID
|
|
2359
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2360
2463
|
*/
|
|
2361
|
-
get: (id
|
|
2362
|
-
this._request("GET", `/api/v1/vocab-items/${id}
|
|
2363
|
-
bypassBatch: true,
|
|
2364
|
-
queryParams: { "as-of": asOf },
|
|
2365
|
-
}),
|
|
2464
|
+
get: (id) =>
|
|
2465
|
+
this._request("GET", `/api/v1/vocab-items/${id}`),
|
|
2366
2466
|
/**
|
|
2367
|
-
* Delete a vocab item
|
|
2467
|
+
* Delete a vocab item, and every link to it. Every document holding one
|
|
2468
|
+
* of those links has its version bumped, and a strict-mode client picks
|
|
2469
|
+
* up their new versions from the response.
|
|
2368
2470
|
* @param {string} id - The resource ID
|
|
2369
2471
|
*/
|
|
2370
2472
|
delete: (id, auditMessage) =>
|
|
2371
2473
|
this._request("DELETE", `/api/v1/vocab-items/${id}`, { auditMessage }),
|
|
2372
2474
|
/**
|
|
2373
|
-
* Update a vocab item's form
|
|
2475
|
+
* Update a vocab item's form. A document read carries the entry's form on
|
|
2476
|
+
* every link to it, so a rename restates those documents: each has its
|
|
2477
|
+
* version bumped, and a strict-mode client picks up their new versions
|
|
2478
|
+
* from the response.
|
|
2374
2479
|
* @param {string} id - The resource ID
|
|
2375
2480
|
* @param {string} form - The vocab item form
|
|
2376
2481
|
*/
|
|
@@ -2437,13 +2542,9 @@ class PlaidClient {
|
|
|
2437
2542
|
/**
|
|
2438
2543
|
* Get a relation layer by ID.
|
|
2439
2544
|
* @param {string} relationLayerId - The relation layer ID
|
|
2440
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2441
2545
|
*/
|
|
2442
|
-
get: (relationLayerId
|
|
2443
|
-
this._request("GET", `/api/v1/relation-layers/${relationLayerId}
|
|
2444
|
-
bypassBatch: true,
|
|
2445
|
-
queryParams: { "as-of": asOf },
|
|
2446
|
-
}),
|
|
2546
|
+
get: (relationLayerId) =>
|
|
2547
|
+
this._request("GET", `/api/v1/relation-layers/${relationLayerId}`),
|
|
2447
2548
|
/**
|
|
2448
2549
|
* Delete a relation layer.
|
|
2449
2550
|
* @param {string} relationLayerId - The relation layer ID
|
|
@@ -2506,13 +2607,9 @@ class PlaidClient {
|
|
|
2506
2607
|
/**
|
|
2507
2608
|
* Get a token.
|
|
2508
2609
|
* @param {string} tokenId - The token ID
|
|
2509
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2510
2610
|
*/
|
|
2511
|
-
get: (tokenId
|
|
2512
|
-
this._request("GET", `/api/v1/tokens/${tokenId}
|
|
2513
|
-
bypassBatch: true,
|
|
2514
|
-
queryParams: { "as-of": asOf },
|
|
2515
|
-
}),
|
|
2611
|
+
get: (tokenId) =>
|
|
2612
|
+
this._request("GET", `/api/v1/tokens/${tokenId}`),
|
|
2516
2613
|
/**
|
|
2517
2614
|
* Delete a token and remove it from any spans. If this causes a span to
|
|
2518
2615
|
* have no remaining tokens, the span will also be deleted.
|
|
@@ -2550,7 +2647,7 @@ class PlaidClient {
|
|
|
2550
2647
|
this._request("DELETE", "/api/v1/tokens/bulk", { auditMessage, body }),
|
|
2551
2648
|
/**
|
|
2552
2649
|
* Update many tokens in a single operation: patch metadata.
|
|
2553
|
-
* @param {Array} body - Objects of the shape `{id, metadata}`. `metadata` is a
|
|
2650
|
+
* @param {Array} body - Objects of the shape `{id, metadata}`. `metadata` is a list of metadata ops, as for patchMetadata. The tokens may lie in several documents of one project; every document touched has its version bumped and every new version comes back in `X-Document-Versions` (past fifty documents, only their number, in `X-Document-Versions-Omitted`). A `document-version` precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update.
|
|
2554
2651
|
* @returns {Promise<{count: number}>} How many tokens were updated.
|
|
2555
2652
|
*/
|
|
2556
2653
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -2604,7 +2701,6 @@ class PlaidClient {
|
|
|
2604
2701
|
this._request("PUT", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2605
2702
|
auditMessage,
|
|
2606
2703
|
rawBody: body,
|
|
2607
|
-
skipResponseTransform: true,
|
|
2608
2704
|
}),
|
|
2609
2705
|
/**
|
|
2610
2706
|
* Remove all metadata from a token.
|
|
@@ -2613,18 +2709,16 @@ class PlaidClient {
|
|
|
2613
2709
|
deleteMetadata: (tokenId, auditMessage) =>
|
|
2614
2710
|
this._request("DELETE", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2615
2711
|
auditMessage,
|
|
2616
|
-
skipResponseTransform: true,
|
|
2617
2712
|
}),
|
|
2618
2713
|
/**
|
|
2619
|
-
*
|
|
2714
|
+
* Edit metadata for a token with a list of ops applied in order, in one operation. `{op: 'set', path, value}` writes value at path, creating missing objects along it; `{op: 'delete', path}` removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
|
|
2620
2715
|
* @param {string} tokenId - The token ID
|
|
2621
|
-
* @param {any} body - The metadata
|
|
2716
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
2622
2717
|
*/
|
|
2623
2718
|
patchMetadata: (tokenId, body, auditMessage) =>
|
|
2624
2719
|
this._request("PATCH", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2625
2720
|
auditMessage,
|
|
2626
2721
|
rawBody: body,
|
|
2627
|
-
skipResponseTransform: true,
|
|
2628
2722
|
}),
|
|
2629
2723
|
};
|
|
2630
2724
|
|
|
@@ -2648,21 +2742,24 @@ class PlaidClient {
|
|
|
2648
2742
|
* this a key such as `case-marker` would reach listeners as
|
|
2649
2743
|
* `caseMarker` in JS and `case_marker` in Python. `listen` restores it
|
|
2650
2744
|
* verbatim on the way in.
|
|
2745
|
+
*
|
|
2746
|
+
* A message is not saved and writes nothing to History, so it never
|
|
2747
|
+
* joins an open operation (`withOperation`). Made on a batch it still
|
|
2748
|
+
* queues, so a message queued after the writes goes out after them.
|
|
2651
2749
|
* @param {string} projectId - The UUID of the project to send to
|
|
2652
2750
|
* @param {any} data - The message data to send
|
|
2653
2751
|
* @returns {Promise<any>} Response from the send operation
|
|
2654
2752
|
*/
|
|
2655
|
-
sendMessage: (projectId, data
|
|
2753
|
+
sendMessage: (projectId, data) =>
|
|
2656
2754
|
this._request("POST", `/api/v1/projects/${projectId}/message`, {
|
|
2657
|
-
|
|
2755
|
+
noOperation: true,
|
|
2658
2756
|
rawBody: { body: data },
|
|
2659
2757
|
}),
|
|
2660
2758
|
|
|
2661
2759
|
/**
|
|
2662
2760
|
* Discover the services seen on a project (synchronous GET). Currently
|
|
2663
2761
|
* connected services carry `online: true`; previously-seen offline ones
|
|
2664
|
-
* carry `online: false` plus a `lastSeenAt` stamp.
|
|
2665
|
-
* even while a batch is open on the client.
|
|
2762
|
+
* carry `online: false` plus a `lastSeenAt` stamp.
|
|
2666
2763
|
* @param {string} projectId - The UUID of the project to query
|
|
2667
2764
|
* @returns {Promise<Array>} Array of discovered service information
|
|
2668
2765
|
*/
|
|
@@ -2688,7 +2785,9 @@ class PlaidClient {
|
|
|
2688
2785
|
* @param {function} onServiceRequest - Callback (data, responseHelper)
|
|
2689
2786
|
* @param {Object} [extras] - Optional additional service metadata
|
|
2690
2787
|
* @param {function} [onStatus] - Optional callback (event, projectId, detail) for
|
|
2691
|
-
* connection transitions: 'registered', 'reconnected', 'disconnected'
|
|
2788
|
+
* connection transitions: 'registered', 'reconnected', 'disconnected',
|
|
2789
|
+
* and 'stopped' when the server refuses the channel for good, which
|
|
2790
|
+
* ends the registration
|
|
2692
2791
|
* @returns {Object} Service registration with .stop(), .isRunning(), .isConnected()
|
|
2693
2792
|
*/
|
|
2694
2793
|
serve: (projectId, serviceInfo, onServiceRequest, extras, onStatus) =>
|
|
@@ -2710,7 +2809,7 @@ class PlaidClient {
|
|
|
2710
2809
|
* @param {number} [timeout=10000] - Timeout in ms
|
|
2711
2810
|
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
2712
2811
|
* @param {AbortSignal} [signal] - Abort to stop waiting; rejects with an AbortError
|
|
2713
|
-
* @param {Object} [opts] - {requestId, onAccepted}
|
|
2812
|
+
* @param {Object} [opts] - {requestId, onAccepted, projectIds}
|
|
2714
2813
|
* @returns {Promise<any>} The service's result
|
|
2715
2814
|
*/
|
|
2716
2815
|
requestService: (
|
|
@@ -2808,21 +2907,15 @@ class PlaidClient {
|
|
|
2808
2907
|
* @param {Object} body - The query AST ({find, where, scope?, limit?, orderBy?,
|
|
2809
2908
|
* return?, bindings?}).
|
|
2810
2909
|
* @returns {Promise<Object>} For 'ids'/'entities': {columns, results, count, truncated}.
|
|
2811
|
-
* For 'count': {return: 'count', count}
|
|
2812
|
-
*
|
|
2910
|
+
* For 'count': {return: 'count', count, truncated}, where `truncated` says
|
|
2911
|
+
* the count stopped at the server's cap. Entity cells are full entity
|
|
2912
|
+
* objects (same shape as the GET endpoints).
|
|
2813
2913
|
*/
|
|
2814
|
-
//
|
|
2815
|
-
//
|
|
2816
|
-
//
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
// 500s and rolls back every write in that batch.
|
|
2820
|
-
this.query = (body, auditMessage) =>
|
|
2821
|
-
this._request("POST", "/api/v1/query", {
|
|
2822
|
-
auditMessage,
|
|
2823
|
-
body,
|
|
2824
|
-
bypassBatch: true,
|
|
2825
|
-
});
|
|
2914
|
+
// outOfBand: a query is a read that travels as a POST (see the note at
|
|
2915
|
+
// the top of http.js). Made on a batch it goes over the wire like any
|
|
2916
|
+
// read, and it never joins a logical operation.
|
|
2917
|
+
this.query = (body) =>
|
|
2918
|
+
this._request("POST", "/api/v1/query", { body, outOfBand: true });
|
|
2826
2919
|
|
|
2827
2920
|
// Logical-operation groups (audit-log grouping). There is no create: a
|
|
2828
2921
|
// group row is made lazily by the first write carrying `?group-id=`
|
|
@@ -2832,10 +2925,7 @@ class PlaidClient {
|
|
|
2832
2925
|
* Get a logical-operation group (its label + creator).
|
|
2833
2926
|
* @param {string} id - The group id
|
|
2834
2927
|
*/
|
|
2835
|
-
get: (id) =>
|
|
2836
|
-
this._request("GET", `/api/v1/operation-groups/${id}`, {
|
|
2837
|
-
bypassBatch: true,
|
|
2838
|
-
}),
|
|
2928
|
+
get: (id) => this._request("GET", `/api/v1/operation-groups/${id}`),
|
|
2839
2929
|
/**
|
|
2840
2930
|
* Relabel a logical-operation group after the fact. Owner or admin only.
|
|
2841
2931
|
* @param {string} id - The group id
|
|
@@ -2876,8 +2966,7 @@ class PlaidClient {
|
|
|
2876
2966
|
* Read one comment.
|
|
2877
2967
|
* @param {string} id - The comment id
|
|
2878
2968
|
*/
|
|
2879
|
-
get: (id) =>
|
|
2880
|
-
this._request("GET", `/api/v1/comments/${id}`, { bypassBatch: true }),
|
|
2969
|
+
get: (id) => this._request("GET", `/api/v1/comments/${id}`),
|
|
2881
2970
|
/**
|
|
2882
2971
|
* Edit a comment's body. Only the comment's AUTHOR may do this - not
|
|
2883
2972
|
* maintainers, not admins. Sets `edited` on the comment.
|
|
@@ -2969,7 +3058,6 @@ class PlaidClient {
|
|
|
2969
3058
|
*/
|
|
2970
3059
|
counts: (projectId, { documentId, entityType, entityId } = {}) =>
|
|
2971
3060
|
this._request("GET", `/api/v1/projects/${projectId}/comments/counts`, {
|
|
2972
|
-
bypassBatch: true,
|
|
2973
3061
|
queryParams: {
|
|
2974
3062
|
"document-id": documentId,
|
|
2975
3063
|
"entity-type": entityType,
|
|
@@ -3004,6 +3092,20 @@ class PlaidClient {
|
|
|
3004
3092
|
cursor,
|
|
3005
3093
|
query: { "entity-id": entityId },
|
|
3006
3094
|
}),
|
|
3095
|
+
/**
|
|
3096
|
+
* Async-iterate a vocabulary's comments page by page, oldest first;
|
|
3097
|
+
* yields each page's entries array.
|
|
3098
|
+
* @param {string} vocabId - The vocab layer to read
|
|
3099
|
+
* @param {object} [opts]
|
|
3100
|
+
* @param {string} [opts.entityId] - One entry's thread
|
|
3101
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
3102
|
+
* @returns {AsyncGenerator<Array>}
|
|
3103
|
+
*/
|
|
3104
|
+
iterInVocabPages: (vocabId, { entityId, pageSize } = {}) =>
|
|
3105
|
+
iterPages(this, `/api/v1/vocab-layers/${vocabId}/comments`, {
|
|
3106
|
+
pageSize,
|
|
3107
|
+
query: { "entity-id": entityId },
|
|
3108
|
+
}),
|
|
3007
3109
|
/**
|
|
3008
3110
|
* Comment counts per entry of a vocabulary, as an `{entityId: n}` map.
|
|
3009
3111
|
* The response is NOT key-transformed: its keys are entry ids.
|
|
@@ -3017,12 +3119,111 @@ class PlaidClient {
|
|
|
3017
3119
|
"GET",
|
|
3018
3120
|
`/api/v1/vocab-layers/${vocabId}/comments/counts`,
|
|
3019
3121
|
{
|
|
3020
|
-
bypassBatch: true,
|
|
3021
3122
|
queryParams: { "entity-id": entityId },
|
|
3022
3123
|
skipResponseTransform: true,
|
|
3023
3124
|
},
|
|
3024
3125
|
),
|
|
3025
3126
|
};
|
|
3127
|
+
|
|
3128
|
+
this.guidelines = {
|
|
3129
|
+
/**
|
|
3130
|
+
* Create a guideline in a project. `title` is the handle an assistant
|
|
3131
|
+
* asks for one by, and the only thing a person has to keep current. It is
|
|
3132
|
+
* NOT required to be unique, so a title already in use is written like
|
|
3133
|
+
* any other: warn about it, do not refuse it. A `pinned` guideline is one
|
|
3134
|
+
* the assistant is given in full on every turn.
|
|
3135
|
+
* @param {string} projectId - The project the guideline belongs to
|
|
3136
|
+
* @param {string} title - The handle an assistant asks for one by (1..100 characters)
|
|
3137
|
+
* @param {object} [opts]
|
|
3138
|
+
* @param {string} [opts.body] - The Markdown text (up to 20000 characters; may be empty)
|
|
3139
|
+
* @param {boolean} [opts.pinned] - Send this one to the assistant in full on every turn
|
|
3140
|
+
*/
|
|
3141
|
+
create: (projectId, title, { body, pinned } = {}, auditMessage) =>
|
|
3142
|
+
this._request("POST", `/api/v1/projects/${projectId}/guidelines`, {
|
|
3143
|
+
auditMessage,
|
|
3144
|
+
body: bodyOf({ title, body, pinned }),
|
|
3145
|
+
}),
|
|
3146
|
+
/**
|
|
3147
|
+
* Read one guideline, Markdown body included.
|
|
3148
|
+
* @param {string} id - The guideline id
|
|
3149
|
+
*/
|
|
3150
|
+
get: (id) => this._request("GET", `/api/v1/guidelines/${id}`),
|
|
3151
|
+
/**
|
|
3152
|
+
* Update a guideline. Every field is optional and an omitted one is
|
|
3153
|
+
* left alone, so an edit to the body need not restate the title.
|
|
3154
|
+
*
|
|
3155
|
+
* Pass `expectedUpdatedAt` (the `updatedAt` you last read) when a person
|
|
3156
|
+
* has been editing prose: the write then fails with 409 rather than
|
|
3157
|
+
* overwriting somebody who saved in between. Leave it off for a pin
|
|
3158
|
+
* toggle or a script, which have nothing of anyone's to lose.
|
|
3159
|
+
* @param {string} id - The guideline id
|
|
3160
|
+
* @param {object} [changes]
|
|
3161
|
+
* @param {string} [changes.title] - The new handle
|
|
3162
|
+
* @param {string} [changes.body] - The new Markdown text
|
|
3163
|
+
* @param {boolean} [changes.pinned] - Whether the assistant always gets it in full
|
|
3164
|
+
* @param {string} [changes.expectedUpdatedAt] - Write only if this is still the stored updatedAt
|
|
3165
|
+
*/
|
|
3166
|
+
update: (
|
|
3167
|
+
id,
|
|
3168
|
+
{ title, body, pinned, expectedUpdatedAt } = {},
|
|
3169
|
+
auditMessage,
|
|
3170
|
+
) =>
|
|
3171
|
+
this._request("PATCH", `/api/v1/guidelines/${id}`, {
|
|
3172
|
+
auditMessage,
|
|
3173
|
+
queryParams: { "updated-at": expectedUpdatedAt },
|
|
3174
|
+
body: bodyOf({ title, body, pinned }),
|
|
3175
|
+
}),
|
|
3176
|
+
/**
|
|
3177
|
+
* Delete a guideline.
|
|
3178
|
+
* @param {string} id - The guideline id
|
|
3179
|
+
*/
|
|
3180
|
+
delete: (id, auditMessage) =>
|
|
3181
|
+
this._request("DELETE", `/api/v1/guidelines/${id}`, { auditMessage }),
|
|
3182
|
+
/**
|
|
3183
|
+
* List a project's guidelines, by title. Transparently follows
|
|
3184
|
+
* pagination cursors and returns the full flat array.
|
|
3185
|
+
*
|
|
3186
|
+
* Without `includeBodies` each entry carries `bodyChars`, the length of
|
|
3187
|
+
* its body, so a caller can budget before fetching any. Pinned
|
|
3188
|
+
* guidelines are NOT sorted first: `pinned` is on every entry and
|
|
3189
|
+
* grouping is the caller's.
|
|
3190
|
+
* @param {string} projectId - The project to read
|
|
3191
|
+
* @param {object} [opts]
|
|
3192
|
+
* @param {boolean} [opts.includeBodies] - Return each body instead of its length
|
|
3193
|
+
*/
|
|
3194
|
+
list: (projectId, { includeBodies } = {}) =>
|
|
3195
|
+
listAll(this, `/api/v1/projects/${projectId}/guidelines`, {
|
|
3196
|
+
query: { "include-bodies": includeBodies },
|
|
3197
|
+
}),
|
|
3198
|
+
/**
|
|
3199
|
+
* Fetch a single page of a project's guidelines.
|
|
3200
|
+
* @param {string} projectId - The project to read
|
|
3201
|
+
* @param {object} [opts]
|
|
3202
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
3203
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
3204
|
+
* @param {boolean} [opts.includeBodies] - Return each body instead of its length
|
|
3205
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
3206
|
+
*/
|
|
3207
|
+
listPage: (projectId, { limit, cursor, includeBodies } = {}) =>
|
|
3208
|
+
listPage(this, `/api/v1/projects/${projectId}/guidelines`, {
|
|
3209
|
+
limit,
|
|
3210
|
+
cursor,
|
|
3211
|
+
query: { "include-bodies": includeBodies },
|
|
3212
|
+
}),
|
|
3213
|
+
/**
|
|
3214
|
+
* Async-iterate a project's guidelines page by page; yields each page's entries array.
|
|
3215
|
+
* @param {string} projectId - The project to read
|
|
3216
|
+
* @param {object} [opts]
|
|
3217
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
3218
|
+
* @param {boolean} [opts.includeBodies] - Return each body instead of its length
|
|
3219
|
+
* @returns {AsyncGenerator<Array>}
|
|
3220
|
+
*/
|
|
3221
|
+
iterPages: (projectId, { pageSize, includeBodies } = {}) =>
|
|
3222
|
+
iterPages(this, `/api/v1/projects/${projectId}/guidelines`, {
|
|
3223
|
+
pageSize,
|
|
3224
|
+
query: { "include-bodies": includeBodies },
|
|
3225
|
+
}),
|
|
3226
|
+
};
|
|
3026
3227
|
}
|
|
3027
3228
|
|
|
3028
3229
|
// --- Core methods ---
|
|
@@ -3067,8 +3268,8 @@ class PlaidClient {
|
|
|
3067
3268
|
* "Every write" is the writes of project data. Reads never join, and neither
|
|
3068
3269
|
* do the out-of-band signals shaped like a write (a document lock taken or
|
|
3069
3270
|
* renewed, a stopped service request, a service reporting itself, an admin
|
|
3070
|
-
* control)
|
|
3071
|
-
* label.
|
|
3271
|
+
* control), and neither does a broadcast message (`messages.sendMessage`):
|
|
3272
|
+
* none of them is audited, so there would be nothing under the label.
|
|
3072
3273
|
*
|
|
3073
3274
|
* @param {string} message - Human label for the operation.
|
|
3074
3275
|
* @param {object} [opts] - Optional `{ id }`: adopt an existing group id instead of minting one (a service joining the requester's operation; `requestService` propagates an open operation to the service automatically).
|
|
@@ -3124,7 +3325,7 @@ class PlaidClient {
|
|
|
3124
3325
|
* callback to refine the label once the outcome is known.
|
|
3125
3326
|
*
|
|
3126
3327
|
* await client.withOperation('Merge morphemes', async (setMessage) => {
|
|
3127
|
-
* await client.batched(() => { ... });
|
|
3328
|
+
* await client.batched(async (b) => { ... });
|
|
3128
3329
|
* setMessage(`Merged ${n} morphemes`);
|
|
3129
3330
|
* });
|
|
3130
3331
|
*
|
|
@@ -3146,58 +3347,38 @@ class PlaidClient {
|
|
|
3146
3347
|
}
|
|
3147
3348
|
|
|
3148
3349
|
/**
|
|
3149
|
-
*
|
|
3150
|
-
*
|
|
3350
|
+
* Open a batch: a view of this client with the same bundles, on which every
|
|
3351
|
+
* write of project data queues instead of going out. `submit()` sends the
|
|
3352
|
+
* queued operations as ONE atomic request (larger than the server's cap,
|
|
3353
|
+
* as consecutive requests with the results concatenated in queue order,
|
|
3354
|
+
* each atomic on its own, so a failure in a later one leaves the earlier
|
|
3355
|
+
* ones committed, and the error it throws carries `committed`, the count
|
|
3356
|
+
* saved, and `committedResults`, their results) and
|
|
3357
|
+
* resolves to one result per operation; `abort()` drops them. A call made
|
|
3358
|
+
* on the client itself is never touched by an open batch, and a read or an
|
|
3359
|
+
* out-of-band signal made on the batch goes over the wire now (see the note
|
|
3360
|
+
* at the top of `http.js`).
|
|
3361
|
+
*
|
|
3362
|
+
* const b = client.batch();
|
|
3363
|
+
* b.tokens.bulkCreate(sentenceOps);
|
|
3364
|
+
* b.tokens.bulkCreate(wordOps);
|
|
3365
|
+
* const [sentRes, wordRes] = await b.submit();
|
|
3366
|
+
*
|
|
3367
|
+
* Server-side a batch runs sequentially in one transaction: a child op sees
|
|
3368
|
+
* parents created earlier in the same batch, and any op's failure rolls the
|
|
3369
|
+
* whole batch back. A batch is not nestable. Prefer `batched()`, which
|
|
3370
|
+
* submits or aborts for you.
|
|
3371
|
+
* @returns {PlaidClient} the batch view
|
|
3151
3372
|
*/
|
|
3152
|
-
|
|
3153
|
-
this
|
|
3154
|
-
this.batchOperations = [];
|
|
3155
|
-
// Strict mode stamps the expected document-version on the FIRST QUEUED
|
|
3156
|
-
// write of the batch only (see _request), so reset the marker per batch.
|
|
3157
|
-
this.batchVersionStamped = false;
|
|
3373
|
+
batch() {
|
|
3374
|
+
return openBatch(this);
|
|
3158
3375
|
}
|
|
3159
3376
|
|
|
3160
3377
|
/**
|
|
3161
|
-
*
|
|
3162
|
-
*
|
|
3163
|
-
*
|
|
3378
|
+
* POST one batch request (at most MAX_BATCH_OPS operations). Resolves to
|
|
3379
|
+
* one `{ status, headers, body }` per operation, with only the body
|
|
3380
|
+
* recased: the headers keep the server's spelling.
|
|
3164
3381
|
*/
|
|
3165
|
-
async submitBatch() {
|
|
3166
|
-
if (!this.isBatching) {
|
|
3167
|
-
throw new Error("No active batch. Call beginBatch() first.");
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
|
-
if (this.batchOperations.length === 0) {
|
|
3171
|
-
this.isBatching = false;
|
|
3172
|
-
return [];
|
|
3173
|
-
}
|
|
3174
|
-
|
|
3175
|
-
try {
|
|
3176
|
-
const url = `${this.baseUrl}/api/v1/batch`;
|
|
3177
|
-
const ops = this.batchOperations.map((op) => ({
|
|
3178
|
-
path: op.path,
|
|
3179
|
-
method: op.method.toUpperCase(),
|
|
3180
|
-
...(op.body && { body: op.body }),
|
|
3181
|
-
}));
|
|
3182
|
-
// The server caps a batch at MAX_BATCH_OPS so one transaction cannot
|
|
3183
|
-
// hold the write lock without bound. A larger batch goes as consecutive
|
|
3184
|
-
// requests, results concatenated in queue order: it could not have been
|
|
3185
|
-
// one transaction anyway, and a repair or bulk edit over a big document
|
|
3186
|
-
// must not fail on its size alone.
|
|
3187
|
-
const results = [];
|
|
3188
|
-
for (let i = 0; i < ops.length; i += MAX_BATCH_OPS) {
|
|
3189
|
-
results.push(
|
|
3190
|
-
...(await this._postBatch(url, ops.slice(i, i + MAX_BATCH_OPS))),
|
|
3191
|
-
);
|
|
3192
|
-
}
|
|
3193
|
-
return results;
|
|
3194
|
-
} finally {
|
|
3195
|
-
this.isBatching = false;
|
|
3196
|
-
this.batchOperations = [];
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
|
|
3200
|
-
/** POST one batch request (at most MAX_BATCH_OPS operations); resolves to its transformed results. */
|
|
3201
3382
|
async _postBatch(url, body) {
|
|
3202
3383
|
const fetchOptions = {
|
|
3203
3384
|
method: "POST",
|
|
@@ -3222,6 +3403,22 @@ class PlaidClient {
|
|
|
3222
3403
|
throw makeHttpError(res, await parseErrorBody(res), url, "POST");
|
|
3223
3404
|
}
|
|
3224
3405
|
return res;
|
|
3406
|
+
}).catch((error) => {
|
|
3407
|
+
// 401 means the token is missing, expired or invalid: fire the app's
|
|
3408
|
+
// auth-error handler once, exactly as makeRequest does.
|
|
3409
|
+
if (
|
|
3410
|
+
error?.status === 401 &&
|
|
3411
|
+
typeof this.onAuthError === "function" &&
|
|
3412
|
+
!this._authErrorFired
|
|
3413
|
+
) {
|
|
3414
|
+
this._authErrorFired = true;
|
|
3415
|
+
try {
|
|
3416
|
+
this.onAuthError(error);
|
|
3417
|
+
} catch (_) {
|
|
3418
|
+
/* handler must not mask the original error */
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
throw error;
|
|
3225
3422
|
});
|
|
3226
3423
|
|
|
3227
3424
|
const results = await response.json();
|
|
@@ -3249,80 +3446,44 @@ class PlaidClient {
|
|
|
3249
3446
|
}
|
|
3250
3447
|
}
|
|
3251
3448
|
|
|
3252
|
-
return results.map((result) =>
|
|
3449
|
+
return results.map((result) => ({
|
|
3450
|
+
...result,
|
|
3451
|
+
body: transformResponse(result.body),
|
|
3452
|
+
}));
|
|
3253
3453
|
} catch (error) {
|
|
3254
3454
|
if (error.status) throw error;
|
|
3255
3455
|
throw makeNetworkError(error, url, "POST");
|
|
3256
3456
|
}
|
|
3257
3457
|
}
|
|
3258
3458
|
|
|
3259
|
-
/** Abort the current batch without executing any operations. */
|
|
3260
|
-
abortBatch() {
|
|
3261
|
-
this.isBatching = false;
|
|
3262
|
-
this.batchOperations = [];
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
3459
|
/**
|
|
3266
|
-
*
|
|
3267
|
-
*
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
}
|
|
3272
|
-
|
|
3273
|
-
/**
|
|
3274
|
-
* Run `fn` with a batch open, then submit all queued ops as ONE atomic
|
|
3275
|
-
* request — or abort the batch if `fn` throws, so a half-open batch can never
|
|
3276
|
-
* silently swallow later non-batch calls. `fn` makes the (queued) client
|
|
3277
|
-
* calls; it must NOT call submitBatch itself. Resolves to the batch results
|
|
3278
|
-
* array (`[]` if `fn` queued nothing).
|
|
3460
|
+
* Run `fn` with a batch, then submit all queued ops as `submit()` does (ONE
|
|
3461
|
+
* atomic request up to MAX_BATCH_OPS operations, consecutive requests past
|
|
3462
|
+
* it), or abort the batch if `fn` throws. `fn` receives the batch and makes its
|
|
3463
|
+
* writes on it; it must NOT call `submit()` itself. Resolves to the batch
|
|
3464
|
+
* results array (`[]` if `fn` queued nothing).
|
|
3279
3465
|
*
|
|
3280
|
-
* const [sentRes, wordRes] = await client.batched(async () => {
|
|
3281
|
-
*
|
|
3282
|
-
*
|
|
3466
|
+
* const [sentRes, wordRes] = await client.batched(async (b) => {
|
|
3467
|
+
* b.tokens.bulkCreate(sentenceOps);
|
|
3468
|
+
* b.tokens.bulkCreate(wordOps);
|
|
3283
3469
|
* });
|
|
3284
3470
|
*
|
|
3285
|
-
*
|
|
3286
|
-
*
|
|
3287
|
-
* batch
|
|
3288
|
-
* low-level batch resource.
|
|
3289
|
-
*
|
|
3290
|
-
* WRITES are what a batch queues, and batch mode is one flag on the whole
|
|
3291
|
-
* client, so it catches every write made while it is open, including writes
|
|
3292
|
-
* made by code that knows nothing about this batch. READS are never caught:
|
|
3293
|
-
* every read method goes over the wire while a batch is open and is answered
|
|
3294
|
-
* from the state the batch has not committed yet, so a read cannot come back
|
|
3295
|
-
* as a batch marker and cannot take a slot in the results array. Neither are
|
|
3296
|
-
* the calls that signal something out of band rather than write project data:
|
|
3297
|
-
* stopping a service request, a service reporting its progress or result,
|
|
3298
|
-
* taking and dropping a document lock, and the admin actions on the server
|
|
3299
|
-
* itself. See the note at the top of `http.js` for which column a new
|
|
3300
|
-
* endpoint belongs in.
|
|
3301
|
-
* @param {() => (void | Promise<void>)} fn
|
|
3471
|
+
* A write made on `client` inside `fn` is not part of the batch: it goes
|
|
3472
|
+
* over the wire at once, as it would anywhere else. See `batch()`.
|
|
3473
|
+
* @param {(batch: PlaidClient) => (void | Promise<void>)} fn
|
|
3302
3474
|
* @returns {Promise<Array>}
|
|
3303
3475
|
*/
|
|
3304
3476
|
async batched(fn) {
|
|
3305
|
-
this.
|
|
3477
|
+
const batch = this.batch();
|
|
3306
3478
|
try {
|
|
3307
|
-
await fn();
|
|
3479
|
+
await fn(batch);
|
|
3308
3480
|
} catch (e) {
|
|
3309
|
-
|
|
3310
|
-
// later plain calls don't queue into a never-submitted batch.
|
|
3311
|
-
if (this.isBatchMode()) this.abortBatch();
|
|
3481
|
+
batch.abort();
|
|
3312
3482
|
throw e;
|
|
3313
3483
|
}
|
|
3314
|
-
return
|
|
3484
|
+
return batch.submit();
|
|
3315
3485
|
}
|
|
3316
3486
|
|
|
3317
|
-
/**
|
|
3318
|
-
* Authenticate and return a new client instance with token. This is the
|
|
3319
|
-
* single auth entry point — there is no `client.login` resource.
|
|
3320
|
-
* @param {string} baseUrl - The base URL for the API
|
|
3321
|
-
* @param {string} userId - User ID for authentication
|
|
3322
|
-
* @param {string} password - Password for authentication
|
|
3323
|
-
* @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
|
|
3324
|
-
* @returns {Promise<PlaidClient>} - Authenticated client instance
|
|
3325
|
-
*/
|
|
3326
3487
|
/**
|
|
3327
3488
|
* Liveness, version and database size, with NO authentication and no client
|
|
3328
3489
|
* instance — for a launcher or status page checking whether a server is up
|
|
@@ -3417,6 +3578,15 @@ class PlaidClient {
|
|
|
3417
3578
|
};
|
|
3418
3579
|
}
|
|
3419
3580
|
|
|
3581
|
+
/**
|
|
3582
|
+
* Authenticate and return a new client instance with token. This is the
|
|
3583
|
+
* single auth entry point — there is no `client.login` resource.
|
|
3584
|
+
* @param {string} baseUrl - The base URL for the API
|
|
3585
|
+
* @param {string} userId - User ID for authentication
|
|
3586
|
+
* @param {string} password - Password for authentication
|
|
3587
|
+
* @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
|
|
3588
|
+
* @returns {Promise<PlaidClient>} - Authenticated client instance
|
|
3589
|
+
*/
|
|
3420
3590
|
static async login(baseUrl, userId, password, options = {}) {
|
|
3421
3591
|
baseUrl = baseUrl.replace(/\/$/, "");
|
|
3422
3592
|
const url = `${baseUrl}/api/v1/login`;
|
|
@@ -3516,7 +3686,6 @@ export {
|
|
|
3516
3686
|
needsReview,
|
|
3517
3687
|
verifyOnEdit,
|
|
3518
3688
|
contributeOnEdit,
|
|
3519
|
-
mergeMetadata,
|
|
3520
3689
|
serviceSource,
|
|
3521
3690
|
userSource,
|
|
3522
3691
|
REVIEW_KEY,
|
|
@@ -3527,4 +3696,15 @@ export {
|
|
|
3527
3696
|
withReviewedUser,
|
|
3528
3697
|
writerPolicy,
|
|
3529
3698
|
} from "./provenance.js";
|
|
3699
|
+
// Metadata ops: the body of a metadata PATCH and of a bulk update entry's
|
|
3700
|
+
// `metadata`. See ./metadataOps.js.
|
|
3701
|
+
export {
|
|
3702
|
+
metadataOps,
|
|
3703
|
+
applyMetadataOps,
|
|
3704
|
+
mergeMetadata,
|
|
3705
|
+
isReservedMetadataKey,
|
|
3706
|
+
} from "./metadataOps.js";
|
|
3707
|
+
// The ids a create or bulk create answered with, off the call or its batch
|
|
3708
|
+
// result. See ./created.js.
|
|
3709
|
+
export { createdId, createdIds } from "./created.js";
|
|
3530
3710
|
export { MAX_BATCH_OPS };
|