@larc-iu/plaid-client 0.2.0-alpha.16 → 0.2.0-alpha.18
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 +409 -65
- package/package.json +1 -1
- package/src/created.js +29 -0
- package/src/documentLock.js +14 -9
- package/src/events.js +256 -0
- package/src/http.js +168 -56
- package/src/index.js +495 -208
- package/src/metadataOps.js +117 -0
- package/src/provenance.js +7 -17
- package/src/requester.js +56 -0
- package/src/services.js +62 -7
- package/src/sse.js +29 -3
- package/src/transforms.js +7 -1
package/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { transformRequest, transformResponse } from "./transforms.js";
|
|
|
6
6
|
import {
|
|
7
7
|
makeRequest,
|
|
8
8
|
queueRequest,
|
|
9
|
+
restampDocumentVersion,
|
|
9
10
|
extractDocumentVersions,
|
|
10
11
|
parseErrorBody,
|
|
11
12
|
makeHttpError,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
DEFAULT_BATCH_TIMEOUT_MS,
|
|
17
18
|
} from "./http.js";
|
|
18
19
|
import { listAll, listPage, iterPages } from "./pagination.js";
|
|
20
|
+
import { recorderFor } from "./events.js";
|
|
19
21
|
import { withDocumentLock } from "./documentLock.js";
|
|
20
22
|
import { createSSEConnection } from "./sse.js";
|
|
21
23
|
import {
|
|
@@ -128,6 +130,9 @@ function openBatch(client) {
|
|
|
128
130
|
const batch = Object.create(client);
|
|
129
131
|
batch.client = client;
|
|
130
132
|
batch.operations = [];
|
|
133
|
+
// The document each queued op's strict-mode stamp is for (null for none),
|
|
134
|
+
// index for index with `operations`.
|
|
135
|
+
batch.stampedDocuments = [];
|
|
131
136
|
batch.open = true;
|
|
132
137
|
batch._request = (method, path, options = {}) =>
|
|
133
138
|
queueRequest(batch, method, path, options);
|
|
@@ -138,6 +143,7 @@ function openBatch(client) {
|
|
|
138
143
|
batch.submit = () => submitBatch(batch);
|
|
139
144
|
batch.abort = () => {
|
|
140
145
|
batch.operations = [];
|
|
146
|
+
batch.stampedDocuments = [];
|
|
141
147
|
batch.open = false;
|
|
142
148
|
};
|
|
143
149
|
batch._installResources();
|
|
@@ -150,23 +156,66 @@ async function submitBatch(batch) {
|
|
|
150
156
|
}
|
|
151
157
|
batch.open = false;
|
|
152
158
|
const ops = batch.operations;
|
|
159
|
+
const stamps = batch.stampedDocuments;
|
|
153
160
|
batch.operations = [];
|
|
161
|
+
batch.stampedDocuments = [];
|
|
154
162
|
if (ops.length === 0) return [];
|
|
155
|
-
const
|
|
163
|
+
const client = batch.client;
|
|
164
|
+
const url = `${client.baseUrl}/api/v1/batch`;
|
|
156
165
|
// The server caps a batch at MAX_BATCH_OPS so one transaction cannot hold
|
|
157
166
|
// the write lock without bound. A larger batch goes as consecutive
|
|
158
167
|
// requests, results concatenated in queue order: it could not have been one
|
|
159
168
|
// transaction anyway, and a repair or bulk edit over a big document must
|
|
160
|
-
// not fail on its size alone.
|
|
169
|
+
// not fail on its size alone. Past the first request, a strict-mode stamp
|
|
170
|
+
// claims the version the requests before it left (learned from their
|
|
171
|
+
// results), not the one the document had when the op was queued, which the
|
|
172
|
+
// first request has already moved on.
|
|
173
|
+
//
|
|
174
|
+
// Each request is atomic, the whole is not: a failure leaves the requests
|
|
175
|
+
// before it saved. The error says so, as `committed` (how many operations
|
|
176
|
+
// were saved) and `committedResults` (their results, in queue order), so a
|
|
177
|
+
// caller can tell a partial write from a batch that saved nothing. The
|
|
178
|
+
// failed request itself counts as unsaved, even when its answer was lost.
|
|
161
179
|
const results = [];
|
|
162
|
-
|
|
163
|
-
results
|
|
164
|
-
|
|
165
|
-
)
|
|
180
|
+
try {
|
|
181
|
+
await sendChunks(client, url, ops, stamps, results);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (error && typeof error === "object") {
|
|
184
|
+
error.committed = results.length;
|
|
185
|
+
error.committedResults = results;
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
166
188
|
}
|
|
167
189
|
return results;
|
|
168
190
|
}
|
|
169
191
|
|
|
192
|
+
async function sendChunks(client, url, ops, stamps, results) {
|
|
193
|
+
for (let i = 0; i < ops.length; i += MAX_BATCH_OPS) {
|
|
194
|
+
let chunk = ops.slice(i, i + MAX_BATCH_OPS);
|
|
195
|
+
if (i > 0) {
|
|
196
|
+
chunk = chunk.map((op, j) => {
|
|
197
|
+
const docId = stamps[i + j];
|
|
198
|
+
if (!docId) return op;
|
|
199
|
+
const path = restampDocumentVersion(
|
|
200
|
+
op.path,
|
|
201
|
+
client.documentVersions[docId],
|
|
202
|
+
);
|
|
203
|
+
return { ...op, path };
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
results.push(...(await client._postBatch(url, chunk)));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The query of an events read: `types` as the wire's comma-separated list. */
|
|
211
|
+
function eventQuery(types, startTime, endTime) {
|
|
212
|
+
return {
|
|
213
|
+
types: Array.isArray(types) ? types.join(",") || undefined : types,
|
|
214
|
+
"start-time": startTime,
|
|
215
|
+
"end-time": endTime,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
170
219
|
class PlaidClient {
|
|
171
220
|
/**
|
|
172
221
|
* Create a new PlaidClient instance
|
|
@@ -193,6 +242,9 @@ class PlaidClient {
|
|
|
193
242
|
? options.timeout
|
|
194
243
|
: DEFAULT_BATCH_TIMEOUT_MS;
|
|
195
244
|
this.documentVersions = {};
|
|
245
|
+
// The server's clock minus this machine's, in ms, from the last response
|
|
246
|
+
// with a Date header (null before one). See serverNow().
|
|
247
|
+
this.serverClockOffsetMs = null;
|
|
196
248
|
this.strictModeDocumentId = null;
|
|
197
249
|
// Set to a DocumentLockLost while a `documents.locked()` block's keep-alive
|
|
198
250
|
// has failed. Every write throws it until the block exits; see
|
|
@@ -212,6 +264,17 @@ class PlaidClient {
|
|
|
212
264
|
this._installResources();
|
|
213
265
|
}
|
|
214
266
|
|
|
267
|
+
/**
|
|
268
|
+
* The server's time now, as its last response's Date header put it (to the
|
|
269
|
+
* second), else this machine's. Judge a time the server stamped, such as an
|
|
270
|
+
* audit entry's `ts`, against this rather than `Date.now()`: a browser
|
|
271
|
+
* clock can be minutes off.
|
|
272
|
+
* @returns {Date}
|
|
273
|
+
*/
|
|
274
|
+
serverNow() {
|
|
275
|
+
return new Date(Date.now() + (this.serverClockOffsetMs ?? 0));
|
|
276
|
+
}
|
|
277
|
+
|
|
215
278
|
/**
|
|
216
279
|
* The API bundles (`client.documents`, `client.tokens`, ...). Installed on
|
|
217
280
|
* the client, and again on every batch it opens (see `batch()`): a batch is
|
|
@@ -261,7 +324,6 @@ class PlaidClient {
|
|
|
261
324
|
this._request("PUT", `/api/v1/vocab-links/${id}/metadata`, {
|
|
262
325
|
auditMessage,
|
|
263
326
|
rawBody: body,
|
|
264
|
-
skipResponseTransform: true,
|
|
265
327
|
}),
|
|
266
328
|
/**
|
|
267
329
|
* Remove all metadata from a vocab link.
|
|
@@ -270,28 +332,23 @@ class PlaidClient {
|
|
|
270
332
|
deleteMetadata: (id, auditMessage) =>
|
|
271
333
|
this._request("DELETE", `/api/v1/vocab-links/${id}/metadata`, {
|
|
272
334
|
auditMessage,
|
|
273
|
-
skipResponseTransform: true,
|
|
274
335
|
}),
|
|
275
336
|
/**
|
|
276
|
-
*
|
|
337
|
+
* 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.
|
|
277
338
|
* @param {string} id - The resource ID
|
|
278
|
-
* @param {any} body - The metadata
|
|
339
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
279
340
|
*/
|
|
280
341
|
patchMetadata: (id, body, auditMessage) =>
|
|
281
342
|
this._request("PATCH", `/api/v1/vocab-links/${id}/metadata`, {
|
|
282
343
|
auditMessage,
|
|
283
344
|
rawBody: body,
|
|
284
|
-
skipResponseTransform: true,
|
|
285
345
|
}),
|
|
286
346
|
/**
|
|
287
347
|
* Get a vocab link by ID
|
|
288
348
|
* @param {string} id - The resource ID
|
|
289
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
290
349
|
*/
|
|
291
|
-
get: (id
|
|
292
|
-
this._request("GET", `/api/v1/vocab-links/${id}
|
|
293
|
-
queryParams: { "as-of": asOf },
|
|
294
|
-
}),
|
|
350
|
+
get: (id) =>
|
|
351
|
+
this._request("GET", `/api/v1/vocab-links/${id}`),
|
|
295
352
|
/**
|
|
296
353
|
* Delete a vocab link
|
|
297
354
|
* @param {string} id - The resource ID
|
|
@@ -302,15 +359,113 @@ class PlaidClient {
|
|
|
302
359
|
|
|
303
360
|
this.vocabLayers = {
|
|
304
361
|
/**
|
|
305
|
-
* Get a vocab layer by ID
|
|
362
|
+
* Get a vocab layer by ID. With `asOf`, the vocabulary as it was at that
|
|
363
|
+
* instant, read from its history: the same shape as the live read, its
|
|
364
|
+
* entries with `includeItems`. A vocabulary that did not exist then is a
|
|
365
|
+
* 404, a malformed or pruned time a 400.
|
|
306
366
|
* @param {string} id - The resource ID
|
|
307
367
|
* @param {boolean} [includeItems] - Include vocab items
|
|
308
|
-
* @param {string} [asOf] -
|
|
368
|
+
* @param {string} [asOf] - The moment to read at (ISO-8601 instant)
|
|
309
369
|
*/
|
|
310
370
|
get: (id, includeItems, asOf) =>
|
|
311
371
|
this._request("GET", `/api/v1/vocab-layers/${id}`, {
|
|
312
372
|
queryParams: { "include-items": includeItems, "as-of": asOf },
|
|
313
373
|
}),
|
|
374
|
+
/**
|
|
375
|
+
* One entry of the vocabulary as it was at `asOf`, also when it has
|
|
376
|
+
* been deleted since. The same shape as `vocabItems.get`. A 404 when
|
|
377
|
+
* the entry was not in this vocabulary at that time.
|
|
378
|
+
* @param {string} id - The vocabulary ID
|
|
379
|
+
* @param {string} itemId - The entry ID
|
|
380
|
+
* @param {string} asOf - The moment to read at (ISO-8601 instant)
|
|
381
|
+
*/
|
|
382
|
+
getItemAt: (id, itemId, asOf) =>
|
|
383
|
+
this._request("GET", `/api/v1/vocab-layers/${id}/items/${itemId}`, {
|
|
384
|
+
queryParams: { "as-of": asOf },
|
|
385
|
+
}),
|
|
386
|
+
/**
|
|
387
|
+
* Get the audit log of a vocabulary: every change to it or to its
|
|
388
|
+
* entries, folded into entries as the document log is. Links are not
|
|
389
|
+
* listed, they belong to the document they annotate. Transparently
|
|
390
|
+
* follows pagination cursors and returns the full flat array.
|
|
391
|
+
* @param {string} id - The vocabulary ID
|
|
392
|
+
* @param {string} [startTime] - Start of time range
|
|
393
|
+
* @param {string} [endTime] - End of time range
|
|
394
|
+
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
395
|
+
* types, spelled as in an entry's `op/type` (e.g.
|
|
396
|
+
* `['vocab-item/delete', 'vocab-item/restore']`)
|
|
397
|
+
* @param {string} [itemId] - Only the changes that wrote this one entry,
|
|
398
|
+
* each with only its operations that did
|
|
399
|
+
*/
|
|
400
|
+
audit: (id, startTime, endTime, opTypes, itemId) =>
|
|
401
|
+
listAll(this, `/api/v1/vocab-layers/${id}/audit`, {
|
|
402
|
+
query: {
|
|
403
|
+
"start-time": startTime,
|
|
404
|
+
"end-time": endTime,
|
|
405
|
+
"op-types": opTypesParam(opTypes),
|
|
406
|
+
"item-id": itemId,
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
409
|
+
/**
|
|
410
|
+
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
411
|
+
* rather than audit() wherever the caller wants the recent end of a log
|
|
412
|
+
* that may be long: audit() walks every page before it resolves.
|
|
413
|
+
* @param {string} id - The vocabulary ID
|
|
414
|
+
* @param {object} [opts]
|
|
415
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
416
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
417
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
418
|
+
* @param {string} [opts.itemId] - Only the changes that wrote this one
|
|
419
|
+
* entry, each with only its operations that did
|
|
420
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
421
|
+
*/
|
|
422
|
+
auditPage: (
|
|
423
|
+
id,
|
|
424
|
+
{ startTime, endTime, opTypes, order, limit, cursor, itemId } = {},
|
|
425
|
+
) =>
|
|
426
|
+
listPage(this, `/api/v1/vocab-layers/${id}/audit`, {
|
|
427
|
+
limit,
|
|
428
|
+
cursor,
|
|
429
|
+
query: {
|
|
430
|
+
"start-time": startTime,
|
|
431
|
+
"end-time": endTime,
|
|
432
|
+
"op-types": opTypesParam(opTypes),
|
|
433
|
+
order,
|
|
434
|
+
"item-id": itemId,
|
|
435
|
+
},
|
|
436
|
+
}),
|
|
437
|
+
/**
|
|
438
|
+
* Put one entry of the vocabulary back as it was at `asOf`, as one
|
|
439
|
+
* operation. A deleted entry comes back under its original id with its
|
|
440
|
+
* form and fields, and a living one has its form and fields set back.
|
|
441
|
+
* Links are not part of an entry: a deleted entry's links come back
|
|
442
|
+
* through each document's own `documents.restore`. Resolves to
|
|
443
|
+
* `{inserted, form, metadata, total}`, `total` 0 when nothing changes.
|
|
444
|
+
* A form set back bumps every linking document, and the new versions
|
|
445
|
+
* come back in X-Document-Versions (or, past fifty documents,
|
|
446
|
+
* X-Document-Versions-Omitted), which the client takes up as on any
|
|
447
|
+
* write. A time when the entry did not exist is a 400. Maintainers of
|
|
448
|
+
* the vocabulary only.
|
|
449
|
+
* @param {string} id - The vocabulary ID
|
|
450
|
+
* @param {string} itemId - The entry ID
|
|
451
|
+
* @param {string} asOf - The moment to go back to (ISO-8601 instant),
|
|
452
|
+
* typically a history entry's `endTime`
|
|
453
|
+
* @param {object} [options] - `{ dryRun }`: with `dryRun` true nothing
|
|
454
|
+
* is written and the summary says what would change
|
|
455
|
+
* @param {string} [auditMessage] - Custom audit message for this operation
|
|
456
|
+
*/
|
|
457
|
+
restoreItem: (id, itemId, asOf, { dryRun } = {}, auditMessage) =>
|
|
458
|
+
this._request(
|
|
459
|
+
"POST",
|
|
460
|
+
`/api/v1/vocab-layers/${id}/items/${itemId}/restore`,
|
|
461
|
+
{
|
|
462
|
+
queryParams: {
|
|
463
|
+
"as-of": asOf,
|
|
464
|
+
"dry-run": dryRun ? "true" : undefined,
|
|
465
|
+
},
|
|
466
|
+
auditMessage,
|
|
467
|
+
},
|
|
468
|
+
),
|
|
314
469
|
/**
|
|
315
470
|
* Delete a vocab layer.
|
|
316
471
|
* @param {string} id - The resource ID
|
|
@@ -355,35 +510,29 @@ class PlaidClient {
|
|
|
355
510
|
/**
|
|
356
511
|
* List all vocab layers accessible to user. Transparently follows
|
|
357
512
|
* pagination cursors and returns the full flat array.
|
|
358
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
359
513
|
*/
|
|
360
|
-
list: (
|
|
361
|
-
listAll(this, "/api/v1/vocab-layers", { query: { "as-of": asOf } }),
|
|
514
|
+
list: () => listAll(this, "/api/v1/vocab-layers"),
|
|
362
515
|
/**
|
|
363
516
|
* Fetch a single page of vocab layers.
|
|
364
517
|
* @param {object} [opts]
|
|
365
518
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
366
519
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
367
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
368
520
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
369
521
|
*/
|
|
370
|
-
listPage: ({ limit, cursor
|
|
522
|
+
listPage: ({ limit, cursor } = {}) =>
|
|
371
523
|
listPage(this, "/api/v1/vocab-layers", {
|
|
372
524
|
limit,
|
|
373
525
|
cursor,
|
|
374
|
-
query: { "as-of": asOf },
|
|
375
526
|
}),
|
|
376
527
|
/**
|
|
377
528
|
* Async-iterate vocab layers page by page; yields each page's entries array.
|
|
378
529
|
* @param {object} [opts]
|
|
379
530
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
380
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
381
531
|
* @returns {AsyncGenerator<Array>}
|
|
382
532
|
*/
|
|
383
|
-
iterPages: ({ pageSize
|
|
533
|
+
iterPages: ({ pageSize } = {}) =>
|
|
384
534
|
iterPages(this, "/api/v1/vocab-layers", {
|
|
385
535
|
pageSize,
|
|
386
|
-
query: { "as-of": asOf },
|
|
387
536
|
}),
|
|
388
537
|
/**
|
|
389
538
|
* Create a new vocab layer. Note: this also registers the user as a maintainer.
|
|
@@ -428,7 +577,6 @@ class PlaidClient {
|
|
|
428
577
|
this._request("PUT", `/api/v1/relations/${relationId}/metadata`, {
|
|
429
578
|
auditMessage,
|
|
430
579
|
rawBody: body,
|
|
431
|
-
skipResponseTransform: true,
|
|
432
580
|
}),
|
|
433
581
|
/**
|
|
434
582
|
* Remove all metadata from a relation.
|
|
@@ -437,18 +585,16 @@ class PlaidClient {
|
|
|
437
585
|
deleteMetadata: (relationId, auditMessage) =>
|
|
438
586
|
this._request("DELETE", `/api/v1/relations/${relationId}/metadata`, {
|
|
439
587
|
auditMessage,
|
|
440
|
-
skipResponseTransform: true,
|
|
441
588
|
}),
|
|
442
589
|
/**
|
|
443
|
-
*
|
|
590
|
+
* 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.
|
|
444
591
|
* @param {string} relationId - The relation ID
|
|
445
|
-
* @param {any} body - The metadata
|
|
592
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
446
593
|
*/
|
|
447
594
|
patchMetadata: (relationId, body, auditMessage) =>
|
|
448
595
|
this._request("PATCH", `/api/v1/relations/${relationId}/metadata`, {
|
|
449
596
|
auditMessage,
|
|
450
597
|
rawBody: body,
|
|
451
|
-
skipResponseTransform: true,
|
|
452
598
|
}),
|
|
453
599
|
/**
|
|
454
600
|
* Update the target span of a relation.
|
|
@@ -463,12 +609,9 @@ class PlaidClient {
|
|
|
463
609
|
/**
|
|
464
610
|
* Get a relation by ID.
|
|
465
611
|
* @param {string} relationId - The relation ID
|
|
466
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
467
612
|
*/
|
|
468
|
-
get: (relationId
|
|
469
|
-
this._request("GET", `/api/v1/relations/${relationId}
|
|
470
|
-
queryParams: { "as-of": asOf },
|
|
471
|
-
}),
|
|
613
|
+
get: (relationId) =>
|
|
614
|
+
this._request("GET", `/api/v1/relations/${relationId}`),
|
|
472
615
|
/**
|
|
473
616
|
* Delete a relation.
|
|
474
617
|
* @param {string} relationId - The relation ID
|
|
@@ -536,7 +679,7 @@ class PlaidClient {
|
|
|
536
679
|
}),
|
|
537
680
|
/**
|
|
538
681
|
* Update many relations in a single operation: set values and/or patch metadata.
|
|
539
|
-
* @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
|
|
682
|
+
* @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.
|
|
540
683
|
* @returns {Promise<{count: number}>} How many relations were updated.
|
|
541
684
|
*/
|
|
542
685
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -581,12 +724,9 @@ class PlaidClient {
|
|
|
581
724
|
/**
|
|
582
725
|
* Get a span layer by ID.
|
|
583
726
|
* @param {string} spanLayerId - The span layer ID
|
|
584
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
585
727
|
*/
|
|
586
|
-
get: (spanLayerId
|
|
587
|
-
this._request("GET", `/api/v1/span-layers/${spanLayerId}
|
|
588
|
-
queryParams: { "as-of": asOf },
|
|
589
|
-
}),
|
|
728
|
+
get: (spanLayerId) =>
|
|
729
|
+
this._request("GET", `/api/v1/span-layers/${spanLayerId}`),
|
|
590
730
|
/**
|
|
591
731
|
* Delete a span layer.
|
|
592
732
|
* @param {string} spanLayerId - The span layer ID
|
|
@@ -659,12 +799,9 @@ class PlaidClient {
|
|
|
659
799
|
/**
|
|
660
800
|
* Get a span by ID.
|
|
661
801
|
* @param {string} spanId - The span ID
|
|
662
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
663
802
|
*/
|
|
664
|
-
get: (spanId
|
|
665
|
-
this._request("GET", `/api/v1/spans/${spanId}
|
|
666
|
-
queryParams: { "as-of": asOf },
|
|
667
|
-
}),
|
|
803
|
+
get: (spanId) =>
|
|
804
|
+
this._request("GET", `/api/v1/spans/${spanId}`),
|
|
668
805
|
/**
|
|
669
806
|
* Delete a span.
|
|
670
807
|
* @param {string} spanId - The span ID
|
|
@@ -696,7 +833,7 @@ class PlaidClient {
|
|
|
696
833
|
this._request("DELETE", "/api/v1/spans/bulk", { auditMessage, body }),
|
|
697
834
|
/**
|
|
698
835
|
* Update many spans in a single operation: set values and/or patch metadata.
|
|
699
|
-
* @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
|
|
836
|
+
* @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.
|
|
700
837
|
* @returns {Promise<{count: number}>} How many spans were updated.
|
|
701
838
|
*/
|
|
702
839
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -713,7 +850,6 @@ class PlaidClient {
|
|
|
713
850
|
this._request("PUT", `/api/v1/spans/${spanId}/metadata`, {
|
|
714
851
|
auditMessage,
|
|
715
852
|
rawBody: body,
|
|
716
|
-
skipResponseTransform: true,
|
|
717
853
|
}),
|
|
718
854
|
/**
|
|
719
855
|
* Remove all metadata from a span.
|
|
@@ -722,18 +858,16 @@ class PlaidClient {
|
|
|
722
858
|
deleteMetadata: (spanId, auditMessage) =>
|
|
723
859
|
this._request("DELETE", `/api/v1/spans/${spanId}/metadata`, {
|
|
724
860
|
auditMessage,
|
|
725
|
-
skipResponseTransform: true,
|
|
726
861
|
}),
|
|
727
862
|
/**
|
|
728
|
-
*
|
|
863
|
+
* 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.
|
|
729
864
|
* @param {string} spanId - The span ID
|
|
730
|
-
* @param {any} body - The metadata
|
|
865
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
731
866
|
*/
|
|
732
867
|
patchMetadata: (spanId, body, auditMessage) =>
|
|
733
868
|
this._request("PATCH", `/api/v1/spans/${spanId}/metadata`, {
|
|
734
869
|
auditMessage,
|
|
735
870
|
rawBody: body,
|
|
736
|
-
skipResponseTransform: true,
|
|
737
871
|
}),
|
|
738
872
|
};
|
|
739
873
|
|
|
@@ -769,6 +903,24 @@ class PlaidClient {
|
|
|
769
903
|
health: () => PlaidClient.health(this.baseUrl, { timeout: this.timeout }),
|
|
770
904
|
};
|
|
771
905
|
|
|
906
|
+
// The signed-in user's sign-ins. Signing in is `PlaidClient.login`, which
|
|
907
|
+
// makes the client.
|
|
908
|
+
this.auth = {
|
|
909
|
+
/**
|
|
910
|
+
* End every sign-in of this user on every device: each browser tab,
|
|
911
|
+
* script and service holding one of the user's sign-in tokens is
|
|
912
|
+
* refused from now on, this client included. Named API tokens are not
|
|
913
|
+
* affected (revoke one with `apiTokens.revoke`). To sign out of one tab
|
|
914
|
+
* only, discard its token instead. A signal rather than project data:
|
|
915
|
+
* made on a batch it still goes out at once, and it never joins an
|
|
916
|
+
* operation.
|
|
917
|
+
* @returns {Promise<void>}
|
|
918
|
+
*/
|
|
919
|
+
logoutEverywhere: async () => {
|
|
920
|
+
await this._request("POST", "/api/v1/logout", { outOfBand: true });
|
|
921
|
+
},
|
|
922
|
+
};
|
|
923
|
+
|
|
772
924
|
this.texts = {
|
|
773
925
|
/**
|
|
774
926
|
* Replace all metadata for a text.
|
|
@@ -779,7 +931,6 @@ class PlaidClient {
|
|
|
779
931
|
this._request("PUT", `/api/v1/texts/${textId}/metadata`, {
|
|
780
932
|
auditMessage,
|
|
781
933
|
rawBody: body,
|
|
782
|
-
skipResponseTransform: true,
|
|
783
934
|
}),
|
|
784
935
|
/**
|
|
785
936
|
* Remove all metadata from a text.
|
|
@@ -788,18 +939,16 @@ class PlaidClient {
|
|
|
788
939
|
deleteMetadata: (textId, auditMessage) =>
|
|
789
940
|
this._request("DELETE", `/api/v1/texts/${textId}/metadata`, {
|
|
790
941
|
auditMessage,
|
|
791
|
-
skipResponseTransform: true,
|
|
792
942
|
}),
|
|
793
943
|
/**
|
|
794
|
-
*
|
|
944
|
+
* 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.
|
|
795
945
|
* @param {string} textId - The text ID
|
|
796
|
-
* @param {any} body - The metadata
|
|
946
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
797
947
|
*/
|
|
798
948
|
patchMetadata: (textId, body, auditMessage) =>
|
|
799
949
|
this._request("PATCH", `/api/v1/texts/${textId}/metadata`, {
|
|
800
950
|
auditMessage,
|
|
801
951
|
rawBody: body,
|
|
802
|
-
skipResponseTransform: true,
|
|
803
952
|
}),
|
|
804
953
|
/**
|
|
805
954
|
* Create a new text in a document's text layer. A text is a container for
|
|
@@ -822,12 +971,9 @@ class PlaidClient {
|
|
|
822
971
|
/**
|
|
823
972
|
* Get a text.
|
|
824
973
|
* @param {string} textId - The text ID
|
|
825
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
826
974
|
*/
|
|
827
|
-
get: (textId
|
|
828
|
-
this._request("GET", `/api/v1/texts/${textId}
|
|
829
|
-
queryParams: { "as-of": asOf },
|
|
830
|
-
}),
|
|
975
|
+
get: (textId) =>
|
|
976
|
+
this._request("GET", `/api/v1/texts/${textId}`),
|
|
831
977
|
/**
|
|
832
978
|
* Delete a text and all dependent data.
|
|
833
979
|
* @param {string} textId - The text ID
|
|
@@ -859,37 +1005,34 @@ class PlaidClient {
|
|
|
859
1005
|
* returns the full flat array. Admin-or-maintainer only.
|
|
860
1006
|
* @param {object} [opts]
|
|
861
1007
|
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
862
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
863
1008
|
*/
|
|
864
|
-
list: ({ q
|
|
865
|
-
listAll(this, "/api/v1/users", { query: { q
|
|
1009
|
+
list: ({ q } = {}) =>
|
|
1010
|
+
listAll(this, "/api/v1/users", { query: { q } }),
|
|
866
1011
|
/**
|
|
867
1012
|
* Fetch a single page of users (optionally filtered by `q`).
|
|
868
1013
|
* @param {object} [opts]
|
|
869
1014
|
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
870
1015
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
871
1016
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
872
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
873
1017
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
874
1018
|
*/
|
|
875
|
-
listPage: ({ q, limit, cursor
|
|
1019
|
+
listPage: ({ q, limit, cursor } = {}) =>
|
|
876
1020
|
listPage(this, "/api/v1/users", {
|
|
877
1021
|
limit,
|
|
878
1022
|
cursor,
|
|
879
|
-
query: { q
|
|
1023
|
+
query: { q },
|
|
880
1024
|
}),
|
|
881
1025
|
/**
|
|
882
1026
|
* Async-iterate users page by page; yields each page's entries array.
|
|
883
1027
|
* @param {object} [opts]
|
|
884
1028
|
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
885
1029
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
886
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
887
1030
|
* @returns {AsyncGenerator<Array>}
|
|
888
1031
|
*/
|
|
889
|
-
iterPages: ({ q, pageSize
|
|
1032
|
+
iterPages: ({ q, pageSize } = {}) =>
|
|
890
1033
|
iterPages(this, "/api/v1/users", {
|
|
891
1034
|
pageSize,
|
|
892
|
-
query: { q
|
|
1035
|
+
query: { q },
|
|
893
1036
|
}),
|
|
894
1037
|
/**
|
|
895
1038
|
* Create a new user.
|
|
@@ -907,7 +1050,9 @@ class PlaidClient {
|
|
|
907
1050
|
email,
|
|
908
1051
|
password,
|
|
909
1052
|
"is-admin": isAdmin,
|
|
910
|
-
|
|
1053
|
+
// Left out when not given (null too), so core gives the user the
|
|
1054
|
+
// local part of the email. Core refuses a null display name.
|
|
1055
|
+
"display-name": displayName ?? undefined,
|
|
911
1056
|
}),
|
|
912
1057
|
}),
|
|
913
1058
|
/**
|
|
@@ -916,18 +1061,16 @@ class PlaidClient {
|
|
|
916
1061
|
* @param {string} userId - The user ID
|
|
917
1062
|
* @param {string} [startTime] - Start of time range
|
|
918
1063
|
* @param {string} [endTime] - End of time range
|
|
919
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
920
1064
|
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
921
1065
|
* types, spelled as in an entry's `op/type` (e.g.
|
|
922
1066
|
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
923
1067
|
* one of its operations matches, carrying only the ones that did.
|
|
924
1068
|
*/
|
|
925
|
-
audit: (userId, startTime, endTime,
|
|
1069
|
+
audit: (userId, startTime, endTime, opTypes) =>
|
|
926
1070
|
listAll(this, `/api/v1/users/${userId}/audit`, {
|
|
927
1071
|
query: {
|
|
928
1072
|
"start-time": startTime,
|
|
929
1073
|
"end-time": endTime,
|
|
930
|
-
"as-of": asOf,
|
|
931
1074
|
"op-types": opTypesParam(opTypes),
|
|
932
1075
|
},
|
|
933
1076
|
}),
|
|
@@ -935,6 +1078,7 @@ class PlaidClient {
|
|
|
935
1078
|
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
936
1079
|
* rather than audit() wherever the caller wants the recent end of a log
|
|
937
1080
|
* that may be long: audit() walks every page before it resolves.
|
|
1081
|
+
* @param {string} userId - The user ID
|
|
938
1082
|
* @param {object} [opts]
|
|
939
1083
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
940
1084
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
@@ -943,7 +1087,7 @@ class PlaidClient {
|
|
|
943
1087
|
*/
|
|
944
1088
|
auditPage: (
|
|
945
1089
|
userId,
|
|
946
|
-
{ startTime, endTime,
|
|
1090
|
+
{ startTime, endTime, opTypes, order, limit, cursor } = {},
|
|
947
1091
|
) =>
|
|
948
1092
|
listPage(this, `/api/v1/users/${userId}/audit`, {
|
|
949
1093
|
limit,
|
|
@@ -951,7 +1095,6 @@ class PlaidClient {
|
|
|
951
1095
|
query: {
|
|
952
1096
|
"start-time": startTime,
|
|
953
1097
|
"end-time": endTime,
|
|
954
|
-
"as-of": asOf,
|
|
955
1098
|
"op-types": opTypesParam(opTypes),
|
|
956
1099
|
order,
|
|
957
1100
|
},
|
|
@@ -959,12 +1102,9 @@ class PlaidClient {
|
|
|
959
1102
|
/**
|
|
960
1103
|
* Get a user by ID
|
|
961
1104
|
* @param {string} id - The resource ID
|
|
962
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
963
1105
|
*/
|
|
964
|
-
get: (id
|
|
965
|
-
this._request("GET", `/api/v1/users/${id}
|
|
966
|
-
queryParams: { "as-of": asOf },
|
|
967
|
-
}),
|
|
1106
|
+
get: (id) =>
|
|
1107
|
+
this._request("GET", `/api/v1/users/${id}`),
|
|
968
1108
|
/**
|
|
969
1109
|
* Deactivate a user. Users are never hard-deleted: deactivation
|
|
970
1110
|
* rejects their logins and tokens, strips their project memberships
|
|
@@ -1142,7 +1282,7 @@ class PlaidClient {
|
|
|
1142
1282
|
),
|
|
1143
1283
|
/**
|
|
1144
1284
|
* Create or replace one private data entry. `value` is any JSON (up to
|
|
1145
|
-
* 1 MB)
|
|
1285
|
+
* 1 MB). Not audited, not batchable.
|
|
1146
1286
|
*
|
|
1147
1287
|
* The server stores it verbatim, but this client recases object keys on
|
|
1148
1288
|
* the way out and back like any other body (`myKey` <-> `my-key`), so a
|
|
@@ -1210,7 +1350,7 @@ class PlaidClient {
|
|
|
1210
1350
|
* credential and is shown ONLY here — store it immediately. API tokens
|
|
1211
1351
|
* do not expire and survive password changes / logout; revoke to kill.
|
|
1212
1352
|
* @param {string} userId - The user ID who will own the token
|
|
1213
|
-
* @param {string} name - A human label, e.g. "Stanza
|
|
1353
|
+
* @param {string} name - A human label, e.g. "Stanza parser"
|
|
1214
1354
|
* @returns {Promise<{id: string, name: string, token: string}>}
|
|
1215
1355
|
*/
|
|
1216
1356
|
create: (userId, name, auditMessage) =>
|
|
@@ -1626,12 +1766,9 @@ class PlaidClient {
|
|
|
1626
1766
|
/**
|
|
1627
1767
|
* Get a token layer by ID.
|
|
1628
1768
|
* @param {string} tokenLayerId - The token layer ID
|
|
1629
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
1630
1769
|
*/
|
|
1631
|
-
get: (tokenLayerId
|
|
1632
|
-
this._request("GET", `/api/v1/token-layers/${tokenLayerId}
|
|
1633
|
-
queryParams: { "as-of": asOf },
|
|
1634
|
-
}),
|
|
1770
|
+
get: (tokenLayerId) =>
|
|
1771
|
+
this._request("GET", `/api/v1/token-layers/${tokenLayerId}`),
|
|
1635
1772
|
/**
|
|
1636
1773
|
* Delete a token layer.
|
|
1637
1774
|
* @param {string} tokenLayerId - The token layer ID
|
|
@@ -1656,14 +1793,15 @@ class PlaidClient {
|
|
|
1656
1793
|
/**
|
|
1657
1794
|
* Check the lock status of a document.
|
|
1658
1795
|
* @param {string} documentId - The document ID
|
|
1659
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
1660
1796
|
*/
|
|
1661
|
-
checkLock: (documentId
|
|
1662
|
-
this._request("GET", `/api/v1/documents/${documentId}/lock
|
|
1663
|
-
queryParams: { "as-of": asOf },
|
|
1664
|
-
}),
|
|
1797
|
+
checkLock: (documentId) =>
|
|
1798
|
+
this._request("GET", `/api/v1/documents/${documentId}/lock`),
|
|
1665
1799
|
/**
|
|
1666
|
-
* Acquire
|
|
1800
|
+
* Acquire a document lock as a new holder.
|
|
1801
|
+
*
|
|
1802
|
+
* The answer's `lockId` names the holder: `renewLock` and `releaseLock`
|
|
1803
|
+
* take it. While the lock is held, a second acquire is refused with
|
|
1804
|
+
* HTTP 423, whoever makes it, this user included.
|
|
1667
1805
|
*
|
|
1668
1806
|
* outOfBand: the lock is a signal, not project data (see the note at
|
|
1669
1807
|
* the top of http.js). Queued on a batch it would be taken only at
|
|
@@ -1678,13 +1816,30 @@ class PlaidClient {
|
|
|
1678
1816
|
outOfBand: true,
|
|
1679
1817
|
}),
|
|
1680
1818
|
/**
|
|
1681
|
-
*
|
|
1819
|
+
* Renew the lock `lockId` holds while it is live. HTTP 423 if another
|
|
1820
|
+
* holder has it, and also once it has expired or been dropped, even when
|
|
1821
|
+
* nobody holds the document now: a renewal never takes a free document.
|
|
1822
|
+
* outOfBand, for the same reason as acquireLock.
|
|
1823
|
+
* @param {string} documentId - The document ID
|
|
1824
|
+
* @param {string} lockId - The `lockId` acquireLock answered with
|
|
1825
|
+
*/
|
|
1826
|
+
renewLock: (documentId, lockId, auditMessage) =>
|
|
1827
|
+
this._request("POST", `/api/v1/documents/${documentId}/lock`, {
|
|
1828
|
+
queryParams: { "lock-id": lockId },
|
|
1829
|
+
auditMessage,
|
|
1830
|
+
outOfBand: true,
|
|
1831
|
+
}),
|
|
1832
|
+
/**
|
|
1833
|
+
* Release the lock `lockId` holds. Idempotent: a lock that holder no
|
|
1834
|
+
* longer has is left alone. outOfBand, for the same reason as
|
|
1682
1835
|
* acquireLock: queued, the lock would be held until the batch submits,
|
|
1683
1836
|
* and not released at all if it aborts.
|
|
1684
1837
|
* @param {string} documentId - The document ID
|
|
1838
|
+
* @param {string} lockId - The `lockId` acquireLock answered with
|
|
1685
1839
|
*/
|
|
1686
|
-
releaseLock: (documentId, auditMessage) =>
|
|
1840
|
+
releaseLock: (documentId, lockId, auditMessage) =>
|
|
1687
1841
|
this._request("DELETE", `/api/v1/documents/${documentId}/lock`, {
|
|
1842
|
+
queryParams: { "lock-id": lockId },
|
|
1688
1843
|
auditMessage,
|
|
1689
1844
|
outOfBand: true,
|
|
1690
1845
|
}),
|
|
@@ -1702,9 +1857,9 @@ class PlaidClient {
|
|
|
1702
1857
|
* tokenizer that deletes and recreates a document's tokens, spans and
|
|
1703
1858
|
* relations. A single atomic call does not need it. While the lock is
|
|
1704
1859
|
* held, writes to the document by ANOTHER user are refused with HTTP
|
|
1705
|
-
* 423; the holder's own writes pass and renew it. If
|
|
1706
|
-
*
|
|
1707
|
-
* run.
|
|
1860
|
+
* 423; the holder's own writes pass and renew it. If anyone already
|
|
1861
|
+
* holds it, another block of this same user included, this rejects with
|
|
1862
|
+
* a readable 423 and `fn` does not run.
|
|
1708
1863
|
*
|
|
1709
1864
|
* The lock is renewed for as long as `fn` runs, so work that computes
|
|
1710
1865
|
* for minutes before it writes holds the lock the whole time rather than
|
|
@@ -1714,11 +1869,13 @@ class PlaidClient {
|
|
|
1714
1869
|
* success. `fn` receives a lock handle and may read `lock.lost` (or call
|
|
1715
1870
|
* `lock.raiseIfLost()`) to give up sooner.
|
|
1716
1871
|
*
|
|
1717
|
-
*
|
|
1718
|
-
*
|
|
1719
|
-
*
|
|
1720
|
-
*
|
|
1721
|
-
*
|
|
1872
|
+
* The lock is per HOLDER: each block is its own, named by
|
|
1873
|
+
* `lock.lockId`, and only that id renews or releases it. NOT
|
|
1874
|
+
* re-entrant: a nested `locked()` block on one document is a second
|
|
1875
|
+
* holder and gets the 423. Lock at exactly one level per call path. A
|
|
1876
|
+
* lost lock is recorded on the CLIENT, like strict mode, so it stops
|
|
1877
|
+
* every write the client makes (on any batch of it too) and not only the
|
|
1878
|
+
* ones this block makes.
|
|
1722
1879
|
* @param {string} documentId - The document ID
|
|
1723
1880
|
* @param {(lock: DocumentLock) => any} fn - The work to run while holding it
|
|
1724
1881
|
* @param {object} [options] - `{ keepAlive }`: renew on a timer (default true)
|
|
@@ -1775,7 +1932,6 @@ class PlaidClient {
|
|
|
1775
1932
|
this._request("PUT", `/api/v1/documents/${documentId}/metadata`, {
|
|
1776
1933
|
auditMessage,
|
|
1777
1934
|
rawBody: body,
|
|
1778
|
-
skipResponseTransform: true,
|
|
1779
1935
|
}),
|
|
1780
1936
|
/**
|
|
1781
1937
|
* Remove all metadata from a document.
|
|
@@ -1784,18 +1940,16 @@ class PlaidClient {
|
|
|
1784
1940
|
deleteMetadata: (documentId, auditMessage) =>
|
|
1785
1941
|
this._request("DELETE", `/api/v1/documents/${documentId}/metadata`, {
|
|
1786
1942
|
auditMessage,
|
|
1787
|
-
skipResponseTransform: true,
|
|
1788
1943
|
}),
|
|
1789
1944
|
/**
|
|
1790
|
-
*
|
|
1945
|
+
* 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.
|
|
1791
1946
|
* @param {string} documentId - The document ID
|
|
1792
|
-
* @param {any} body - The metadata
|
|
1947
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
1793
1948
|
*/
|
|
1794
1949
|
patchMetadata: (documentId, body, auditMessage) =>
|
|
1795
1950
|
this._request("PATCH", `/api/v1/documents/${documentId}/metadata`, {
|
|
1796
1951
|
auditMessage,
|
|
1797
1952
|
rawBody: body,
|
|
1798
|
-
skipResponseTransform: true,
|
|
1799
1953
|
}),
|
|
1800
1954
|
/**
|
|
1801
1955
|
* Get audit log for a document. Transparently follows pagination cursors
|
|
@@ -1803,18 +1957,16 @@ class PlaidClient {
|
|
|
1803
1957
|
* @param {string} documentId - The document ID
|
|
1804
1958
|
* @param {string} [startTime] - Start of time range
|
|
1805
1959
|
* @param {string} [endTime] - End of time range
|
|
1806
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
1807
1960
|
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
1808
1961
|
* types, spelled as in an entry's `op/type` (e.g.
|
|
1809
1962
|
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
1810
1963
|
* one of its operations matches, carrying only the ones that did.
|
|
1811
1964
|
*/
|
|
1812
|
-
audit: (documentId, startTime, endTime,
|
|
1965
|
+
audit: (documentId, startTime, endTime, opTypes) =>
|
|
1813
1966
|
listAll(this, `/api/v1/documents/${documentId}/audit`, {
|
|
1814
1967
|
query: {
|
|
1815
1968
|
"start-time": startTime,
|
|
1816
1969
|
"end-time": endTime,
|
|
1817
|
-
"as-of": asOf,
|
|
1818
1970
|
"op-types": opTypesParam(opTypes),
|
|
1819
1971
|
},
|
|
1820
1972
|
}),
|
|
@@ -1822,6 +1974,7 @@ class PlaidClient {
|
|
|
1822
1974
|
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
1823
1975
|
* rather than audit() wherever the caller wants the recent end of a log
|
|
1824
1976
|
* that may be long: audit() walks every page before it resolves.
|
|
1977
|
+
* @param {string} documentId - The document ID
|
|
1825
1978
|
* @param {object} [opts]
|
|
1826
1979
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
1827
1980
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
@@ -1830,7 +1983,7 @@ class PlaidClient {
|
|
|
1830
1983
|
*/
|
|
1831
1984
|
auditPage: (
|
|
1832
1985
|
documentId,
|
|
1833
|
-
{ startTime, endTime,
|
|
1986
|
+
{ startTime, endTime, opTypes, order, limit, cursor } = {},
|
|
1834
1987
|
) =>
|
|
1835
1988
|
listPage(this, `/api/v1/documents/${documentId}/audit`, {
|
|
1836
1989
|
limit,
|
|
@@ -1838,7 +1991,6 @@ class PlaidClient {
|
|
|
1838
1991
|
query: {
|
|
1839
1992
|
"start-time": startTime,
|
|
1840
1993
|
"end-time": endTime,
|
|
1841
|
-
"as-of": asOf,
|
|
1842
1994
|
"op-types": opTypesParam(opTypes),
|
|
1843
1995
|
order,
|
|
1844
1996
|
},
|
|
@@ -2026,18 +2178,16 @@ class PlaidClient {
|
|
|
2026
2178
|
* @param {string} projectId - The project ID
|
|
2027
2179
|
* @param {string} [startTime] - Start of time range
|
|
2028
2180
|
* @param {string} [endTime] - End of time range
|
|
2029
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2030
2181
|
* @param {string[]|string} [opTypes] - Only return operations of these
|
|
2031
2182
|
* types, spelled as in an entry's `op/type` (e.g.
|
|
2032
2183
|
* `['span-layer/create', 'span-layer/delete']`). An entry appears when
|
|
2033
2184
|
* one of its operations matches, carrying only the ones that did.
|
|
2034
2185
|
*/
|
|
2035
|
-
audit: (projectId, startTime, endTime,
|
|
2186
|
+
audit: (projectId, startTime, endTime, opTypes) =>
|
|
2036
2187
|
listAll(this, `/api/v1/projects/${projectId}/audit`, {
|
|
2037
2188
|
query: {
|
|
2038
2189
|
"start-time": startTime,
|
|
2039
2190
|
"end-time": endTime,
|
|
2040
|
-
"as-of": asOf,
|
|
2041
2191
|
"op-types": opTypesParam(opTypes),
|
|
2042
2192
|
},
|
|
2043
2193
|
}),
|
|
@@ -2045,6 +2195,7 @@ class PlaidClient {
|
|
|
2045
2195
|
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
2046
2196
|
* rather than audit() wherever the caller wants the recent end of a log
|
|
2047
2197
|
* that may be long: audit() walks every page before it resolves.
|
|
2198
|
+
* @param {string} projectId - The project ID
|
|
2048
2199
|
* @param {object} [opts]
|
|
2049
2200
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
2050
2201
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
@@ -2053,7 +2204,7 @@ class PlaidClient {
|
|
|
2053
2204
|
*/
|
|
2054
2205
|
auditPage: (
|
|
2055
2206
|
projectId,
|
|
2056
|
-
{ startTime, endTime,
|
|
2207
|
+
{ startTime, endTime, opTypes, order, limit, cursor } = {},
|
|
2057
2208
|
) =>
|
|
2058
2209
|
listPage(this, `/api/v1/projects/${projectId}/audit`, {
|
|
2059
2210
|
limit,
|
|
@@ -2061,7 +2212,6 @@ class PlaidClient {
|
|
|
2061
2212
|
query: {
|
|
2062
2213
|
"start-time": startTime,
|
|
2063
2214
|
"end-time": endTime,
|
|
2064
|
-
"as-of": asOf,
|
|
2065
2215
|
"op-types": opTypesParam(opTypes),
|
|
2066
2216
|
order,
|
|
2067
2217
|
},
|
|
@@ -2102,12 +2252,9 @@ class PlaidClient {
|
|
|
2102
2252
|
* Get a project by ID. To fetch the project's documents, use
|
|
2103
2253
|
* listDocuments(id) — the include-documents flag has been removed.
|
|
2104
2254
|
* @param {string} id - The resource ID
|
|
2105
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2106
2255
|
*/
|
|
2107
|
-
get: (id
|
|
2108
|
-
this._request("GET", `/api/v1/projects/${id}
|
|
2109
|
-
queryParams: { "as-of": asOf },
|
|
2110
|
-
}),
|
|
2256
|
+
get: (id) =>
|
|
2257
|
+
this._request("GET", `/api/v1/projects/${id}`),
|
|
2111
2258
|
/**
|
|
2112
2259
|
* List all documents in a project. Transparently follows pagination
|
|
2113
2260
|
* cursors and returns the full flat array.
|
|
@@ -2144,17 +2291,15 @@ class PlaidClient {
|
|
|
2144
2291
|
iterDocuments: (id, { pageSize } = {}) =>
|
|
2145
2292
|
iterPages(this, `/api/v1/projects/${id}/documents`, { pageSize }),
|
|
2146
2293
|
/**
|
|
2147
|
-
* Delete a project and everything in it. This is irrecoverable.
|
|
2148
|
-
*
|
|
2149
|
-
*
|
|
2150
|
-
* per-request timeout is DISABLED by default. Pass `{ timeout }` to
|
|
2151
|
-
* impose a finite bound.
|
|
2294
|
+
* Delete a project and everything in it. This is irrecoverable. The
|
|
2295
|
+
* project is gone when this returns, and what it holds is removed on
|
|
2296
|
+
* the server afterwards.
|
|
2152
2297
|
* @param {string} id - The resource ID
|
|
2153
2298
|
* @param {string} [auditMessage] - Custom audit-log message
|
|
2154
2299
|
* @param {object} [options]
|
|
2155
|
-
* @param {number} [options.timeout
|
|
2300
|
+
* @param {number} [options.timeout] - Per-request timeout in ms, the client's own by default (0/null disables)
|
|
2156
2301
|
*/
|
|
2157
|
-
delete: (id, auditMessage, { timeout
|
|
2302
|
+
delete: (id, auditMessage, { timeout } = {}) =>
|
|
2158
2303
|
this._request("DELETE", `/api/v1/projects/${id}`, {
|
|
2159
2304
|
auditMessage,
|
|
2160
2305
|
timeout,
|
|
@@ -2172,35 +2317,29 @@ class PlaidClient {
|
|
|
2172
2317
|
/**
|
|
2173
2318
|
* List all projects accessible to user. Transparently follows pagination
|
|
2174
2319
|
* cursors and returns the full flat array.
|
|
2175
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2176
2320
|
*/
|
|
2177
|
-
list: (
|
|
2178
|
-
listAll(this, "/api/v1/projects", { query: { "as-of": asOf } }),
|
|
2321
|
+
list: () => listAll(this, "/api/v1/projects"),
|
|
2179
2322
|
/**
|
|
2180
2323
|
* Fetch a single page of projects.
|
|
2181
2324
|
* @param {object} [opts]
|
|
2182
2325
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
2183
2326
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
2184
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
2185
2327
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2186
2328
|
*/
|
|
2187
|
-
listPage: ({ limit, cursor
|
|
2329
|
+
listPage: ({ limit, cursor } = {}) =>
|
|
2188
2330
|
listPage(this, "/api/v1/projects", {
|
|
2189
2331
|
limit,
|
|
2190
2332
|
cursor,
|
|
2191
|
-
query: { "as-of": asOf },
|
|
2192
2333
|
}),
|
|
2193
2334
|
/**
|
|
2194
2335
|
* Async-iterate projects page by page; yields each page's entries array.
|
|
2195
2336
|
* @param {object} [opts]
|
|
2196
2337
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
2197
|
-
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
2198
2338
|
* @returns {AsyncGenerator<Array>}
|
|
2199
2339
|
*/
|
|
2200
|
-
iterPages: ({ pageSize
|
|
2340
|
+
iterPages: ({ pageSize } = {}) =>
|
|
2201
2341
|
iterPages(this, "/api/v1/projects", {
|
|
2202
2342
|
pageSize,
|
|
2203
|
-
query: { "as-of": asOf },
|
|
2204
2343
|
}),
|
|
2205
2344
|
/**
|
|
2206
2345
|
* Create a new project. Note: this also registers the user as a maintainer.
|
|
@@ -2248,12 +2387,9 @@ class PlaidClient {
|
|
|
2248
2387
|
/**
|
|
2249
2388
|
* Get a text layer by ID.
|
|
2250
2389
|
* @param {string} textLayerId - The text layer ID
|
|
2251
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2252
2390
|
*/
|
|
2253
|
-
get: (textLayerId
|
|
2254
|
-
this._request("GET", `/api/v1/text-layers/${textLayerId}
|
|
2255
|
-
queryParams: { "as-of": asOf },
|
|
2256
|
-
}),
|
|
2391
|
+
get: (textLayerId) =>
|
|
2392
|
+
this._request("GET", `/api/v1/text-layers/${textLayerId}`),
|
|
2257
2393
|
/**
|
|
2258
2394
|
* Delete a text layer.
|
|
2259
2395
|
* @param {string} textLayerId - The text layer ID
|
|
@@ -2304,7 +2440,6 @@ class PlaidClient {
|
|
|
2304
2440
|
this._request("PUT", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2305
2441
|
auditMessage,
|
|
2306
2442
|
rawBody: body,
|
|
2307
|
-
skipResponseTransform: true,
|
|
2308
2443
|
}),
|
|
2309
2444
|
/**
|
|
2310
2445
|
* Remove all metadata from a vocab item.
|
|
@@ -2313,18 +2448,16 @@ class PlaidClient {
|
|
|
2313
2448
|
deleteMetadata: (id, auditMessage) =>
|
|
2314
2449
|
this._request("DELETE", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2315
2450
|
auditMessage,
|
|
2316
|
-
skipResponseTransform: true,
|
|
2317
2451
|
}),
|
|
2318
2452
|
/**
|
|
2319
|
-
*
|
|
2453
|
+
* 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.
|
|
2320
2454
|
* @param {string} id - The resource ID
|
|
2321
|
-
* @param {any} body - The metadata
|
|
2455
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
2322
2456
|
*/
|
|
2323
2457
|
patchMetadata: (id, body, auditMessage) =>
|
|
2324
2458
|
this._request("PATCH", `/api/v1/vocab-items/${id}/metadata`, {
|
|
2325
2459
|
auditMessage,
|
|
2326
2460
|
rawBody: body,
|
|
2327
|
-
skipResponseTransform: true,
|
|
2328
2461
|
}),
|
|
2329
2462
|
/**
|
|
2330
2463
|
* Create a new vocab item
|
|
@@ -2350,7 +2483,7 @@ class PlaidClient {
|
|
|
2350
2483
|
/**
|
|
2351
2484
|
* Update many vocab items in a single operation: set forms and/or patch
|
|
2352
2485
|
* metadata.
|
|
2353
|
-
* @param {Array<{id: string, form?: string, metadata?:
|
|
2486
|
+
* @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.
|
|
2354
2487
|
* @returns {Promise<{count: number}>} How many vocab items were updated.
|
|
2355
2488
|
*/
|
|
2356
2489
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -2374,12 +2507,9 @@ class PlaidClient {
|
|
|
2374
2507
|
/**
|
|
2375
2508
|
* Get a vocab item by ID
|
|
2376
2509
|
* @param {string} id - The resource ID
|
|
2377
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2378
2510
|
*/
|
|
2379
|
-
get: (id
|
|
2380
|
-
this._request("GET", `/api/v1/vocab-items/${id}
|
|
2381
|
-
queryParams: { "as-of": asOf },
|
|
2382
|
-
}),
|
|
2511
|
+
get: (id) =>
|
|
2512
|
+
this._request("GET", `/api/v1/vocab-items/${id}`),
|
|
2383
2513
|
/**
|
|
2384
2514
|
* Delete a vocab item, and every link to it. Every document holding one
|
|
2385
2515
|
* of those links has its version bumped, and a strict-mode client picks
|
|
@@ -2459,12 +2589,9 @@ class PlaidClient {
|
|
|
2459
2589
|
/**
|
|
2460
2590
|
* Get a relation layer by ID.
|
|
2461
2591
|
* @param {string} relationLayerId - The relation layer ID
|
|
2462
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2463
2592
|
*/
|
|
2464
|
-
get: (relationLayerId
|
|
2465
|
-
this._request("GET", `/api/v1/relation-layers/${relationLayerId}
|
|
2466
|
-
queryParams: { "as-of": asOf },
|
|
2467
|
-
}),
|
|
2593
|
+
get: (relationLayerId) =>
|
|
2594
|
+
this._request("GET", `/api/v1/relation-layers/${relationLayerId}`),
|
|
2468
2595
|
/**
|
|
2469
2596
|
* Delete a relation layer.
|
|
2470
2597
|
* @param {string} relationLayerId - The relation layer ID
|
|
@@ -2527,12 +2654,9 @@ class PlaidClient {
|
|
|
2527
2654
|
/**
|
|
2528
2655
|
* Get a token.
|
|
2529
2656
|
* @param {string} tokenId - The token ID
|
|
2530
|
-
* @param {string} [asOf] - Temporal query timestamp
|
|
2531
2657
|
*/
|
|
2532
|
-
get: (tokenId
|
|
2533
|
-
this._request("GET", `/api/v1/tokens/${tokenId}
|
|
2534
|
-
queryParams: { "as-of": asOf },
|
|
2535
|
-
}),
|
|
2658
|
+
get: (tokenId) =>
|
|
2659
|
+
this._request("GET", `/api/v1/tokens/${tokenId}`),
|
|
2536
2660
|
/**
|
|
2537
2661
|
* Delete a token and remove it from any spans. If this causes a span to
|
|
2538
2662
|
* have no remaining tokens, the span will also be deleted.
|
|
@@ -2570,7 +2694,7 @@ class PlaidClient {
|
|
|
2570
2694
|
this._request("DELETE", "/api/v1/tokens/bulk", { auditMessage, body }),
|
|
2571
2695
|
/**
|
|
2572
2696
|
* Update many tokens in a single operation: patch metadata.
|
|
2573
|
-
* @param {Array} body - Objects of the shape `{id, metadata}`. `metadata` is a
|
|
2697
|
+
* @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.
|
|
2574
2698
|
* @returns {Promise<{count: number}>} How many tokens were updated.
|
|
2575
2699
|
*/
|
|
2576
2700
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -2624,7 +2748,6 @@ class PlaidClient {
|
|
|
2624
2748
|
this._request("PUT", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2625
2749
|
auditMessage,
|
|
2626
2750
|
rawBody: body,
|
|
2627
|
-
skipResponseTransform: true,
|
|
2628
2751
|
}),
|
|
2629
2752
|
/**
|
|
2630
2753
|
* Remove all metadata from a token.
|
|
@@ -2633,18 +2756,16 @@ class PlaidClient {
|
|
|
2633
2756
|
deleteMetadata: (tokenId, auditMessage) =>
|
|
2634
2757
|
this._request("DELETE", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2635
2758
|
auditMessage,
|
|
2636
|
-
skipResponseTransform: true,
|
|
2637
2759
|
}),
|
|
2638
2760
|
/**
|
|
2639
|
-
*
|
|
2761
|
+
* 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.
|
|
2640
2762
|
* @param {string} tokenId - The token ID
|
|
2641
|
-
* @param {any} body - The metadata
|
|
2763
|
+
* @param {Array<{op: string, path: string[], value?: any}>} body - The metadata ops
|
|
2642
2764
|
*/
|
|
2643
2765
|
patchMetadata: (tokenId, body, auditMessage) =>
|
|
2644
2766
|
this._request("PATCH", `/api/v1/tokens/${tokenId}/metadata`, {
|
|
2645
2767
|
auditMessage,
|
|
2646
2768
|
rawBody: body,
|
|
2647
|
-
skipResponseTransform: true,
|
|
2648
2769
|
}),
|
|
2649
2770
|
};
|
|
2650
2771
|
|
|
@@ -2668,13 +2789,17 @@ class PlaidClient {
|
|
|
2668
2789
|
* this a key such as `case-marker` would reach listeners as
|
|
2669
2790
|
* `caseMarker` in JS and `case_marker` in Python. `listen` restores it
|
|
2670
2791
|
* verbatim on the way in.
|
|
2792
|
+
*
|
|
2793
|
+
* A message is not saved and writes nothing to History, so it never
|
|
2794
|
+
* joins an open operation (`withOperation`). Made on a batch it still
|
|
2795
|
+
* queues, so a message queued after the writes goes out after them.
|
|
2671
2796
|
* @param {string} projectId - The UUID of the project to send to
|
|
2672
2797
|
* @param {any} data - The message data to send
|
|
2673
2798
|
* @returns {Promise<any>} Response from the send operation
|
|
2674
2799
|
*/
|
|
2675
|
-
sendMessage: (projectId, data
|
|
2800
|
+
sendMessage: (projectId, data) =>
|
|
2676
2801
|
this._request("POST", `/api/v1/projects/${projectId}/message`, {
|
|
2677
|
-
|
|
2802
|
+
noOperation: true,
|
|
2678
2803
|
rawBody: { body: data },
|
|
2679
2804
|
}),
|
|
2680
2805
|
|
|
@@ -2707,7 +2832,9 @@ class PlaidClient {
|
|
|
2707
2832
|
* @param {function} onServiceRequest - Callback (data, responseHelper)
|
|
2708
2833
|
* @param {Object} [extras] - Optional additional service metadata
|
|
2709
2834
|
* @param {function} [onStatus] - Optional callback (event, projectId, detail) for
|
|
2710
|
-
* connection transitions: 'registered', 'reconnected', 'disconnected'
|
|
2835
|
+
* connection transitions: 'registered', 'reconnected', 'disconnected',
|
|
2836
|
+
* and 'stopped' when the server refuses the channel for good, which
|
|
2837
|
+
* ends the registration
|
|
2711
2838
|
* @returns {Object} Service registration with .stop(), .isRunning(), .isConnected()
|
|
2712
2839
|
*/
|
|
2713
2840
|
serve: (projectId, serviceInfo, onServiceRequest, extras, onStatus) =>
|
|
@@ -2729,7 +2856,7 @@ class PlaidClient {
|
|
|
2729
2856
|
* @param {number} [timeout=10000] - Timeout in ms
|
|
2730
2857
|
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
2731
2858
|
* @param {AbortSignal} [signal] - Abort to stop waiting; rejects with an AbortError
|
|
2732
|
-
* @param {Object} [opts] - {requestId, onAccepted}
|
|
2859
|
+
* @param {Object} [opts] - {requestId, onAccepted, projectIds, noOperation}
|
|
2733
2860
|
* @returns {Promise<any>} The service's result
|
|
2734
2861
|
*/
|
|
2735
2862
|
requestService: (
|
|
@@ -2827,18 +2954,15 @@ class PlaidClient {
|
|
|
2827
2954
|
* @param {Object} body - The query AST ({find, where, scope?, limit?, orderBy?,
|
|
2828
2955
|
* return?, bindings?}).
|
|
2829
2956
|
* @returns {Promise<Object>} For 'ids'/'entities': {columns, results, count, truncated}.
|
|
2830
|
-
* For 'count': {return: 'count', count}
|
|
2831
|
-
*
|
|
2957
|
+
* For 'count': {return: 'count', count, truncated}, where `truncated` says
|
|
2958
|
+
* the count stopped at the server's cap. Entity cells are full entity
|
|
2959
|
+
* objects (same shape as the GET endpoints).
|
|
2832
2960
|
*/
|
|
2833
2961
|
// outOfBand: a query is a read that travels as a POST (see the note at
|
|
2834
2962
|
// the top of http.js). Made on a batch it goes over the wire like any
|
|
2835
2963
|
// read, and it never joins a logical operation.
|
|
2836
|
-
this.query = (body
|
|
2837
|
-
this._request("POST", "/api/v1/query", {
|
|
2838
|
-
auditMessage,
|
|
2839
|
-
body,
|
|
2840
|
-
outOfBand: true,
|
|
2841
|
-
});
|
|
2964
|
+
this.query = (body) =>
|
|
2965
|
+
this._request("POST", "/api/v1/query", { body, outOfBand: true });
|
|
2842
2966
|
|
|
2843
2967
|
// Logical-operation groups (audit-log grouping). There is no create: a
|
|
2844
2968
|
// group row is made lazily by the first write carrying `?group-id=`
|
|
@@ -3015,6 +3139,20 @@ class PlaidClient {
|
|
|
3015
3139
|
cursor,
|
|
3016
3140
|
query: { "entity-id": entityId },
|
|
3017
3141
|
}),
|
|
3142
|
+
/**
|
|
3143
|
+
* Async-iterate a vocabulary's comments page by page, oldest first;
|
|
3144
|
+
* yields each page's entries array.
|
|
3145
|
+
* @param {string} vocabId - The vocab layer to read
|
|
3146
|
+
* @param {object} [opts]
|
|
3147
|
+
* @param {string} [opts.entityId] - One entry's thread
|
|
3148
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
3149
|
+
* @returns {AsyncGenerator<Array>}
|
|
3150
|
+
*/
|
|
3151
|
+
iterInVocabPages: (vocabId, { entityId, pageSize } = {}) =>
|
|
3152
|
+
iterPages(this, `/api/v1/vocab-layers/${vocabId}/comments`, {
|
|
3153
|
+
pageSize,
|
|
3154
|
+
query: { "entity-id": entityId },
|
|
3155
|
+
}),
|
|
3018
3156
|
/**
|
|
3019
3157
|
* Comment counts per entry of a vocabulary, as an `{entityId: n}` map.
|
|
3020
3158
|
* The response is NOT key-transformed: its keys are entry ids.
|
|
@@ -3034,6 +3172,100 @@ class PlaidClient {
|
|
|
3034
3172
|
),
|
|
3035
3173
|
};
|
|
3036
3174
|
|
|
3175
|
+
this.events = {
|
|
3176
|
+
/**
|
|
3177
|
+
* Record one research-telemetry event (core manual, "Research
|
|
3178
|
+
* telemetry"). Fire and forget: the event is buffered and sent with
|
|
3179
|
+
* others every ten seconds, at fifty events, and when the page is
|
|
3180
|
+
* hidden. A batch that fails is dropped, nothing is retried, and
|
|
3181
|
+
* nothing is ever thrown. Sends nothing for a project whose switch
|
|
3182
|
+
* (`config.plaid.research.telemetry`) is off, which it reads from the
|
|
3183
|
+
* project itself. `suggestion.shown` is recorded once per target,
|
|
3184
|
+
* field and value in a page session.
|
|
3185
|
+
* @param {'suggestion.shown'|'suggestion.adopted'|'suggestion.dismissed'|'plan.opened'} type - The event type
|
|
3186
|
+
* @param {object} fields
|
|
3187
|
+
* @param {string} fields.projectId - The project it happened in
|
|
3188
|
+
* @param {string} [fields.documentId] - The document it happened in
|
|
3189
|
+
* @param {string} [fields.targetId] - What it is about: a token, a plan
|
|
3190
|
+
* @param {object} [fields.data] - Its details, keyed by single lowercase words (`value`, `source`, `field`, `written`, `conversation`)
|
|
3191
|
+
* @returns {boolean} Whether the event was buffered
|
|
3192
|
+
*/
|
|
3193
|
+
record: (type, fields) => recorderFor(this).record(this, type, fields),
|
|
3194
|
+
/**
|
|
3195
|
+
* Send what the recorder holds now, rather than at its next interval.
|
|
3196
|
+
*/
|
|
3197
|
+
flush: () => recorderFor(this).flush(),
|
|
3198
|
+
/**
|
|
3199
|
+
* Tell the recorder a project's switch at once, e.g. right after a
|
|
3200
|
+
* maintainer changed it. Turning it off drops what is buffered.
|
|
3201
|
+
* @param {string} projectId - The project
|
|
3202
|
+
* @param {boolean} on - Whether telemetry is on
|
|
3203
|
+
*/
|
|
3204
|
+
setEnabled: (projectId, on) => recorderFor(this).setEnabled(projectId, on),
|
|
3205
|
+
/**
|
|
3206
|
+
* Record events directly, with no buffering. Requires write access, and
|
|
3207
|
+
* the server refuses with a 403 while the project's switch is off. All
|
|
3208
|
+
* or nothing: one bad event refuses the request with a 400 naming it.
|
|
3209
|
+
* @param {string} projectId - The project the events happened in
|
|
3210
|
+
* @param {Array<{type: string, documentId?: string, targetId?: string, data?: object, clientTs?: string}>} events - At most 500 events
|
|
3211
|
+
* @returns {Promise<{count: number}>}
|
|
3212
|
+
*/
|
|
3213
|
+
create: (projectId, events) =>
|
|
3214
|
+
this._request("POST", `/api/v1/projects/${projectId}/events`, {
|
|
3215
|
+
body: events,
|
|
3216
|
+
outOfBand: true,
|
|
3217
|
+
}),
|
|
3218
|
+
/**
|
|
3219
|
+
* A project's events in arrival order. Maintainer or admin only.
|
|
3220
|
+
* Transparently follows pagination cursors and returns the full flat
|
|
3221
|
+
* array.
|
|
3222
|
+
* @param {string} projectId - The project to read
|
|
3223
|
+
* @param {object} [filters]
|
|
3224
|
+
* @param {string|string[]} [filters.types] - Only these types
|
|
3225
|
+
* @param {string} [filters.startTime] - Only events the server stamped at or after this instant
|
|
3226
|
+
* @param {string} [filters.endTime] - Only events the server stamped at or before this instant
|
|
3227
|
+
*/
|
|
3228
|
+
list: (projectId, { types, startTime, endTime } = {}) =>
|
|
3229
|
+
listAll(this, `/api/v1/projects/${projectId}/events`, {
|
|
3230
|
+
query: eventQuery(types, startTime, endTime),
|
|
3231
|
+
}),
|
|
3232
|
+
/**
|
|
3233
|
+
* One page of a project's events. Maintainer or admin only.
|
|
3234
|
+
* @param {string} projectId - The project to read
|
|
3235
|
+
* @param {object} [opts]
|
|
3236
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
3237
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
3238
|
+
* @param {string|string[]} [opts.types] - Only these types
|
|
3239
|
+
* @param {string} [opts.startTime] - Only events at or after this instant
|
|
3240
|
+
* @param {string} [opts.endTime] - Only events at or before this instant
|
|
3241
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
3242
|
+
*/
|
|
3243
|
+
listPage: (
|
|
3244
|
+
projectId,
|
|
3245
|
+
{ limit, cursor, types, startTime, endTime } = {},
|
|
3246
|
+
) =>
|
|
3247
|
+
listPage(this, `/api/v1/projects/${projectId}/events`, {
|
|
3248
|
+
limit,
|
|
3249
|
+
cursor,
|
|
3250
|
+
query: eventQuery(types, startTime, endTime),
|
|
3251
|
+
}),
|
|
3252
|
+
/**
|
|
3253
|
+
* Async-iterate a project's events page by page; yields each page's entries array.
|
|
3254
|
+
* @param {string} projectId - The project to read
|
|
3255
|
+
* @param {object} [opts]
|
|
3256
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
3257
|
+
* @param {string|string[]} [opts.types] - Only these types
|
|
3258
|
+
* @param {string} [opts.startTime] - Only events at or after this instant
|
|
3259
|
+
* @param {string} [opts.endTime] - Only events at or before this instant
|
|
3260
|
+
* @returns {AsyncGenerator<Array>}
|
|
3261
|
+
*/
|
|
3262
|
+
iterPages: (projectId, { pageSize, types, startTime, endTime } = {}) =>
|
|
3263
|
+
iterPages(this, `/api/v1/projects/${projectId}/events`, {
|
|
3264
|
+
pageSize,
|
|
3265
|
+
query: eventQuery(types, startTime, endTime),
|
|
3266
|
+
}),
|
|
3267
|
+
};
|
|
3268
|
+
|
|
3037
3269
|
this.guidelines = {
|
|
3038
3270
|
/**
|
|
3039
3271
|
* Create a guideline in a project. `title` is the handle an assistant
|
|
@@ -3177,14 +3409,22 @@ class PlaidClient {
|
|
|
3177
3409
|
* "Every write" is the writes of project data. Reads never join, and neither
|
|
3178
3410
|
* do the out-of-band signals shaped like a write (a document lock taken or
|
|
3179
3411
|
* renewed, a stopped service request, a service reporting itself, an admin
|
|
3180
|
-
* control)
|
|
3181
|
-
* label.
|
|
3412
|
+
* control), and neither does a broadcast message (`messages.sendMessage`):
|
|
3413
|
+
* none of them is audited, so there would be nothing under the label.
|
|
3414
|
+
*
|
|
3415
|
+
* `kind` says what kind of operation this is, for a program reading the
|
|
3416
|
+
* log: one of `assistant-plan`, `service-run`, `import`, `bulk-edit`,
|
|
3417
|
+
* `guess-adoption` or `repair` (the server refuses any other). `ref` is a
|
|
3418
|
+
* short string naming what the operation came from, in the shape its kind
|
|
3419
|
+
* documents (the core manual, "Kinds of operation"). Both are recorded from
|
|
3420
|
+
* the first write like the label, and a nested operation keeps the outer
|
|
3421
|
+
* one's.
|
|
3182
3422
|
*
|
|
3183
3423
|
* @param {string} message - Human label for the operation.
|
|
3184
|
-
* @param {object} [opts] - Optional `{ id }
|
|
3424
|
+
* @param {object} [opts] - Optional `{ id, kind, ref }`. `id` adopts an existing group id instead of minting one (a service joining the requester's operation; `requestService` propagates an open operation to the service automatically). `kind` and `ref` are described above.
|
|
3185
3425
|
* @returns {string} The operation's group id.
|
|
3186
3426
|
*/
|
|
3187
|
-
beginOperation(message, { id } = {}) {
|
|
3427
|
+
beginOperation(message, { id, kind, ref } = {}) {
|
|
3188
3428
|
if (this.operationGroup) {
|
|
3189
3429
|
this.operationGroup.depth += 1;
|
|
3190
3430
|
return this.operationGroup.id;
|
|
@@ -3192,6 +3432,8 @@ class PlaidClient {
|
|
|
3192
3432
|
this.operationGroup = {
|
|
3193
3433
|
id: id || crypto.randomUUID(),
|
|
3194
3434
|
message: message == null ? null : String(message),
|
|
3435
|
+
kind: kind == null ? null : String(kind),
|
|
3436
|
+
ref: ref == null ? null : String(ref),
|
|
3195
3437
|
depth: 1,
|
|
3196
3438
|
written: false,
|
|
3197
3439
|
refined: undefined,
|
|
@@ -3238,12 +3480,17 @@ class PlaidClient {
|
|
|
3238
3480
|
* setMessage(`Merged ${n} morphemes`);
|
|
3239
3481
|
* });
|
|
3240
3482
|
*
|
|
3483
|
+
* The kind and reference (see beginOperation) come after `fn`:
|
|
3484
|
+
*
|
|
3485
|
+
* await client.withOperation('Import ELAN corpus', run, { kind: 'import', ref: 'format:elan' });
|
|
3486
|
+
*
|
|
3241
3487
|
* @param {string} message - Human label for the operation.
|
|
3242
3488
|
* @param {function} fn - The work to run; receives `setMessage(msg)` to refine the label once the outcome is known.
|
|
3489
|
+
* @param {object} [opts] - Optional `{ kind, ref }`, as for beginOperation.
|
|
3243
3490
|
* @returns {Promise<any>} Whatever `fn` resolves to.
|
|
3244
3491
|
*/
|
|
3245
|
-
async withOperation(message, fn) {
|
|
3246
|
-
this.beginOperation(message);
|
|
3492
|
+
async withOperation(message, fn, { kind, ref } = {}) {
|
|
3493
|
+
this.beginOperation(message, { kind, ref });
|
|
3247
3494
|
const group = this.operationGroup;
|
|
3248
3495
|
const setMessage = (msg) => {
|
|
3249
3496
|
if (group.depth === 1) group.refined = msg;
|
|
@@ -3259,7 +3506,10 @@ class PlaidClient {
|
|
|
3259
3506
|
* Open a batch: a view of this client with the same bundles, on which every
|
|
3260
3507
|
* write of project data queues instead of going out. `submit()` sends the
|
|
3261
3508
|
* queued operations as ONE atomic request (larger than the server's cap,
|
|
3262
|
-
* as consecutive requests with the results concatenated in queue order
|
|
3509
|
+
* as consecutive requests with the results concatenated in queue order,
|
|
3510
|
+
* each atomic on its own, so a failure in a later one leaves the earlier
|
|
3511
|
+
* ones committed, and the error it throws carries `committed`, the count
|
|
3512
|
+
* saved, and `committedResults`, their results) and
|
|
3263
3513
|
* resolves to one result per operation; `abort()` drops them. A call made
|
|
3264
3514
|
* on the client itself is never touched by an open batch, and a read or an
|
|
3265
3515
|
* out-of-band signal made on the batch goes over the wire now (see the note
|
|
@@ -3280,7 +3530,11 @@ class PlaidClient {
|
|
|
3280
3530
|
return openBatch(this);
|
|
3281
3531
|
}
|
|
3282
3532
|
|
|
3283
|
-
/**
|
|
3533
|
+
/**
|
|
3534
|
+
* POST one batch request (at most MAX_BATCH_OPS operations). Resolves to
|
|
3535
|
+
* one `{ status, headers, body }` per operation, with only the body
|
|
3536
|
+
* recased: the headers keep the server's spelling.
|
|
3537
|
+
*/
|
|
3284
3538
|
async _postBatch(url, body) {
|
|
3285
3539
|
const fetchOptions = {
|
|
3286
3540
|
method: "POST",
|
|
@@ -3305,6 +3559,22 @@ class PlaidClient {
|
|
|
3305
3559
|
throw makeHttpError(res, await parseErrorBody(res), url, "POST");
|
|
3306
3560
|
}
|
|
3307
3561
|
return res;
|
|
3562
|
+
}).catch((error) => {
|
|
3563
|
+
// 401 means the token is missing, expired or invalid: fire the app's
|
|
3564
|
+
// auth-error handler once, exactly as makeRequest does.
|
|
3565
|
+
if (
|
|
3566
|
+
error?.status === 401 &&
|
|
3567
|
+
typeof this.onAuthError === "function" &&
|
|
3568
|
+
!this._authErrorFired
|
|
3569
|
+
) {
|
|
3570
|
+
this._authErrorFired = true;
|
|
3571
|
+
try {
|
|
3572
|
+
this.onAuthError(error);
|
|
3573
|
+
} catch (_) {
|
|
3574
|
+
/* handler must not mask the original error */
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
throw error;
|
|
3308
3578
|
});
|
|
3309
3579
|
|
|
3310
3580
|
const results = await response.json();
|
|
@@ -3332,7 +3602,10 @@ class PlaidClient {
|
|
|
3332
3602
|
}
|
|
3333
3603
|
}
|
|
3334
3604
|
|
|
3335
|
-
return results.map((result) =>
|
|
3605
|
+
return results.map((result) => ({
|
|
3606
|
+
...result,
|
|
3607
|
+
body: transformResponse(result.body),
|
|
3608
|
+
}));
|
|
3336
3609
|
} catch (error) {
|
|
3337
3610
|
if (error.status) throw error;
|
|
3338
3611
|
throw makeNetworkError(error, url, "POST");
|
|
@@ -3340,8 +3613,9 @@ class PlaidClient {
|
|
|
3340
3613
|
}
|
|
3341
3614
|
|
|
3342
3615
|
/**
|
|
3343
|
-
* Run `fn` with a batch, then submit all queued ops as
|
|
3344
|
-
*
|
|
3616
|
+
* Run `fn` with a batch, then submit all queued ops as `submit()` does (ONE
|
|
3617
|
+
* atomic request up to MAX_BATCH_OPS operations, consecutive requests past
|
|
3618
|
+
* it), or abort the batch if `fn` throws. `fn` receives the batch and makes its
|
|
3345
3619
|
* writes on it; it must NOT call `submit()` itself. Resolves to the batch
|
|
3346
3620
|
* results array (`[]` if `fn` queued nothing).
|
|
3347
3621
|
*
|
|
@@ -3366,15 +3640,6 @@ class PlaidClient {
|
|
|
3366
3640
|
return batch.submit();
|
|
3367
3641
|
}
|
|
3368
3642
|
|
|
3369
|
-
/**
|
|
3370
|
-
* Authenticate and return a new client instance with token. This is the
|
|
3371
|
-
* single auth entry point — there is no `client.login` resource.
|
|
3372
|
-
* @param {string} baseUrl - The base URL for the API
|
|
3373
|
-
* @param {string} userId - User ID for authentication
|
|
3374
|
-
* @param {string} password - Password for authentication
|
|
3375
|
-
* @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
|
|
3376
|
-
* @returns {Promise<PlaidClient>} - Authenticated client instance
|
|
3377
|
-
*/
|
|
3378
3643
|
/**
|
|
3379
3644
|
* Liveness, version and database size, with NO authentication and no client
|
|
3380
3645
|
* instance — for a launcher or status page checking whether a server is up
|
|
@@ -3469,6 +3734,15 @@ class PlaidClient {
|
|
|
3469
3734
|
};
|
|
3470
3735
|
}
|
|
3471
3736
|
|
|
3737
|
+
/**
|
|
3738
|
+
* Authenticate and return a new client instance with token. This is the
|
|
3739
|
+
* single auth entry point — there is no `client.login` resource.
|
|
3740
|
+
* @param {string} baseUrl - The base URL for the API
|
|
3741
|
+
* @param {string} userId - User ID for authentication
|
|
3742
|
+
* @param {string} password - Password for authentication
|
|
3743
|
+
* @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
|
|
3744
|
+
* @returns {Promise<PlaidClient>} - Authenticated client instance
|
|
3745
|
+
*/
|
|
3472
3746
|
static async login(baseUrl, userId, password, options = {}) {
|
|
3473
3747
|
baseUrl = baseUrl.replace(/\/$/, "");
|
|
3474
3748
|
const url = `${baseUrl}/api/v1/login`;
|
|
@@ -3541,6 +3815,9 @@ export {
|
|
|
3541
3815
|
// request, and `critical()` holds that off around writes. See ./services.js
|
|
3542
3816
|
// and the manual, "Stopping a request".
|
|
3543
3817
|
export { ServiceCancelled, createCancelScope } from "./services.js";
|
|
3818
|
+
// Who asked for a service run, named in its History label and in what it
|
|
3819
|
+
// stores. See ./requester.js.
|
|
3820
|
+
export { REQUESTED_BY, makeRequester, requesterOf } from "./requester.js";
|
|
3544
3821
|
// Document locks: `client.documents.locked()` holds one for a block of work and
|
|
3545
3822
|
// renews it while the block runs. DocumentLockLost is what a write throws once
|
|
3546
3823
|
// a renewal has failed. See ./documentLock.js.
|
|
@@ -3568,7 +3845,6 @@ export {
|
|
|
3568
3845
|
needsReview,
|
|
3569
3846
|
verifyOnEdit,
|
|
3570
3847
|
contributeOnEdit,
|
|
3571
|
-
mergeMetadata,
|
|
3572
3848
|
serviceSource,
|
|
3573
3849
|
userSource,
|
|
3574
3850
|
REVIEW_KEY,
|
|
@@ -3579,4 +3855,15 @@ export {
|
|
|
3579
3855
|
withReviewedUser,
|
|
3580
3856
|
writerPolicy,
|
|
3581
3857
|
} from "./provenance.js";
|
|
3858
|
+
// Metadata ops: the body of a metadata PATCH and of a bulk update entry's
|
|
3859
|
+
// `metadata`. See ./metadataOps.js.
|
|
3860
|
+
export {
|
|
3861
|
+
metadataOps,
|
|
3862
|
+
applyMetadataOps,
|
|
3863
|
+
mergeMetadata,
|
|
3864
|
+
isReservedMetadataKey,
|
|
3865
|
+
} from "./metadataOps.js";
|
|
3866
|
+
// The ids a create or bulk create answered with, off the call or its batch
|
|
3867
|
+
// result. See ./created.js.
|
|
3868
|
+
export { createdId, createdIds } from "./created.js";
|
|
3582
3869
|
export { MAX_BATCH_OPS };
|