@larc-iu/plaid-client 0.2.0-alpha.13 → 0.2.0-alpha.16
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 +212 -14
- package/package.json +1 -1
- package/src/documentLock.js +299 -0
- package/src/http.js +143 -53
- package/src/index.js +387 -151
- package/src/pagination.js +19 -35
- package/src/serviceSchema.js +27 -1
- package/src/services.js +137 -21
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { transformRequest, transformResponse } from "./transforms.js";
|
|
6
6
|
import {
|
|
7
7
|
makeRequest,
|
|
8
|
+
queueRequest,
|
|
8
9
|
extractDocumentVersions,
|
|
9
10
|
parseErrorBody,
|
|
10
11
|
makeHttpError,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
DEFAULT_BATCH_TIMEOUT_MS,
|
|
16
17
|
} from "./http.js";
|
|
17
18
|
import { listAll, listPage, iterPages } from "./pagination.js";
|
|
19
|
+
import { withDocumentLock } from "./documentLock.js";
|
|
18
20
|
import { createSSEConnection } from "./sse.js";
|
|
19
21
|
import {
|
|
20
22
|
discoverServices,
|
|
@@ -116,6 +118,55 @@ async function anonymousGet(baseUrl, path, options = {}) {
|
|
|
116
118
|
*/
|
|
117
119
|
const MAX_BATCH_OPS = 1000;
|
|
118
120
|
|
|
121
|
+
/**
|
|
122
|
+
* A batch is the client with a different `_request` (see `queueRequest`) and
|
|
123
|
+
* its own copy of the bundles. Everything else resolves to the client through
|
|
124
|
+
* the prototype: the token and base URL, strict mode and the document versions
|
|
125
|
+
* it tracks, the open logical operation.
|
|
126
|
+
*/
|
|
127
|
+
function openBatch(client) {
|
|
128
|
+
const batch = Object.create(client);
|
|
129
|
+
batch.client = client;
|
|
130
|
+
batch.operations = [];
|
|
131
|
+
batch.open = true;
|
|
132
|
+
batch._request = (method, path, options = {}) =>
|
|
133
|
+
queueRequest(batch, method, path, options);
|
|
134
|
+
batch.batch = () => {
|
|
135
|
+
throw new Error("A batch is not nestable: queue on the batch you have");
|
|
136
|
+
};
|
|
137
|
+
batch.batched = batch.batch;
|
|
138
|
+
batch.submit = () => submitBatch(batch);
|
|
139
|
+
batch.abort = () => {
|
|
140
|
+
batch.operations = [];
|
|
141
|
+
batch.open = false;
|
|
142
|
+
};
|
|
143
|
+
batch._installResources();
|
|
144
|
+
return batch;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function submitBatch(batch) {
|
|
148
|
+
if (!batch.open) {
|
|
149
|
+
throw new Error("This batch was already submitted or aborted");
|
|
150
|
+
}
|
|
151
|
+
batch.open = false;
|
|
152
|
+
const ops = batch.operations;
|
|
153
|
+
batch.operations = [];
|
|
154
|
+
if (ops.length === 0) return [];
|
|
155
|
+
const url = `${batch.client.baseUrl}/api/v1/batch`;
|
|
156
|
+
// The server caps a batch at MAX_BATCH_OPS so one transaction cannot hold
|
|
157
|
+
// the write lock without bound. A larger batch goes as consecutive
|
|
158
|
+
// requests, results concatenated in queue order: it could not have been one
|
|
159
|
+
// transaction anyway, and a repair or bulk edit over a big document must
|
|
160
|
+
// not fail on its size alone.
|
|
161
|
+
const results = [];
|
|
162
|
+
for (let i = 0; i < ops.length; i += MAX_BATCH_OPS) {
|
|
163
|
+
results.push(
|
|
164
|
+
...(await batch.client._postBatch(url, ops.slice(i, i + MAX_BATCH_OPS))),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return results;
|
|
168
|
+
}
|
|
169
|
+
|
|
119
170
|
class PlaidClient {
|
|
120
171
|
/**
|
|
121
172
|
* Create a new PlaidClient instance
|
|
@@ -141,10 +192,12 @@ class PlaidClient {
|
|
|
141
192
|
: options.timeout !== undefined
|
|
142
193
|
? options.timeout
|
|
143
194
|
: DEFAULT_BATCH_TIMEOUT_MS;
|
|
144
|
-
this.isBatching = false;
|
|
145
|
-
this.batchOperations = [];
|
|
146
195
|
this.documentVersions = {};
|
|
147
196
|
this.strictModeDocumentId = null;
|
|
197
|
+
// Set to a DocumentLockLost while a `documents.locked()` block's keep-alive
|
|
198
|
+
// has failed. Every write throws it until the block exits; see
|
|
199
|
+
// documentLock.js.
|
|
200
|
+
this.documentLockLost = null;
|
|
148
201
|
// The open logical operation (audit-log group), or null. While set, every
|
|
149
202
|
// write is stamped with `?group-id=` (+ `group-message`) so the audit log
|
|
150
203
|
// folds them into ONE expandable entry. See beginOperation / withOperation.
|
|
@@ -156,8 +209,16 @@ class PlaidClient {
|
|
|
156
209
|
this.onAuthError = options.onAuthError || null;
|
|
157
210
|
this._authErrorFired = false;
|
|
158
211
|
|
|
159
|
-
|
|
212
|
+
this._installResources();
|
|
213
|
+
}
|
|
160
214
|
|
|
215
|
+
/**
|
|
216
|
+
* The API bundles (`client.documents`, `client.tokens`, ...). Installed on
|
|
217
|
+
* the client, and again on every batch it opens (see `batch()`): a batch is
|
|
218
|
+
* a view of the client with its own `_request`, so the same bundles built
|
|
219
|
+
* on it queue instead of sending.
|
|
220
|
+
*/
|
|
221
|
+
_installResources() {
|
|
161
222
|
this.vocabLinks = {
|
|
162
223
|
/**
|
|
163
224
|
* Create a new vocab link between tokens and a vocab item.
|
|
@@ -294,7 +355,6 @@ class PlaidClient {
|
|
|
294
355
|
/**
|
|
295
356
|
* List all vocab layers accessible to user. Transparently follows
|
|
296
357
|
* pagination cursors and returns the full flat array.
|
|
297
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
298
358
|
* @param {string} [asOf] - Temporal query timestamp
|
|
299
359
|
*/
|
|
300
360
|
list: (asOf) =>
|
|
@@ -318,7 +378,6 @@ class PlaidClient {
|
|
|
318
378
|
* @param {object} [opts]
|
|
319
379
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
320
380
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
321
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
322
381
|
* @returns {AsyncGenerator<Array>}
|
|
323
382
|
*/
|
|
324
383
|
iterPages: ({ pageSize, asOf } = {}) =>
|
|
@@ -477,7 +536,7 @@ class PlaidClient {
|
|
|
477
536
|
}),
|
|
478
537
|
/**
|
|
479
538
|
* Update many relations in a single operation: set values and/or patch metadata.
|
|
480
|
-
* @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 patch, a null value deleting that key. The relations may lie in several documents of one project; every document touched has its version bumped. An unknown id refuses the whole update.
|
|
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 patch, a null value deleting that key. 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`. A `document-version` precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update.
|
|
481
540
|
* @returns {Promise<{count: number}>} How many relations were updated.
|
|
482
541
|
*/
|
|
483
542
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -637,7 +696,7 @@ class PlaidClient {
|
|
|
637
696
|
this._request("DELETE", "/api/v1/spans/bulk", { auditMessage, body }),
|
|
638
697
|
/**
|
|
639
698
|
* Update many spans in a single operation: set values and/or patch metadata.
|
|
640
|
-
* @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 patch, a null value deleting that key. The spans may lie in several documents of one project; every document touched has its version bumped. An unknown id refuses the whole update.
|
|
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 patch, a null value deleting that key. 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`. A `document-version` precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update.
|
|
641
700
|
* @returns {Promise<{count: number}>} How many spans were updated.
|
|
642
701
|
*/
|
|
643
702
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -690,11 +749,7 @@ class PlaidClient {
|
|
|
690
749
|
*/
|
|
691
750
|
info: () => {
|
|
692
751
|
if (!infoPromise) {
|
|
693
|
-
|
|
694
|
-
// whatever batch happens to be open on this shared client.
|
|
695
|
-
infoPromise = this._request("GET", "/api/v1/info", {
|
|
696
|
-
bypassBatch: true,
|
|
697
|
-
}).catch((err) => {
|
|
752
|
+
infoPromise = this._request("GET", "/api/v1/info").catch((err) => {
|
|
698
753
|
// A failure must not be cached: a client that starts before the
|
|
699
754
|
// server is up would never see the limits at all.
|
|
700
755
|
infoPromise = null;
|
|
@@ -714,20 +769,6 @@ class PlaidClient {
|
|
|
714
769
|
health: () => PlaidClient.health(this.baseUrl, { timeout: this.timeout }),
|
|
715
770
|
};
|
|
716
771
|
|
|
717
|
-
this.batch = {
|
|
718
|
-
/**
|
|
719
|
-
* Execute multiple API operations atomically. If any operation fails, all
|
|
720
|
-
* changes are rolled back.
|
|
721
|
-
* @param {Array} body - The request body
|
|
722
|
-
*/
|
|
723
|
-
submit: (body, auditMessage) =>
|
|
724
|
-
this._request("POST", "/api/v1/batch", {
|
|
725
|
-
auditMessage,
|
|
726
|
-
body,
|
|
727
|
-
noBatch: true,
|
|
728
|
-
}),
|
|
729
|
-
};
|
|
730
|
-
|
|
731
772
|
this.texts = {
|
|
732
773
|
/**
|
|
733
774
|
* Replace all metadata for a text.
|
|
@@ -816,7 +857,6 @@ class PlaidClient {
|
|
|
816
857
|
/**
|
|
817
858
|
* List (or search) users. Transparently follows pagination cursors and
|
|
818
859
|
* returns the full flat array. Admin-or-maintainer only.
|
|
819
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
820
860
|
* @param {object} [opts]
|
|
821
861
|
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
822
862
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
@@ -844,7 +884,6 @@ class PlaidClient {
|
|
|
844
884
|
* @param {string} [opts.q] - Filter to users whose display name or email contains this text (case-insensitive)
|
|
845
885
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
846
886
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
847
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
848
887
|
* @returns {AsyncGenerator<Array>}
|
|
849
888
|
*/
|
|
850
889
|
iterPages: ({ q, pageSize, asOf } = {}) =>
|
|
@@ -874,7 +913,6 @@ class PlaidClient {
|
|
|
874
913
|
/**
|
|
875
914
|
* Get audit log for a user's actions. Transparently follows pagination
|
|
876
915
|
* cursors and returns the full flat array.
|
|
877
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
878
916
|
* @param {string} userId - The user ID
|
|
879
917
|
* @param {string} [startTime] - Start of time range
|
|
880
918
|
* @param {string} [endTime] - End of time range
|
|
@@ -994,7 +1032,6 @@ class PlaidClient {
|
|
|
994
1032
|
*/
|
|
995
1033
|
getAvatar: (id) =>
|
|
996
1034
|
this._request("GET", `/api/v1/users/${id}/avatar`, {
|
|
997
|
-
noBatch: true,
|
|
998
1035
|
binaryResponse: true,
|
|
999
1036
|
}),
|
|
1000
1037
|
/**
|
|
@@ -1019,32 +1056,79 @@ class PlaidClient {
|
|
|
1019
1056
|
* Remove a profile picture. Your own, or anyone's if you are an admin.
|
|
1020
1057
|
* @param {string} id - The user ID
|
|
1021
1058
|
*/
|
|
1059
|
+
// No flag: the upload beside this one is multipart and cannot be
|
|
1060
|
+
// batched, but a DELETE carries no blob, so the batch transport takes
|
|
1061
|
+
// it. It is an ordinary audited write and queues like any other.
|
|
1022
1062
|
deleteAvatar: (id, auditMessage) =>
|
|
1023
1063
|
this._request("DELETE", `/api/v1/users/${id}/avatar`, {
|
|
1024
1064
|
auditMessage,
|
|
1025
|
-
noBatch: true,
|
|
1026
1065
|
}),
|
|
1027
1066
|
};
|
|
1028
1067
|
|
|
1029
1068
|
this.userData = {
|
|
1030
1069
|
/**
|
|
1031
1070
|
* List a user's private data entries ({key, updatedAt}, plus value when
|
|
1032
|
-
* includeValues). Owner or admin only.
|
|
1071
|
+
* includeValues), ordered by key. Owner or admin only. Transparently
|
|
1072
|
+
* follows pagination cursors and returns the full flat array.
|
|
1033
1073
|
*
|
|
1034
1074
|
* Narrow with `prefix` (the literal head of a key) and/or `pattern`, a
|
|
1035
1075
|
* GLOB over the whole key (`*` any run, `?` one character) — the way to
|
|
1036
1076
|
* ask for a key convention identified by a segment in the middle, e.g.
|
|
1037
1077
|
* `igt:assistant:*:meta:*` for every conversation's sidebar entry across
|
|
1038
1078
|
* every project without dragging down the transcripts beside them.
|
|
1079
|
+
*
|
|
1080
|
+
* `pageSize` is exposed here, and defaults lower than elsewhere, because
|
|
1081
|
+
* one value runs to 1 MB: a page of them with `includeValues` is the
|
|
1082
|
+
* largest response this API can be asked for. Raise it when the listing
|
|
1083
|
+
* is keys, or the values are known to be small.
|
|
1039
1084
|
* @param {string} userId
|
|
1040
1085
|
* @param {object} [opts]
|
|
1041
1086
|
* @param {string} [opts.prefix] - Only keys starting with this prefix
|
|
1042
1087
|
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
1043
1088
|
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
1089
|
+
* @param {number} [opts.pageSize=100] - Entries per request (1..1000)
|
|
1044
1090
|
*/
|
|
1045
|
-
list: (userId, { prefix, pattern, includeValues } = {}) =>
|
|
1046
|
-
this
|
|
1047
|
-
|
|
1091
|
+
list: (userId, { prefix, pattern, includeValues, pageSize = 100 } = {}) =>
|
|
1092
|
+
listAll(this, `/api/v1/users/${userId}/data`, {
|
|
1093
|
+
pageSize,
|
|
1094
|
+
query: { prefix, pattern, "include-values": includeValues },
|
|
1095
|
+
}),
|
|
1096
|
+
/**
|
|
1097
|
+
* One page of a user's private data entries, ordered by key.
|
|
1098
|
+
* @param {string} userId
|
|
1099
|
+
* @param {object} [opts]
|
|
1100
|
+
* @param {string} [opts.prefix] - Only keys starting with this prefix
|
|
1101
|
+
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
1102
|
+
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
1103
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1104
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1105
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1106
|
+
*/
|
|
1107
|
+
listPage: (
|
|
1108
|
+
userId,
|
|
1109
|
+
{ prefix, pattern, includeValues, limit, cursor } = {},
|
|
1110
|
+
) =>
|
|
1111
|
+
listPage(this, `/api/v1/users/${userId}/data`, {
|
|
1112
|
+
limit,
|
|
1113
|
+
cursor,
|
|
1114
|
+
query: { prefix, pattern, "include-values": includeValues },
|
|
1115
|
+
}),
|
|
1116
|
+
/**
|
|
1117
|
+
* Async generator over a user's private data entries, one page at a time.
|
|
1118
|
+
* @param {string} userId
|
|
1119
|
+
* @param {object} [opts]
|
|
1120
|
+
* @param {string} [opts.prefix] - Only keys starting with this prefix
|
|
1121
|
+
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
1122
|
+
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
1123
|
+
* @param {number} [opts.pageSize=100] - Entries per request (1..1000)
|
|
1124
|
+
*/
|
|
1125
|
+
iterPages: (
|
|
1126
|
+
userId,
|
|
1127
|
+
{ prefix, pattern, includeValues, pageSize = 100 } = {},
|
|
1128
|
+
) =>
|
|
1129
|
+
iterPages(this, `/api/v1/users/${userId}/data`, {
|
|
1130
|
+
pageSize,
|
|
1131
|
+
query: { prefix, pattern, "include-values": includeValues },
|
|
1048
1132
|
}),
|
|
1049
1133
|
/**
|
|
1050
1134
|
* Read one private data entry ({key, updatedAt, value}); 404 if absent.
|
|
@@ -1098,7 +1182,6 @@ class PlaidClient {
|
|
|
1098
1182
|
* List a user's named API tokens. Never includes the signed token
|
|
1099
1183
|
* string itself — that is only returned once, by create().
|
|
1100
1184
|
* Transparently follows pagination cursors and returns the full flat array.
|
|
1101
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
1102
1185
|
* @param {string} userId - The user ID who owns the tokens
|
|
1103
1186
|
*/
|
|
1104
1187
|
list: (userId) => listAll(this, `/api/v1/users/${userId}/tokens`),
|
|
@@ -1118,7 +1201,6 @@ class PlaidClient {
|
|
|
1118
1201
|
* @param {string} userId - The user ID who owns the tokens
|
|
1119
1202
|
* @param {object} [opts]
|
|
1120
1203
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
1121
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
1122
1204
|
* @returns {AsyncGenerator<Array>}
|
|
1123
1205
|
*/
|
|
1124
1206
|
iterPages: (userId, { pageSize } = {}) =>
|
|
@@ -1154,7 +1236,6 @@ class PlaidClient {
|
|
|
1154
1236
|
* maintainer or admin on it. Never includes invite codes — the code is
|
|
1155
1237
|
* returned once, by create(), and is not recoverable afterward.
|
|
1156
1238
|
* Transparently follows pagination cursors and returns the full flat array.
|
|
1157
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
1158
1239
|
* @param {object} [opts]
|
|
1159
1240
|
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1160
1241
|
* @param {boolean} [opts.all] - List every invite on the server (admin only)
|
|
@@ -1184,7 +1265,6 @@ class PlaidClient {
|
|
|
1184
1265
|
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1185
1266
|
* @param {boolean} [opts.all] - List every invite on the server (admin only)
|
|
1186
1267
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
1187
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
1188
1268
|
* @returns {AsyncGenerator<Array>}
|
|
1189
1269
|
*/
|
|
1190
1270
|
iterPages: ({ projectId, all, pageSize } = {}) =>
|
|
@@ -1266,8 +1346,12 @@ class PlaidClient {
|
|
|
1266
1346
|
* succeeded. Uses VACUUM INTO, which only reads, so it is safe while
|
|
1267
1347
|
* people are working. Admin only.
|
|
1268
1348
|
* @returns {Promise<{ok: boolean, directory: string, backups: Array}>}
|
|
1349
|
+
*
|
|
1350
|
+
* outOfBand, as every admin action on the server itself is: none of them
|
|
1351
|
+
* writes project data (see the note at the top of http.js).
|
|
1269
1352
|
*/
|
|
1270
|
-
backup: () =>
|
|
1353
|
+
backup: () =>
|
|
1354
|
+
this._request("POST", "/api/v1/admin/backup", { outOfBand: true }),
|
|
1271
1355
|
/**
|
|
1272
1356
|
* Documents currently held by an editing lock, with who holds each and
|
|
1273
1357
|
* when it expires on its own. Admin only.
|
|
@@ -1280,7 +1364,9 @@ class PlaidClient {
|
|
|
1280
1364
|
* @param {string} documentId - The document ID
|
|
1281
1365
|
*/
|
|
1282
1366
|
releaseLock: (documentId) =>
|
|
1283
|
-
this._request("DELETE", `/api/v1/admin/locks/${documentId}
|
|
1367
|
+
this._request("DELETE", `/api/v1/admin/locks/${documentId}`, {
|
|
1368
|
+
outOfBand: true,
|
|
1369
|
+
}),
|
|
1284
1370
|
/**
|
|
1285
1371
|
* Live login and invite rate-limit buckets: the address, the account
|
|
1286
1372
|
* where there is one, failures inside the window, the limit, and whether
|
|
@@ -1298,18 +1384,43 @@ class PlaidClient {
|
|
|
1298
1384
|
*/
|
|
1299
1385
|
clearRateLimits: ({ ip, userId } = {}) =>
|
|
1300
1386
|
this._request("DELETE", "/api/v1/admin/rate-limits", {
|
|
1387
|
+
outOfBand: true,
|
|
1301
1388
|
queryParams: { ip, "user-id": userId },
|
|
1302
1389
|
}),
|
|
1303
1390
|
/**
|
|
1304
|
-
*
|
|
1305
|
-
*
|
|
1306
|
-
*
|
|
1391
|
+
* What the server has logged, structured and filtered, from an in-memory
|
|
1392
|
+
* buffer kept whether or not a log file is configured. `requests` is one
|
|
1393
|
+
* entry per HTTP request (method, path, status, duration, and who made
|
|
1394
|
+
* it), `events` is everything else, with a stack trace where there was
|
|
1395
|
+
* one. They are buffered separately so a burst of requests cannot evict
|
|
1396
|
+
* an error. Both come newest first, `matched` counts what passed the
|
|
1397
|
+
* filters, and request `stats` describe the filtered set. Covers Plaid's
|
|
1398
|
+
* own log stream since the last restart: for library messages and older
|
|
1399
|
+
* history, see `logFile`. Admin only.
|
|
1400
|
+
* @param {object} [opts]
|
|
1401
|
+
* @param {number} [opts.limit] - Max entries per kind (default 200, max 2000)
|
|
1402
|
+
* @param {string} [opts.q] - Substring of any field, case-insensitive
|
|
1403
|
+
* @param {string} [opts.level] - Minimum level for events, e.g. "warn"
|
|
1404
|
+
* @param {string} [opts.status] - "2xx".."5xx", an exact code, or "failures"
|
|
1405
|
+
* @param {string} [opts.user] - Only requests made by this account
|
|
1406
|
+
* @param {string} [opts.method] - Only requests with this HTTP method
|
|
1407
|
+
* @returns {Promise<{requests: object, events: object, file: (string|null)}>}
|
|
1408
|
+
*/
|
|
1409
|
+
logs: ({ limit, q, level, status, user, method } = {}) =>
|
|
1410
|
+
this._request("GET", "/api/v1/admin/logs", {
|
|
1411
|
+
queryParams: { limit, q, level, status, user, method },
|
|
1412
|
+
}),
|
|
1413
|
+
/**
|
|
1414
|
+
* The tail of the configured log file, as text lines. The only place
|
|
1415
|
+
* third-party library messages and anything from before the last restart
|
|
1416
|
+
* can be read. Resolves with an `error` string instead of lines when no
|
|
1417
|
+
* log file is configured or it does not exist yet. Admin only.
|
|
1307
1418
|
* @param {object} [opts]
|
|
1308
1419
|
* @param {number} [opts.lines] - How many lines (default 200, max 2000)
|
|
1309
1420
|
* @returns {Promise<{file: (string|null), lines: string[], error?: string}>}
|
|
1310
1421
|
*/
|
|
1311
|
-
|
|
1312
|
-
this._request("GET", "/api/v1/admin/logs", {
|
|
1422
|
+
logFile: ({ lines } = {}) =>
|
|
1423
|
+
this._request("GET", "/api/v1/admin/logs/file", {
|
|
1313
1424
|
queryParams: { lines },
|
|
1314
1425
|
}),
|
|
1315
1426
|
/**
|
|
@@ -1323,7 +1434,6 @@ class PlaidClient {
|
|
|
1323
1434
|
* ask for a key convention identified by a segment in the middle, e.g.
|
|
1324
1435
|
* `igt:assistant:*:meta:*`. Values come only with `includeValues`, and
|
|
1325
1436
|
* are recased like any other body (see `userData.put`).
|
|
1326
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use userDataPage() for a single page in a batch.
|
|
1327
1437
|
* @param {object} [opts]
|
|
1328
1438
|
* @param {string} [opts.prefix] - Only keys starting with this
|
|
1329
1439
|
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
@@ -1367,7 +1477,6 @@ class PlaidClient {
|
|
|
1367
1477
|
* and op-type filter as the per-project read, with the entity scope
|
|
1368
1478
|
* dropped. Admin only. Transparently follows pagination cursors and
|
|
1369
1479
|
* returns the full flat array.
|
|
1370
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
1371
1480
|
* @param {object} [opts]
|
|
1372
1481
|
* @param {string} [opts.startTime] - Only operations at or after this instant
|
|
1373
1482
|
* @param {string} [opts.endTime] - Only operations at or before this instant
|
|
@@ -1405,7 +1514,6 @@ class PlaidClient {
|
|
|
1405
1514
|
}),
|
|
1406
1515
|
/**
|
|
1407
1516
|
* Async-iterate the instance-wide audit log page by page. Admin only.
|
|
1408
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
1409
1517
|
* @returns {AsyncGenerator<Array>}
|
|
1410
1518
|
*/
|
|
1411
1519
|
iterPages: ({ startTime, endTime, opTypes, pageSize } = {}) =>
|
|
@@ -1555,21 +1663,68 @@ class PlaidClient {
|
|
|
1555
1663
|
queryParams: { "as-of": asOf },
|
|
1556
1664
|
}),
|
|
1557
1665
|
/**
|
|
1558
|
-
* Acquire or refresh a document lock
|
|
1666
|
+
* Acquire or refresh a document lock.
|
|
1667
|
+
*
|
|
1668
|
+
* outOfBand: the lock is a signal, not project data (see the note at
|
|
1669
|
+
* the top of http.js). Queued on a batch it would be taken only at
|
|
1670
|
+
* submit, after every write it was meant to guard, and until then answer
|
|
1671
|
+
* success to a caller that does not hold it and cannot see the 423
|
|
1672
|
+
* saying somebody else does.
|
|
1559
1673
|
* @param {string} documentId - The document ID
|
|
1560
1674
|
*/
|
|
1561
1675
|
acquireLock: (documentId, auditMessage) =>
|
|
1562
1676
|
this._request("POST", `/api/v1/documents/${documentId}/lock`, {
|
|
1563
1677
|
auditMessage,
|
|
1678
|
+
outOfBand: true,
|
|
1564
1679
|
}),
|
|
1565
1680
|
/**
|
|
1566
|
-
* Release a document lock
|
|
1681
|
+
* Release a document lock. outOfBand, for the same reason as
|
|
1682
|
+
* acquireLock: queued, the lock would be held until the batch submits,
|
|
1683
|
+
* and not released at all if it aborts.
|
|
1567
1684
|
* @param {string} documentId - The document ID
|
|
1568
1685
|
*/
|
|
1569
1686
|
releaseLock: (documentId, auditMessage) =>
|
|
1570
1687
|
this._request("DELETE", `/api/v1/documents/${documentId}/lock`, {
|
|
1571
1688
|
auditMessage,
|
|
1689
|
+
outOfBand: true,
|
|
1572
1690
|
}),
|
|
1691
|
+
/**
|
|
1692
|
+
* Hold this document's server-enforced lock for the length of `fn`,
|
|
1693
|
+
* releasing it on the way out (including on error). Resolves to what
|
|
1694
|
+
* `fn` returned.
|
|
1695
|
+
*
|
|
1696
|
+
* await client.documents.locked(docId, async () => {
|
|
1697
|
+
* // delete + recreate tokens
|
|
1698
|
+
* });
|
|
1699
|
+
*
|
|
1700
|
+
* Wrap any multi-step, server-side mutation of a document that must not
|
|
1701
|
+
* interleave with a human editor or another service, e.g. a parser or
|
|
1702
|
+
* tokenizer that deletes and recreates a document's tokens, spans and
|
|
1703
|
+
* relations. A single atomic call does not need it. While the lock is
|
|
1704
|
+
* held, writes to the document by ANOTHER user are refused with HTTP
|
|
1705
|
+
* 423; the holder's own writes pass and renew it. If another user
|
|
1706
|
+
* already holds it this rejects with a readable 423 and `fn` does not
|
|
1707
|
+
* run.
|
|
1708
|
+
*
|
|
1709
|
+
* The lock is renewed for as long as `fn` runs, so work that computes
|
|
1710
|
+
* for minutes before it writes holds the lock the whole time rather than
|
|
1711
|
+
* only for its first minute. If a renewal fails the lock is gone: every
|
|
1712
|
+
* later write from this client throws `DocumentLockLost`, and a block
|
|
1713
|
+
* that got to the end anyway ends with that error rather than reporting
|
|
1714
|
+
* success. `fn` receives a lock handle and may read `lock.lost` (or call
|
|
1715
|
+
* `lock.raiseIfLost()`) to give up sooner.
|
|
1716
|
+
*
|
|
1717
|
+
* NOT re-entrant: nesting two `locked()` blocks on one document would
|
|
1718
|
+
* release on the inner exit and leave the outer unprotected. Lock at
|
|
1719
|
+
* exactly one level per call path. A lost lock is recorded on the
|
|
1720
|
+
* CLIENT, like batch and strict mode, so it stops every write the client
|
|
1721
|
+
* makes and not only the ones this block makes.
|
|
1722
|
+
* @param {string} documentId - The document ID
|
|
1723
|
+
* @param {(lock: DocumentLock) => any} fn - The work to run while holding it
|
|
1724
|
+
* @param {object} [options] - `{ keepAlive }`: renew on a timer (default true)
|
|
1725
|
+
*/
|
|
1726
|
+
locked: (documentId, fn, options) =>
|
|
1727
|
+
withDocumentLock(this, documentId, fn, options),
|
|
1573
1728
|
/**
|
|
1574
1729
|
* Get the media file for a document. Media is not versioned, so there is
|
|
1575
1730
|
* no as-of form: the route refuses the parameter. Prefer the document's
|
|
@@ -1578,7 +1733,6 @@ class PlaidClient {
|
|
|
1578
1733
|
*/
|
|
1579
1734
|
getMedia: (documentId) =>
|
|
1580
1735
|
this._request("GET", `/api/v1/documents/${documentId}/media`, {
|
|
1581
|
-
noBatch: true,
|
|
1582
1736
|
binaryResponse: true,
|
|
1583
1737
|
}),
|
|
1584
1738
|
/**
|
|
@@ -1603,10 +1757,14 @@ class PlaidClient {
|
|
|
1603
1757
|
* Delete media file for a document
|
|
1604
1758
|
* @param {string} documentId - The document ID
|
|
1605
1759
|
*/
|
|
1760
|
+
// The upload above is multipart and cannot be batched, but a DELETE
|
|
1761
|
+
// carries no blob, so the batch transport takes it. It is a write
|
|
1762
|
+
// of the document's own data and queues like any other. Note that the
|
|
1763
|
+
// file removal happens outside the server's transaction, so a batch that
|
|
1764
|
+
// aborts after this op does not bring the file back.
|
|
1606
1765
|
deleteMedia: (documentId, auditMessage) =>
|
|
1607
1766
|
this._request("DELETE", `/api/v1/documents/${documentId}/media`, {
|
|
1608
1767
|
auditMessage,
|
|
1609
|
-
noBatch: true,
|
|
1610
1768
|
}),
|
|
1611
1769
|
/**
|
|
1612
1770
|
* Replace all metadata for a document.
|
|
@@ -1642,7 +1800,6 @@ class PlaidClient {
|
|
|
1642
1800
|
/**
|
|
1643
1801
|
* Get audit log for a document. Transparently follows pagination cursors
|
|
1644
1802
|
* and returns the full flat array.
|
|
1645
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
1646
1803
|
* @param {string} documentId - The document ID
|
|
1647
1804
|
* @param {string} [startTime] - Start of time range
|
|
1648
1805
|
* @param {string} [endTime] - End of time range
|
|
@@ -1866,7 +2023,6 @@ class PlaidClient {
|
|
|
1866
2023
|
/**
|
|
1867
2024
|
* Get audit log for a project. Transparently follows pagination cursors
|
|
1868
2025
|
* and returns the full flat array.
|
|
1869
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
1870
2026
|
* @param {string} projectId - The project ID
|
|
1871
2027
|
* @param {string} [startTime] - Start of time range
|
|
1872
2028
|
* @param {string} [endTime] - End of time range
|
|
@@ -1955,7 +2111,6 @@ class PlaidClient {
|
|
|
1955
2111
|
/**
|
|
1956
2112
|
* List all documents in a project. Transparently follows pagination
|
|
1957
2113
|
* cursors and returns the full flat array.
|
|
1958
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
1959
2114
|
*
|
|
1960
2115
|
* Note: this endpoint does not support temporal (`as-of`) queries; the
|
|
1961
2116
|
* server rejects `?as-of=` on the documents-list route with a 400.
|
|
@@ -1984,7 +2139,6 @@ class PlaidClient {
|
|
|
1984
2139
|
* @param {string} id - The project ID
|
|
1985
2140
|
* @param {object} [opts]
|
|
1986
2141
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
1987
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
1988
2142
|
* @returns {AsyncGenerator<Array>}
|
|
1989
2143
|
*/
|
|
1990
2144
|
iterDocuments: (id, { pageSize } = {}) =>
|
|
@@ -2018,7 +2172,6 @@ class PlaidClient {
|
|
|
2018
2172
|
/**
|
|
2019
2173
|
* List all projects accessible to user. Transparently follows pagination
|
|
2020
2174
|
* cursors and returns the full flat array.
|
|
2021
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
|
|
2022
2175
|
* @param {string} [asOf] - Temporal query timestamp
|
|
2023
2176
|
*/
|
|
2024
2177
|
list: (asOf) =>
|
|
@@ -2042,7 +2195,6 @@ class PlaidClient {
|
|
|
2042
2195
|
* @param {object} [opts]
|
|
2043
2196
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
2044
2197
|
* @param {string} [opts.asOf] - Temporal query timestamp
|
|
2045
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
|
|
2046
2198
|
* @returns {AsyncGenerator<Array>}
|
|
2047
2199
|
*/
|
|
2048
2200
|
iterPages: ({ pageSize, asOf } = {}) =>
|
|
@@ -2195,9 +2347,23 @@ class PlaidClient {
|
|
|
2195
2347
|
auditMessage,
|
|
2196
2348
|
body,
|
|
2197
2349
|
}),
|
|
2350
|
+
/**
|
|
2351
|
+
* Update many vocab items in a single operation: set forms and/or patch
|
|
2352
|
+
* metadata.
|
|
2353
|
+
* @param {Array<{id: string, form?: string, metadata?: any}>} body - `form` is set only when the key is present; `metadata` is a patch, a null value deleting that key. 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
|
+
* @returns {Promise<{count: number}>} How many vocab items were updated.
|
|
2355
|
+
*/
|
|
2356
|
+
bulkUpdate: (body, auditMessage) =>
|
|
2357
|
+
this._request("PATCH", "/api/v1/vocab-items/bulk", {
|
|
2358
|
+
auditMessage,
|
|
2359
|
+
body,
|
|
2360
|
+
}),
|
|
2198
2361
|
/**
|
|
2199
2362
|
* Delete multiple vocab items in a single operation. Each item's
|
|
2200
2363
|
* descendant vocab links are deleted too. Provide an array of IDs.
|
|
2364
|
+
*
|
|
2365
|
+
* Every document holding one of those links has its version bumped, and
|
|
2366
|
+
* a strict-mode client picks up their new versions from the response.
|
|
2201
2367
|
* @param {string[]} body - The vocab item IDs to delete
|
|
2202
2368
|
*/
|
|
2203
2369
|
bulkDelete: (body, auditMessage) =>
|
|
@@ -2215,13 +2381,18 @@ class PlaidClient {
|
|
|
2215
2381
|
queryParams: { "as-of": asOf },
|
|
2216
2382
|
}),
|
|
2217
2383
|
/**
|
|
2218
|
-
* Delete a vocab item
|
|
2384
|
+
* Delete a vocab item, and every link to it. Every document holding one
|
|
2385
|
+
* of those links has its version bumped, and a strict-mode client picks
|
|
2386
|
+
* up their new versions from the response.
|
|
2219
2387
|
* @param {string} id - The resource ID
|
|
2220
2388
|
*/
|
|
2221
2389
|
delete: (id, auditMessage) =>
|
|
2222
2390
|
this._request("DELETE", `/api/v1/vocab-items/${id}`, { auditMessage }),
|
|
2223
2391
|
/**
|
|
2224
|
-
* Update a vocab item's form
|
|
2392
|
+
* Update a vocab item's form. A document read carries the entry's form on
|
|
2393
|
+
* every link to it, so a rename restates those documents: each has its
|
|
2394
|
+
* version bumped, and a strict-mode client picks up their new versions
|
|
2395
|
+
* from the response.
|
|
2225
2396
|
* @param {string} id - The resource ID
|
|
2226
2397
|
* @param {string} form - The vocab item form
|
|
2227
2398
|
*/
|
|
@@ -2399,7 +2570,7 @@ class PlaidClient {
|
|
|
2399
2570
|
this._request("DELETE", "/api/v1/tokens/bulk", { auditMessage, body }),
|
|
2400
2571
|
/**
|
|
2401
2572
|
* Update many tokens in a single operation: patch metadata.
|
|
2402
|
-
* @param {Array} body - Objects of the shape `{id, metadata}`. `metadata` is a patch, a null value deleting that key. The tokens may lie in several documents of one project; every document touched has its version bumped. An unknown id refuses the whole update.
|
|
2573
|
+
* @param {Array} body - Objects of the shape `{id, metadata}`. `metadata` is a patch, a null value deleting that key. 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`. A `document-version` precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update.
|
|
2403
2574
|
* @returns {Promise<{count: number}>} How many tokens were updated.
|
|
2404
2575
|
*/
|
|
2405
2576
|
bulkUpdate: (body, auditMessage) =>
|
|
@@ -2659,17 +2830,14 @@ class PlaidClient {
|
|
|
2659
2830
|
* For 'count': {return: 'count', count}. Entity cells are full entity objects
|
|
2660
2831
|
* (same shape as the GET endpoints).
|
|
2661
2832
|
*/
|
|
2662
|
-
//
|
|
2663
|
-
//
|
|
2664
|
-
//
|
|
2665
|
-
// be queued into it: the caller got `{batched: true}` instead of results,
|
|
2666
|
-
// and the server ran the query against the batch's tx Connection, which
|
|
2667
|
-
// 500s and rolls back every write in that batch.
|
|
2833
|
+
// outOfBand: a query is a read that travels as a POST (see the note at
|
|
2834
|
+
// the top of http.js). Made on a batch it goes over the wire like any
|
|
2835
|
+
// read, and it never joins a logical operation.
|
|
2668
2836
|
this.query = (body, auditMessage) =>
|
|
2669
2837
|
this._request("POST", "/api/v1/query", {
|
|
2670
2838
|
auditMessage,
|
|
2671
2839
|
body,
|
|
2672
|
-
|
|
2840
|
+
outOfBand: true,
|
|
2673
2841
|
});
|
|
2674
2842
|
|
|
2675
2843
|
// Logical-operation groups (audit-log grouping). There is no create: a
|
|
@@ -2739,7 +2907,6 @@ class PlaidClient {
|
|
|
2739
2907
|
/**
|
|
2740
2908
|
* List comments in a project, oldest first. Transparently follows
|
|
2741
2909
|
* pagination cursors and returns the full flat array.
|
|
2742
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching - use listPage() for a single page in a batch.
|
|
2743
2910
|
* @param {string} projectId - The project to read
|
|
2744
2911
|
* @param {object} [filters] - Narrow the scope
|
|
2745
2912
|
* @param {string} [filters.documentId] - Every comment anywhere in one document
|
|
@@ -2780,7 +2947,6 @@ class PlaidClient {
|
|
|
2780
2947
|
}),
|
|
2781
2948
|
/**
|
|
2782
2949
|
* Async-iterate a project's comments page by page; yields each page's entries array.
|
|
2783
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching - use listPage() for a single page in a batch.
|
|
2784
2950
|
* @param {string} projectId - The project to read
|
|
2785
2951
|
* @param {object} [opts]
|
|
2786
2952
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
@@ -2826,7 +2992,6 @@ class PlaidClient {
|
|
|
2826
2992
|
* List the comments on a vocabulary's entries, oldest first. Requires
|
|
2827
2993
|
* read access to the vocabulary. Transparently follows pagination
|
|
2828
2994
|
* cursors and returns the full flat array.
|
|
2829
|
-
* Cannot be used inside a batch (auto-paginates across requests); throws if called while batching - use listInVocabPage() for a single page in a batch.
|
|
2830
2995
|
* @param {string} vocabId - The vocab layer to read
|
|
2831
2996
|
* @param {object} [filters]
|
|
2832
2997
|
* @param {string} [filters.entityId] - One entry's thread
|
|
@@ -2868,6 +3033,106 @@ class PlaidClient {
|
|
|
2868
3033
|
},
|
|
2869
3034
|
),
|
|
2870
3035
|
};
|
|
3036
|
+
|
|
3037
|
+
this.guidelines = {
|
|
3038
|
+
/**
|
|
3039
|
+
* Create a guideline in a project. `title` is the handle an assistant
|
|
3040
|
+
* asks for one by, and the only thing a person has to keep current. It is
|
|
3041
|
+
* NOT required to be unique, so a title already in use is written like
|
|
3042
|
+
* any other: warn about it, do not refuse it. A `pinned` guideline is one
|
|
3043
|
+
* the assistant is given in full on every turn.
|
|
3044
|
+
* @param {string} projectId - The project the guideline belongs to
|
|
3045
|
+
* @param {string} title - The handle an assistant asks for one by (1..100 characters)
|
|
3046
|
+
* @param {object} [opts]
|
|
3047
|
+
* @param {string} [opts.body] - The Markdown text (up to 20000 characters; may be empty)
|
|
3048
|
+
* @param {boolean} [opts.pinned] - Send this one to the assistant in full on every turn
|
|
3049
|
+
*/
|
|
3050
|
+
create: (projectId, title, { body, pinned } = {}, auditMessage) =>
|
|
3051
|
+
this._request("POST", `/api/v1/projects/${projectId}/guidelines`, {
|
|
3052
|
+
auditMessage,
|
|
3053
|
+
body: bodyOf({ title, body, pinned }),
|
|
3054
|
+
}),
|
|
3055
|
+
/**
|
|
3056
|
+
* Read one guideline, Markdown body included.
|
|
3057
|
+
* @param {string} id - The guideline id
|
|
3058
|
+
*/
|
|
3059
|
+
get: (id) => this._request("GET", `/api/v1/guidelines/${id}`),
|
|
3060
|
+
/**
|
|
3061
|
+
* Update a guideline. Every field is optional and an omitted one is
|
|
3062
|
+
* left alone, so an edit to the body need not restate the title.
|
|
3063
|
+
*
|
|
3064
|
+
* Pass `expectedUpdatedAt` (the `updatedAt` you last read) when a person
|
|
3065
|
+
* has been editing prose: the write then fails with 409 rather than
|
|
3066
|
+
* overwriting somebody who saved in between. Leave it off for a pin
|
|
3067
|
+
* toggle or a script, which have nothing of anyone's to lose.
|
|
3068
|
+
* @param {string} id - The guideline id
|
|
3069
|
+
* @param {object} [changes]
|
|
3070
|
+
* @param {string} [changes.title] - The new handle
|
|
3071
|
+
* @param {string} [changes.body] - The new Markdown text
|
|
3072
|
+
* @param {boolean} [changes.pinned] - Whether the assistant always gets it in full
|
|
3073
|
+
* @param {string} [changes.expectedUpdatedAt] - Write only if this is still the stored updatedAt
|
|
3074
|
+
*/
|
|
3075
|
+
update: (
|
|
3076
|
+
id,
|
|
3077
|
+
{ title, body, pinned, expectedUpdatedAt } = {},
|
|
3078
|
+
auditMessage,
|
|
3079
|
+
) =>
|
|
3080
|
+
this._request("PATCH", `/api/v1/guidelines/${id}`, {
|
|
3081
|
+
auditMessage,
|
|
3082
|
+
queryParams: { "updated-at": expectedUpdatedAt },
|
|
3083
|
+
body: bodyOf({ title, body, pinned }),
|
|
3084
|
+
}),
|
|
3085
|
+
/**
|
|
3086
|
+
* Delete a guideline.
|
|
3087
|
+
* @param {string} id - The guideline id
|
|
3088
|
+
*/
|
|
3089
|
+
delete: (id, auditMessage) =>
|
|
3090
|
+
this._request("DELETE", `/api/v1/guidelines/${id}`, { auditMessage }),
|
|
3091
|
+
/**
|
|
3092
|
+
* List a project's guidelines, by title. Transparently follows
|
|
3093
|
+
* pagination cursors and returns the full flat array.
|
|
3094
|
+
*
|
|
3095
|
+
* Without `includeBodies` each entry carries `bodyChars`, the length of
|
|
3096
|
+
* its body, so a caller can budget before fetching any. Pinned
|
|
3097
|
+
* guidelines are NOT sorted first: `pinned` is on every entry and
|
|
3098
|
+
* grouping is the caller's.
|
|
3099
|
+
* @param {string} projectId - The project to read
|
|
3100
|
+
* @param {object} [opts]
|
|
3101
|
+
* @param {boolean} [opts.includeBodies] - Return each body instead of its length
|
|
3102
|
+
*/
|
|
3103
|
+
list: (projectId, { includeBodies } = {}) =>
|
|
3104
|
+
listAll(this, `/api/v1/projects/${projectId}/guidelines`, {
|
|
3105
|
+
query: { "include-bodies": includeBodies },
|
|
3106
|
+
}),
|
|
3107
|
+
/**
|
|
3108
|
+
* Fetch a single page of a project's guidelines.
|
|
3109
|
+
* @param {string} projectId - The project to read
|
|
3110
|
+
* @param {object} [opts]
|
|
3111
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
3112
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
3113
|
+
* @param {boolean} [opts.includeBodies] - Return each body instead of its length
|
|
3114
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
3115
|
+
*/
|
|
3116
|
+
listPage: (projectId, { limit, cursor, includeBodies } = {}) =>
|
|
3117
|
+
listPage(this, `/api/v1/projects/${projectId}/guidelines`, {
|
|
3118
|
+
limit,
|
|
3119
|
+
cursor,
|
|
3120
|
+
query: { "include-bodies": includeBodies },
|
|
3121
|
+
}),
|
|
3122
|
+
/**
|
|
3123
|
+
* Async-iterate a project's guidelines page by page; yields each page's entries array.
|
|
3124
|
+
* @param {string} projectId - The project to read
|
|
3125
|
+
* @param {object} [opts]
|
|
3126
|
+
* @param {number} [opts.pageSize] - Per-request page size
|
|
3127
|
+
* @param {boolean} [opts.includeBodies] - Return each body instead of its length
|
|
3128
|
+
* @returns {AsyncGenerator<Array>}
|
|
3129
|
+
*/
|
|
3130
|
+
iterPages: (projectId, { pageSize, includeBodies } = {}) =>
|
|
3131
|
+
iterPages(this, `/api/v1/projects/${projectId}/guidelines`, {
|
|
3132
|
+
pageSize,
|
|
3133
|
+
query: { "include-bodies": includeBodies },
|
|
3134
|
+
}),
|
|
3135
|
+
};
|
|
2871
3136
|
}
|
|
2872
3137
|
|
|
2873
3138
|
// --- Core methods ---
|
|
@@ -2909,6 +3174,12 @@ class PlaidClient {
|
|
|
2909
3174
|
* write, so an operation that is never ended (crash, closed tab) is still
|
|
2910
3175
|
* labeled in the log.
|
|
2911
3176
|
*
|
|
3177
|
+
* "Every write" is the writes of project data. Reads never join, and neither
|
|
3178
|
+
* do the out-of-band signals shaped like a write (a document lock taken or
|
|
3179
|
+
* renewed, a stopped service request, a service reporting itself, an admin
|
|
3180
|
+
* control): none of them is audited, so there would be nothing under the
|
|
3181
|
+
* label.
|
|
3182
|
+
*
|
|
2912
3183
|
* @param {string} message - Human label for the operation.
|
|
2913
3184
|
* @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).
|
|
2914
3185
|
* @returns {string} The operation's group id.
|
|
@@ -2963,7 +3234,7 @@ class PlaidClient {
|
|
|
2963
3234
|
* callback to refine the label once the outcome is known.
|
|
2964
3235
|
*
|
|
2965
3236
|
* await client.withOperation('Merge morphemes', async (setMessage) => {
|
|
2966
|
-
* await client.batched(() => { ... });
|
|
3237
|
+
* await client.batched(async (b) => { ... });
|
|
2967
3238
|
* setMessage(`Merged ${n} morphemes`);
|
|
2968
3239
|
* });
|
|
2969
3240
|
*
|
|
@@ -2984,53 +3255,29 @@ class PlaidClient {
|
|
|
2984
3255
|
}
|
|
2985
3256
|
}
|
|
2986
3257
|
|
|
2987
|
-
/** Begin a batch of operations. Subsequent API calls will be queued. */
|
|
2988
|
-
beginBatch() {
|
|
2989
|
-
this.isBatching = true;
|
|
2990
|
-
this.batchOperations = [];
|
|
2991
|
-
// Strict mode stamps the expected document-version on the FIRST write of
|
|
2992
|
-
// the batch only (see _request) — reset the marker per batch.
|
|
2993
|
-
this.batchVersionStamped = false;
|
|
2994
|
-
}
|
|
2995
|
-
|
|
2996
3258
|
/**
|
|
2997
|
-
*
|
|
2998
|
-
*
|
|
2999
|
-
*
|
|
3259
|
+
* Open a batch: a view of this client with the same bundles, on which every
|
|
3260
|
+
* write of project data queues instead of going out. `submit()` sends the
|
|
3261
|
+
* queued operations as ONE atomic request (larger than the server's cap,
|
|
3262
|
+
* as consecutive requests with the results concatenated in queue order) and
|
|
3263
|
+
* resolves to one result per operation; `abort()` drops them. A call made
|
|
3264
|
+
* on the client itself is never touched by an open batch, and a read or an
|
|
3265
|
+
* out-of-band signal made on the batch goes over the wire now (see the note
|
|
3266
|
+
* at the top of `http.js`).
|
|
3267
|
+
*
|
|
3268
|
+
* const b = client.batch();
|
|
3269
|
+
* b.tokens.bulkCreate(sentenceOps);
|
|
3270
|
+
* b.tokens.bulkCreate(wordOps);
|
|
3271
|
+
* const [sentRes, wordRes] = await b.submit();
|
|
3272
|
+
*
|
|
3273
|
+
* Server-side a batch runs sequentially in one transaction: a child op sees
|
|
3274
|
+
* parents created earlier in the same batch, and any op's failure rolls the
|
|
3275
|
+
* whole batch back. A batch is not nestable. Prefer `batched()`, which
|
|
3276
|
+
* submits or aborts for you.
|
|
3277
|
+
* @returns {PlaidClient} the batch view
|
|
3000
3278
|
*/
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
throw new Error("No active batch. Call beginBatch() first.");
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
if (this.batchOperations.length === 0) {
|
|
3007
|
-
this.isBatching = false;
|
|
3008
|
-
return [];
|
|
3009
|
-
}
|
|
3010
|
-
|
|
3011
|
-
try {
|
|
3012
|
-
const url = `${this.baseUrl}/api/v1/batch`;
|
|
3013
|
-
const ops = this.batchOperations.map((op) => ({
|
|
3014
|
-
path: op.path,
|
|
3015
|
-
method: op.method.toUpperCase(),
|
|
3016
|
-
...(op.body && { body: op.body }),
|
|
3017
|
-
}));
|
|
3018
|
-
// The server caps a batch at MAX_BATCH_OPS so one transaction cannot
|
|
3019
|
-
// hold the write lock without bound. A larger batch goes as consecutive
|
|
3020
|
-
// requests, results concatenated in queue order: it could not have been
|
|
3021
|
-
// one transaction anyway, and a repair or bulk edit over a big document
|
|
3022
|
-
// must not fail on its size alone.
|
|
3023
|
-
const results = [];
|
|
3024
|
-
for (let i = 0; i < ops.length; i += MAX_BATCH_OPS) {
|
|
3025
|
-
results.push(
|
|
3026
|
-
...(await this._postBatch(url, ops.slice(i, i + MAX_BATCH_OPS))),
|
|
3027
|
-
);
|
|
3028
|
-
}
|
|
3029
|
-
return results;
|
|
3030
|
-
} finally {
|
|
3031
|
-
this.isBatching = false;
|
|
3032
|
-
this.batchOperations = [];
|
|
3033
|
-
}
|
|
3279
|
+
batch() {
|
|
3280
|
+
return openBatch(this);
|
|
3034
3281
|
}
|
|
3035
3282
|
|
|
3036
3283
|
/** POST one batch request (at most MAX_BATCH_OPS operations); resolves to its transformed results. */
|
|
@@ -3092,50 +3339,31 @@ class PlaidClient {
|
|
|
3092
3339
|
}
|
|
3093
3340
|
}
|
|
3094
3341
|
|
|
3095
|
-
/** Abort the current batch without executing any operations. */
|
|
3096
|
-
abortBatch() {
|
|
3097
|
-
this.isBatching = false;
|
|
3098
|
-
this.batchOperations = [];
|
|
3099
|
-
}
|
|
3100
|
-
|
|
3101
|
-
/**
|
|
3102
|
-
* Check if currently in batch mode.
|
|
3103
|
-
* @returns {boolean}
|
|
3104
|
-
*/
|
|
3105
|
-
isBatchMode() {
|
|
3106
|
-
return this.isBatching;
|
|
3107
|
-
}
|
|
3108
|
-
|
|
3109
3342
|
/**
|
|
3110
|
-
* Run `fn` with a batch
|
|
3111
|
-
*
|
|
3112
|
-
*
|
|
3113
|
-
*
|
|
3114
|
-
* array (`[]` if `fn` queued nothing).
|
|
3343
|
+
* Run `fn` with a batch, then submit all queued ops as ONE atomic request,
|
|
3344
|
+
* or abort the batch if `fn` throws. `fn` receives the batch and makes its
|
|
3345
|
+
* writes on it; it must NOT call `submit()` itself. Resolves to the batch
|
|
3346
|
+
* results array (`[]` if `fn` queued nothing).
|
|
3115
3347
|
*
|
|
3116
|
-
* const [sentRes, wordRes] = await client.batched(async () => {
|
|
3117
|
-
*
|
|
3118
|
-
*
|
|
3348
|
+
* const [sentRes, wordRes] = await client.batched(async (b) => {
|
|
3349
|
+
* b.tokens.bulkCreate(sentenceOps);
|
|
3350
|
+
* b.tokens.bulkCreate(wordOps);
|
|
3119
3351
|
* });
|
|
3120
3352
|
*
|
|
3121
|
-
*
|
|
3122
|
-
*
|
|
3123
|
-
* batch
|
|
3124
|
-
* low-level batch resource.
|
|
3125
|
-
* @param {() => (void | Promise<void>)} fn
|
|
3353
|
+
* A write made on `client` inside `fn` is not part of the batch: it goes
|
|
3354
|
+
* over the wire at once, as it would anywhere else. See `batch()`.
|
|
3355
|
+
* @param {(batch: PlaidClient) => (void | Promise<void>)} fn
|
|
3126
3356
|
* @returns {Promise<Array>}
|
|
3127
3357
|
*/
|
|
3128
3358
|
async batched(fn) {
|
|
3129
|
-
this.
|
|
3359
|
+
const batch = this.batch();
|
|
3130
3360
|
try {
|
|
3131
|
-
await fn();
|
|
3361
|
+
await fn(batch);
|
|
3132
3362
|
} catch (e) {
|
|
3133
|
-
|
|
3134
|
-
// later plain calls don't queue into a never-submitted batch.
|
|
3135
|
-
if (this.isBatchMode()) this.abortBatch();
|
|
3363
|
+
batch.abort();
|
|
3136
3364
|
throw e;
|
|
3137
3365
|
}
|
|
3138
|
-
return
|
|
3366
|
+
return batch.submit();
|
|
3139
3367
|
}
|
|
3140
3368
|
|
|
3141
3369
|
/**
|
|
@@ -3313,6 +3541,14 @@ export {
|
|
|
3313
3541
|
// request, and `critical()` holds that off around writes. See ./services.js
|
|
3314
3542
|
// and the manual, "Stopping a request".
|
|
3315
3543
|
export { ServiceCancelled, createCancelScope } from "./services.js";
|
|
3544
|
+
// Document locks: `client.documents.locked()` holds one for a block of work and
|
|
3545
|
+
// renews it while the block runs. DocumentLockLost is what a write throws once
|
|
3546
|
+
// a renewal has failed. See ./documentLock.js.
|
|
3547
|
+
export {
|
|
3548
|
+
DOCUMENT_LOCK_TTL_MS,
|
|
3549
|
+
DocumentLock,
|
|
3550
|
+
DocumentLockLost,
|
|
3551
|
+
} from "./documentLock.js";
|
|
3316
3552
|
// Provenance: the cross-app convention for who made an annotation (flat
|
|
3317
3553
|
// prov/provSource/provConfirmed metadata; absence = a verifier; 'inferred' =
|
|
3318
3554
|
// a machine; 'contributed' = a person whose work is reviewed), plus the
|