@larc-iu/plaid-client 0.2.0-alpha.11 → 0.2.0-alpha.13
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 +227 -10
- package/package.json +1 -1
- package/src/index.js +495 -28
- package/src/provenance.js +19 -0
- package/src/roles.js +9 -0
- package/src/serviceSchema.js +12 -2
- package/src/services.js +129 -17
package/src/index.js
CHANGED
|
@@ -85,6 +85,31 @@ async function anonymousPost(baseUrl, path, body, options = {}) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* GET a path with no authentication, for the two endpoints a client may need
|
|
90
|
+
* before it has a session: /health and /api/v1/info.
|
|
91
|
+
*/
|
|
92
|
+
async function anonymousGet(baseUrl, path, options = {}) {
|
|
93
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
94
|
+
const url = `${base}${path}`;
|
|
95
|
+
try {
|
|
96
|
+
const fetchOptions = { method: "GET" };
|
|
97
|
+
const signal = timeoutSignal(
|
|
98
|
+
options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS,
|
|
99
|
+
);
|
|
100
|
+
if (signal) fetchOptions.signal = signal;
|
|
101
|
+
|
|
102
|
+
const response = await fetch(url, fetchOptions);
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw makeHttpError(response, await parseErrorBody(response), url, "GET");
|
|
105
|
+
}
|
|
106
|
+
return transformResponse(await response.json());
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.status) throw error;
|
|
109
|
+
throw makeNetworkError(error, url, "GET");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
88
113
|
/**
|
|
89
114
|
* The server's cap on operations per batch request (plaid.rest-api.v1.batch).
|
|
90
115
|
* A batch queued past it is submitted as consecutive requests.
|
|
@@ -450,6 +475,16 @@ class PlaidClient {
|
|
|
450
475
|
auditMessage,
|
|
451
476
|
body,
|
|
452
477
|
}),
|
|
478
|
+
/**
|
|
479
|
+
* 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.
|
|
481
|
+
* @returns {Promise<{count: number}>} How many relations were updated.
|
|
482
|
+
*/
|
|
483
|
+
bulkUpdate: (body, auditMessage) =>
|
|
484
|
+
this._request("PATCH", "/api/v1/relations/bulk", {
|
|
485
|
+
auditMessage,
|
|
486
|
+
body,
|
|
487
|
+
}),
|
|
453
488
|
};
|
|
454
489
|
|
|
455
490
|
this.spanLayers = {
|
|
@@ -600,6 +635,16 @@ class PlaidClient {
|
|
|
600
635
|
*/
|
|
601
636
|
bulkDelete: (body, auditMessage) =>
|
|
602
637
|
this._request("DELETE", "/api/v1/spans/bulk", { auditMessage, body }),
|
|
638
|
+
/**
|
|
639
|
+
* 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.
|
|
641
|
+
* @returns {Promise<{count: number}>} How many spans were updated.
|
|
642
|
+
*/
|
|
643
|
+
bulkUpdate: (body, auditMessage) =>
|
|
644
|
+
this._request("PATCH", "/api/v1/spans/bulk", {
|
|
645
|
+
auditMessage,
|
|
646
|
+
body,
|
|
647
|
+
}),
|
|
603
648
|
/**
|
|
604
649
|
* Replace all metadata for a span.
|
|
605
650
|
* @param {string} spanId - The span ID
|
|
@@ -633,6 +678,42 @@ class PlaidClient {
|
|
|
633
678
|
}),
|
|
634
679
|
};
|
|
635
680
|
|
|
681
|
+
// Server-level facts. Fetched at most once: the limits cannot change while
|
|
682
|
+
// the server is up, and a caller asking "will this file be accepted" should
|
|
683
|
+
// not pay a round trip to find out.
|
|
684
|
+
let infoPromise = null;
|
|
685
|
+
this.server = {
|
|
686
|
+
/**
|
|
687
|
+
* This server's version and the limits it enforces, e.g.
|
|
688
|
+
* `{ limits: { mediaFileBytes, jsonBodyBytes, batchOperations, … } }`.
|
|
689
|
+
* Sizes are in bytes. Unauthenticated.
|
|
690
|
+
*/
|
|
691
|
+
info: () => {
|
|
692
|
+
if (!infoPromise) {
|
|
693
|
+
// bypassBatch: a read belongs to whoever asked for it, not to
|
|
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) => {
|
|
698
|
+
// A failure must not be cached: a client that starts before the
|
|
699
|
+
// server is up would never see the limits at all.
|
|
700
|
+
infoPromise = null;
|
|
701
|
+
throw err;
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return infoPromise;
|
|
705
|
+
},
|
|
706
|
+
/** Just the limits, which is what a caller almost always wants. */
|
|
707
|
+
limits: async () => (await this.server.info()).limits,
|
|
708
|
+
/**
|
|
709
|
+
* Liveness, version and database size. Unauthenticated, and served
|
|
710
|
+
* outside the REST router at /health, so it answers even while the API
|
|
711
|
+
* is refusing requests. Never cached — the point is that it is current.
|
|
712
|
+
* @returns {Promise<{ok: boolean, version: string, uptimeMs: number, audit: object}>}
|
|
713
|
+
*/
|
|
714
|
+
health: () => PlaidClient.health(this.baseUrl, { timeout: this.timeout }),
|
|
715
|
+
};
|
|
716
|
+
|
|
636
717
|
this.batch = {
|
|
637
718
|
/**
|
|
638
719
|
* Execute multiple API operations atomically. If any operation fails, all
|
|
@@ -812,6 +893,31 @@ class PlaidClient {
|
|
|
812
893
|
"op-types": opTypesParam(opTypes),
|
|
813
894
|
},
|
|
814
895
|
}),
|
|
896
|
+
/**
|
|
897
|
+
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
898
|
+
* rather than audit() wherever the caller wants the recent end of a log
|
|
899
|
+
* that may be long: audit() walks every page before it resolves.
|
|
900
|
+
* @param {object} [opts]
|
|
901
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
902
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
903
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
904
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
905
|
+
*/
|
|
906
|
+
auditPage: (
|
|
907
|
+
userId,
|
|
908
|
+
{ startTime, endTime, asOf, opTypes, order, limit, cursor } = {},
|
|
909
|
+
) =>
|
|
910
|
+
listPage(this, `/api/v1/users/${userId}/audit`, {
|
|
911
|
+
limit,
|
|
912
|
+
cursor,
|
|
913
|
+
query: {
|
|
914
|
+
"start-time": startTime,
|
|
915
|
+
"end-time": endTime,
|
|
916
|
+
"as-of": asOf,
|
|
917
|
+
"op-types": opTypesParam(opTypes),
|
|
918
|
+
order,
|
|
919
|
+
},
|
|
920
|
+
}),
|
|
815
921
|
/**
|
|
816
922
|
* Get a user by ID
|
|
817
923
|
* @param {string} id - The resource ID
|
|
@@ -924,14 +1030,21 @@ class PlaidClient {
|
|
|
924
1030
|
/**
|
|
925
1031
|
* List a user's private data entries ({key, updatedAt}, plus value when
|
|
926
1032
|
* includeValues). Owner or admin only.
|
|
1033
|
+
*
|
|
1034
|
+
* Narrow with `prefix` (the literal head of a key) and/or `pattern`, a
|
|
1035
|
+
* GLOB over the whole key (`*` any run, `?` one character) — the way to
|
|
1036
|
+
* ask for a key convention identified by a segment in the middle, e.g.
|
|
1037
|
+
* `igt:assistant:*:meta:*` for every conversation's sidebar entry across
|
|
1038
|
+
* every project without dragging down the transcripts beside them.
|
|
927
1039
|
* @param {string} userId
|
|
928
1040
|
* @param {object} [opts]
|
|
929
1041
|
* @param {string} [opts.prefix] - Only keys starting with this prefix
|
|
1042
|
+
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
930
1043
|
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
931
1044
|
*/
|
|
932
|
-
list: (userId, { prefix, includeValues } = {}) =>
|
|
1045
|
+
list: (userId, { prefix, pattern, includeValues } = {}) =>
|
|
933
1046
|
this._request("GET", `/api/v1/users/${userId}/data`, {
|
|
934
|
-
queryParams: { prefix, "include-values": includeValues },
|
|
1047
|
+
queryParams: { prefix, pattern, "include-values": includeValues },
|
|
935
1048
|
}),
|
|
936
1049
|
/**
|
|
937
1050
|
* Read one private data entry ({key, updatedAt, value}); 404 if absent.
|
|
@@ -1044,37 +1157,40 @@ class PlaidClient {
|
|
|
1044
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.
|
|
1045
1158
|
* @param {object} [opts]
|
|
1046
1159
|
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1160
|
+
* @param {boolean} [opts.all] - List every invite on the server (admin only)
|
|
1047
1161
|
*/
|
|
1048
|
-
list: ({ projectId } = {}) =>
|
|
1162
|
+
list: ({ projectId, all } = {}) =>
|
|
1049
1163
|
listAll(this, "/api/v1/invites", {
|
|
1050
|
-
query: { "project-id": projectId },
|
|
1164
|
+
query: { "project-id": projectId, all },
|
|
1051
1165
|
}),
|
|
1052
1166
|
/**
|
|
1053
1167
|
* Fetch a single page of invites.
|
|
1054
1168
|
* @param {object} [opts]
|
|
1055
1169
|
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1170
|
+
* @param {boolean} [opts.all] - List every invite on the server (admin only)
|
|
1056
1171
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1057
1172
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1058
1173
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1059
1174
|
*/
|
|
1060
|
-
listPage: ({ projectId, limit, cursor } = {}) =>
|
|
1175
|
+
listPage: ({ projectId, all, limit, cursor } = {}) =>
|
|
1061
1176
|
listPage(this, "/api/v1/invites", {
|
|
1062
1177
|
limit,
|
|
1063
1178
|
cursor,
|
|
1064
|
-
query: { "project-id": projectId },
|
|
1179
|
+
query: { "project-id": projectId, all },
|
|
1065
1180
|
}),
|
|
1066
1181
|
/**
|
|
1067
1182
|
* Async-iterate invites page by page; yields each page's entries array.
|
|
1068
1183
|
* @param {object} [opts]
|
|
1069
1184
|
* @param {string} [opts.projectId] - List this project's invites instead of your own
|
|
1185
|
+
* @param {boolean} [opts.all] - List every invite on the server (admin only)
|
|
1070
1186
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
1071
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.
|
|
1072
1188
|
* @returns {AsyncGenerator<Array>}
|
|
1073
1189
|
*/
|
|
1074
|
-
iterPages: ({ projectId, pageSize } = {}) =>
|
|
1190
|
+
iterPages: ({ projectId, all, pageSize } = {}) =>
|
|
1075
1191
|
iterPages(this, "/api/v1/invites", {
|
|
1076
1192
|
pageSize,
|
|
1077
|
-
query: { "project-id": projectId },
|
|
1193
|
+
query: { "project-id": projectId, all },
|
|
1078
1194
|
}),
|
|
1079
1195
|
/**
|
|
1080
1196
|
* Mint an invite. The returned `code` is shown ONLY here — it is never
|
|
@@ -1134,6 +1250,206 @@ class PlaidClient {
|
|
|
1134
1250
|
this._request("DELETE", `/api/v1/invites/${id}`, { auditMessage }),
|
|
1135
1251
|
};
|
|
1136
1252
|
|
|
1253
|
+
this.admin = {
|
|
1254
|
+
/**
|
|
1255
|
+
* Everything about the server in one read: version and JVM uptime,
|
|
1256
|
+
* database size and per-table row counts, media directory usage, backup
|
|
1257
|
+
* configuration and the backups on disk, and the settings an operator
|
|
1258
|
+
* gets asked to confirm. Carries no secrets. Runs a count per table and
|
|
1259
|
+
* walks the media directory, so open it, do not poll it. Admin only.
|
|
1260
|
+
* @returns {Promise<{version: string, jvm: object, database: object, media: object, backup: object, settings: object}>}
|
|
1261
|
+
*/
|
|
1262
|
+
server: () => this._request("GET", "/api/v1/admin/server"),
|
|
1263
|
+
/**
|
|
1264
|
+
* Take a database backup right now, outside the nightly schedule.
|
|
1265
|
+
* Resolves to the backup block with `ok` reporting whether the snapshot
|
|
1266
|
+
* succeeded. Uses VACUUM INTO, which only reads, so it is safe while
|
|
1267
|
+
* people are working. Admin only.
|
|
1268
|
+
* @returns {Promise<{ok: boolean, directory: string, backups: Array}>}
|
|
1269
|
+
*/
|
|
1270
|
+
backup: () => this._request("POST", "/api/v1/admin/backup"),
|
|
1271
|
+
/**
|
|
1272
|
+
* Documents currently held by an editing lock, with who holds each and
|
|
1273
|
+
* when it expires on its own. Admin only.
|
|
1274
|
+
* @returns {Promise<{entries: Array<{documentId: string, userId: string, expiresAt: number}>}>}
|
|
1275
|
+
*/
|
|
1276
|
+
locks: () => this._request("GET", "/api/v1/admin/locks"),
|
|
1277
|
+
/**
|
|
1278
|
+
* Drop the lock on a document whoever holds it. Idempotent. For a client
|
|
1279
|
+
* that went away without releasing one. Admin only.
|
|
1280
|
+
* @param {string} documentId - The document ID
|
|
1281
|
+
*/
|
|
1282
|
+
releaseLock: (documentId) =>
|
|
1283
|
+
this._request("DELETE", `/api/v1/admin/locks/${documentId}`),
|
|
1284
|
+
/**
|
|
1285
|
+
* Live login and invite rate-limit buckets: the address, the account
|
|
1286
|
+
* where there is one, failures inside the window, the limit, and whether
|
|
1287
|
+
* it is currently blocking. Admin only.
|
|
1288
|
+
* @returns {Promise<{windowMs: number, logins: Array, ips: Array, invites: Array}>}
|
|
1289
|
+
*/
|
|
1290
|
+
rateLimits: () => this._request("GET", "/api/v1/admin/rate-limits"),
|
|
1291
|
+
/**
|
|
1292
|
+
* Forget recorded rate-limit failures. With `ip`, clears that address,
|
|
1293
|
+
* narrowed to one account with `userId`. With neither, clears every
|
|
1294
|
+
* bucket. Only ever unblocks. Admin only.
|
|
1295
|
+
* @param {object} [opts]
|
|
1296
|
+
* @param {string} [opts.ip] - Clear only this address
|
|
1297
|
+
* @param {string} [opts.userId] - Narrow to one account on that address
|
|
1298
|
+
*/
|
|
1299
|
+
clearRateLimits: ({ ip, userId } = {}) =>
|
|
1300
|
+
this._request("DELETE", "/api/v1/admin/rate-limits", {
|
|
1301
|
+
queryParams: { ip, "user-id": userId },
|
|
1302
|
+
}),
|
|
1303
|
+
/**
|
|
1304
|
+
* The tail of the configured log file. Resolves with an `error` string
|
|
1305
|
+
* instead of lines when no log file is configured or it does not exist
|
|
1306
|
+
* yet. Admin only.
|
|
1307
|
+
* @param {object} [opts]
|
|
1308
|
+
* @param {number} [opts.lines] - How many lines (default 200, max 2000)
|
|
1309
|
+
* @returns {Promise<{file: (string|null), lines: string[], error?: string}>}
|
|
1310
|
+
*/
|
|
1311
|
+
logs: ({ lines } = {}) =>
|
|
1312
|
+
this._request("GET", "/api/v1/admin/logs", {
|
|
1313
|
+
queryParams: { lines },
|
|
1314
|
+
}),
|
|
1315
|
+
/**
|
|
1316
|
+
* Private user-data entries across every account, each carrying its
|
|
1317
|
+
* `userId`. `/users/:userId/data` has always been owner-or-admin; this
|
|
1318
|
+
* is the same reach across accounts at once. Transparently follows
|
|
1319
|
+
* pagination cursors and returns the full flat array. Admin only.
|
|
1320
|
+
*
|
|
1321
|
+
* Narrow with `prefix` (the literal head of a key) and/or `pattern`, a
|
|
1322
|
+
* GLOB over the whole key (`*` any run, `?` one character) — the way to
|
|
1323
|
+
* ask for a key convention identified by a segment in the middle, e.g.
|
|
1324
|
+
* `igt:assistant:*:meta:*`. Values come only with `includeValues`, and
|
|
1325
|
+
* 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
|
+
* @param {object} [opts]
|
|
1328
|
+
* @param {string} [opts.prefix] - Only keys starting with this
|
|
1329
|
+
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
1330
|
+
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
1331
|
+
* @returns {Promise<Array<{userId: string, key: string, updatedAt: string, value?: *}>>}
|
|
1332
|
+
*/
|
|
1333
|
+
userData: ({ prefix, pattern, includeValues } = {}) =>
|
|
1334
|
+
listAll(this, "/api/v1/admin/user-data", {
|
|
1335
|
+
query: {
|
|
1336
|
+
prefix,
|
|
1337
|
+
pattern,
|
|
1338
|
+
"include-values": includeValues,
|
|
1339
|
+
},
|
|
1340
|
+
}),
|
|
1341
|
+
/**
|
|
1342
|
+
* One page of private user-data entries across accounts, ordered by
|
|
1343
|
+
* (user, key). Admin only.
|
|
1344
|
+
* @param {object} [opts]
|
|
1345
|
+
* @param {string} [opts.prefix] - Only keys starting with this
|
|
1346
|
+
* @param {string} [opts.pattern] - Only keys matching this GLOB
|
|
1347
|
+
* @param {boolean} [opts.includeValues] - Also return each entry's value
|
|
1348
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1349
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1350
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1351
|
+
*/
|
|
1352
|
+
userDataPage: ({ prefix, pattern, includeValues, limit, cursor } = {}) =>
|
|
1353
|
+
listPage(this, "/api/v1/admin/user-data", {
|
|
1354
|
+
limit,
|
|
1355
|
+
cursor,
|
|
1356
|
+
query: {
|
|
1357
|
+
prefix,
|
|
1358
|
+
pattern,
|
|
1359
|
+
"include-values": includeValues,
|
|
1360
|
+
},
|
|
1361
|
+
}),
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
this.audit = {
|
|
1365
|
+
/**
|
|
1366
|
+
* The audit log across every project, oldest first. Same fold, window
|
|
1367
|
+
* and op-type filter as the per-project read, with the entity scope
|
|
1368
|
+
* dropped. Admin only. Transparently follows pagination cursors and
|
|
1369
|
+
* 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
|
+
* @param {object} [opts]
|
|
1372
|
+
* @param {string} [opts.startTime] - Only operations at or after this instant
|
|
1373
|
+
* @param {string} [opts.endTime] - Only operations at or before this instant
|
|
1374
|
+
* @param {string[]|string} [opts.opTypes] - Only these op types (e.g.
|
|
1375
|
+
* `['span-layer/create']`). An entry appears when one of its operations
|
|
1376
|
+
* matches, carrying only the ones that did.
|
|
1377
|
+
*/
|
|
1378
|
+
list: ({ startTime, endTime, opTypes } = {}) =>
|
|
1379
|
+
listAll(this, "/api/v1/audit", {
|
|
1380
|
+
query: {
|
|
1381
|
+
"start-time": startTime,
|
|
1382
|
+
"end-time": endTime,
|
|
1383
|
+
"op-types": opTypesParam(opTypes),
|
|
1384
|
+
},
|
|
1385
|
+
}),
|
|
1386
|
+
/**
|
|
1387
|
+
* Fetch a single page of the instance-wide audit log. Admin only.
|
|
1388
|
+
* @param {object} [opts]
|
|
1389
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first, which
|
|
1390
|
+
* is what a feed wants. A cursor belongs to the direction that made it.
|
|
1391
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1392
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1393
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1394
|
+
*/
|
|
1395
|
+
listPage: ({ startTime, endTime, opTypes, order, limit, cursor } = {}) =>
|
|
1396
|
+
listPage(this, "/api/v1/audit", {
|
|
1397
|
+
limit,
|
|
1398
|
+
cursor,
|
|
1399
|
+
query: {
|
|
1400
|
+
"start-time": startTime,
|
|
1401
|
+
"end-time": endTime,
|
|
1402
|
+
"op-types": opTypesParam(opTypes),
|
|
1403
|
+
order,
|
|
1404
|
+
},
|
|
1405
|
+
}),
|
|
1406
|
+
/**
|
|
1407
|
+
* 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
|
+
* @returns {AsyncGenerator<Array>}
|
|
1410
|
+
*/
|
|
1411
|
+
iterPages: ({ startTime, endTime, opTypes, pageSize } = {}) =>
|
|
1412
|
+
iterPages(this, "/api/v1/audit", {
|
|
1413
|
+
pageSize,
|
|
1414
|
+
query: {
|
|
1415
|
+
"start-time": startTime,
|
|
1416
|
+
"end-time": endTime,
|
|
1417
|
+
"op-types": opTypesParam(opTypes),
|
|
1418
|
+
},
|
|
1419
|
+
}),
|
|
1420
|
+
/**
|
|
1421
|
+
* Per-user activity counts. `changes` is the number of logical actions,
|
|
1422
|
+
* folded the way the audit feed folds them, so one "Confirm word
|
|
1423
|
+
* analysis" counts once however many rows it wrote; `operations` is the
|
|
1424
|
+
* unfolded row count. Only users who did something appear — subtract
|
|
1425
|
+
* from the roster you already hold to find the ones who did not.
|
|
1426
|
+
*
|
|
1427
|
+
* With `projectId`, scoped to that project and open to its maintainers.
|
|
1428
|
+
* Without one, instance-wide and admin only.
|
|
1429
|
+
* @param {object} [opts]
|
|
1430
|
+
* @param {string} [opts.projectId] - Scope to one project
|
|
1431
|
+
* @param {string} [opts.startTime] - Only count at or after this instant
|
|
1432
|
+
* @param {string} [opts.endTime] - Only count at or before this instant
|
|
1433
|
+
* @param {boolean} [opts.daily] - Also return `byDay`, a list of
|
|
1434
|
+
* `{date, changes}` per user, oldest first, at the cost of a second
|
|
1435
|
+
* grouped scan
|
|
1436
|
+
* @returns {Promise<Array<{user: object, operations: number, changes: number, documents: number, firstTs: string, lastTs: string, byDay?: object}>>}
|
|
1437
|
+
*/
|
|
1438
|
+
tally: async ({ projectId, startTime, endTime, daily } = {}) => {
|
|
1439
|
+
const path = projectId
|
|
1440
|
+
? `/api/v1/projects/${projectId}/audit/tally`
|
|
1441
|
+
: "/api/v1/audit/tally";
|
|
1442
|
+
const result = await this._request("GET", path, {
|
|
1443
|
+
queryParams: {
|
|
1444
|
+
"start-time": startTime,
|
|
1445
|
+
"end-time": endTime,
|
|
1446
|
+
daily,
|
|
1447
|
+
},
|
|
1448
|
+
});
|
|
1449
|
+
return result.entries;
|
|
1450
|
+
},
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1137
1453
|
this.tokenLayers = {
|
|
1138
1454
|
/**
|
|
1139
1455
|
* Shift a token layer's display order.
|
|
@@ -1345,6 +1661,31 @@ class PlaidClient {
|
|
|
1345
1661
|
"op-types": opTypesParam(opTypes),
|
|
1346
1662
|
},
|
|
1347
1663
|
}),
|
|
1664
|
+
/**
|
|
1665
|
+
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
1666
|
+
* rather than audit() wherever the caller wants the recent end of a log
|
|
1667
|
+
* that may be long: audit() walks every page before it resolves.
|
|
1668
|
+
* @param {object} [opts]
|
|
1669
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
1670
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1671
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1672
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1673
|
+
*/
|
|
1674
|
+
auditPage: (
|
|
1675
|
+
documentId,
|
|
1676
|
+
{ startTime, endTime, asOf, opTypes, order, limit, cursor } = {},
|
|
1677
|
+
) =>
|
|
1678
|
+
listPage(this, `/api/v1/documents/${documentId}/audit`, {
|
|
1679
|
+
limit,
|
|
1680
|
+
cursor,
|
|
1681
|
+
query: {
|
|
1682
|
+
"start-time": startTime,
|
|
1683
|
+
"end-time": endTime,
|
|
1684
|
+
"as-of": asOf,
|
|
1685
|
+
"op-types": opTypesParam(opTypes),
|
|
1686
|
+
order,
|
|
1687
|
+
},
|
|
1688
|
+
}),
|
|
1348
1689
|
/**
|
|
1349
1690
|
* Restore a document to its state at an earlier time, as one
|
|
1350
1691
|
* operation: what was deleted since then comes back under its original
|
|
@@ -1361,7 +1702,10 @@ class PlaidClient {
|
|
|
1361
1702
|
*/
|
|
1362
1703
|
restore: (documentId, asOf, { dryRun } = {}, auditMessage) =>
|
|
1363
1704
|
this._request("POST", `/api/v1/documents/${documentId}/restore`, {
|
|
1364
|
-
queryParams: {
|
|
1705
|
+
queryParams: {
|
|
1706
|
+
"as-of": asOf,
|
|
1707
|
+
"dry-run": dryRun ? "true" : undefined,
|
|
1708
|
+
},
|
|
1365
1709
|
auditMessage,
|
|
1366
1710
|
}),
|
|
1367
1711
|
/**
|
|
@@ -1416,6 +1760,25 @@ class PlaidClient {
|
|
|
1416
1760
|
auditMessage,
|
|
1417
1761
|
body: bodyOf({ "project-id": projectId, name, metadata }),
|
|
1418
1762
|
}),
|
|
1763
|
+
/**
|
|
1764
|
+
* Copy a document and everything in it into a new document of the same
|
|
1765
|
+
* project, as one operation. The copy shares the source's layers and
|
|
1766
|
+
* the vocabulary entries its links name, and holds the source's texts,
|
|
1767
|
+
* tokens, spans, relations and vocab links under fresh ids, with their
|
|
1768
|
+
* metadata. Comments do not travel. The media file does, unless
|
|
1769
|
+
* `includeMedia` is false. Resolves to `{ id }`, plus `mediaError` when
|
|
1770
|
+
* the source had media the copy could not take with it.
|
|
1771
|
+
* @param {string} documentId - The document to copy
|
|
1772
|
+
* @param {string} name - The new document's name
|
|
1773
|
+
* @param {object} [options] - `{ includeMedia }`: false leaves the media
|
|
1774
|
+
* file behind
|
|
1775
|
+
* @param {string} [auditMessage] - Custom audit message for this operation
|
|
1776
|
+
*/
|
|
1777
|
+
copy: (documentId, name, { includeMedia } = {}, auditMessage) =>
|
|
1778
|
+
this._request("POST", `/api/v1/documents/${documentId}/copy`, {
|
|
1779
|
+
auditMessage,
|
|
1780
|
+
body: bodyOf({ name, "include-media": includeMedia }),
|
|
1781
|
+
}),
|
|
1419
1782
|
};
|
|
1420
1783
|
|
|
1421
1784
|
this.projects = {
|
|
@@ -1522,6 +1885,31 @@ class PlaidClient {
|
|
|
1522
1885
|
"op-types": opTypesParam(opTypes),
|
|
1523
1886
|
},
|
|
1524
1887
|
}),
|
|
1888
|
+
/**
|
|
1889
|
+
* One page of the same log, newest-first with `order: "desc"`. Use this
|
|
1890
|
+
* rather than audit() wherever the caller wants the recent end of a log
|
|
1891
|
+
* that may be long: audit() walks every page before it resolves.
|
|
1892
|
+
* @param {object} [opts]
|
|
1893
|
+
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
1894
|
+
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1895
|
+
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1896
|
+
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1897
|
+
*/
|
|
1898
|
+
auditPage: (
|
|
1899
|
+
projectId,
|
|
1900
|
+
{ startTime, endTime, asOf, opTypes, order, limit, cursor } = {},
|
|
1901
|
+
) =>
|
|
1902
|
+
listPage(this, `/api/v1/projects/${projectId}/audit`, {
|
|
1903
|
+
limit,
|
|
1904
|
+
cursor,
|
|
1905
|
+
query: {
|
|
1906
|
+
"start-time": startTime,
|
|
1907
|
+
"end-time": endTime,
|
|
1908
|
+
"as-of": asOf,
|
|
1909
|
+
"op-types": opTypesParam(opTypes),
|
|
1910
|
+
order,
|
|
1911
|
+
},
|
|
1912
|
+
}),
|
|
1525
1913
|
/**
|
|
1526
1914
|
* When the calling user last wrote to each document in a project, as a
|
|
1527
1915
|
* `{documentId: timestamp}` map. Documents they have never written to
|
|
@@ -2009,6 +2397,16 @@ class PlaidClient {
|
|
|
2009
2397
|
*/
|
|
2010
2398
|
bulkDelete: (body, auditMessage) =>
|
|
2011
2399
|
this._request("DELETE", "/api/v1/tokens/bulk", { auditMessage, body }),
|
|
2400
|
+
/**
|
|
2401
|
+
* 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.
|
|
2403
|
+
* @returns {Promise<{count: number}>} How many tokens were updated.
|
|
2404
|
+
*/
|
|
2405
|
+
bulkUpdate: (body, auditMessage) =>
|
|
2406
|
+
this._request("PATCH", "/api/v1/tokens/bulk", {
|
|
2407
|
+
auditMessage,
|
|
2408
|
+
body,
|
|
2409
|
+
}),
|
|
2012
2410
|
/**
|
|
2013
2411
|
* Split a token at a Unicode code-point offset. The original token becomes the
|
|
2014
2412
|
* left half (keeps its ID, spans, vocab-links); the new right token's ID is returned.
|
|
@@ -2163,8 +2561,25 @@ class PlaidClient {
|
|
|
2163
2561
|
* @param {Object} [opts] - {requestId, onAccepted}
|
|
2164
2562
|
* @returns {Promise<any>} The service's result
|
|
2165
2563
|
*/
|
|
2166
|
-
requestService: (
|
|
2167
|
-
|
|
2564
|
+
requestService: (
|
|
2565
|
+
projectId,
|
|
2566
|
+
serviceId,
|
|
2567
|
+
data,
|
|
2568
|
+
timeout,
|
|
2569
|
+
onProgress,
|
|
2570
|
+
signal,
|
|
2571
|
+
opts,
|
|
2572
|
+
) =>
|
|
2573
|
+
requestService(
|
|
2574
|
+
this,
|
|
2575
|
+
projectId,
|
|
2576
|
+
serviceId,
|
|
2577
|
+
data,
|
|
2578
|
+
timeout,
|
|
2579
|
+
onProgress,
|
|
2580
|
+
signal,
|
|
2581
|
+
opts,
|
|
2582
|
+
),
|
|
2168
2583
|
|
|
2169
2584
|
/**
|
|
2170
2585
|
* Rejoin a service request made earlier and await its result: the
|
|
@@ -2180,8 +2595,21 @@ class PlaidClient {
|
|
|
2180
2595
|
* @param {AbortSignal} [signal] - Abort to stop waiting
|
|
2181
2596
|
* @returns {Promise<any>} The service's result
|
|
2182
2597
|
*/
|
|
2183
|
-
attachServiceRequest: (
|
|
2184
|
-
|
|
2598
|
+
attachServiceRequest: (
|
|
2599
|
+
projectId,
|
|
2600
|
+
requestId,
|
|
2601
|
+
timeout,
|
|
2602
|
+
onProgress,
|
|
2603
|
+
signal,
|
|
2604
|
+
) =>
|
|
2605
|
+
attachServiceRequest(
|
|
2606
|
+
this,
|
|
2607
|
+
projectId,
|
|
2608
|
+
requestId,
|
|
2609
|
+
timeout,
|
|
2610
|
+
onProgress,
|
|
2611
|
+
signal,
|
|
2612
|
+
),
|
|
2185
2613
|
|
|
2186
2614
|
/**
|
|
2187
2615
|
* Ask the service to stop a request made earlier. The request still
|
|
@@ -2238,7 +2666,11 @@ class PlaidClient {
|
|
|
2238
2666
|
// and the server ran the query against the batch's tx Connection, which
|
|
2239
2667
|
// 500s and rolls back every write in that batch.
|
|
2240
2668
|
this.query = (body, auditMessage) =>
|
|
2241
|
-
this._request("POST", "/api/v1/query", {
|
|
2669
|
+
this._request("POST", "/api/v1/query", {
|
|
2670
|
+
auditMessage,
|
|
2671
|
+
body,
|
|
2672
|
+
bypassBatch: true,
|
|
2673
|
+
});
|
|
2242
2674
|
|
|
2243
2675
|
// Logical-operation groups (audit-log grouping). There is no create: a
|
|
2244
2676
|
// group row is made lazily by the first write carrying `?group-id=`
|
|
@@ -2333,7 +2765,10 @@ class PlaidClient {
|
|
|
2333
2765
|
* @param {string} [opts.entityId] - With entityType, one entity's thread
|
|
2334
2766
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2335
2767
|
*/
|
|
2336
|
-
listPage: (
|
|
2768
|
+
listPage: (
|
|
2769
|
+
projectId,
|
|
2770
|
+
{ limit, cursor, documentId, entityType, entityId } = {},
|
|
2771
|
+
) =>
|
|
2337
2772
|
listPage(this, `/api/v1/projects/${projectId}/comments`, {
|
|
2338
2773
|
limit,
|
|
2339
2774
|
cursor,
|
|
@@ -2354,7 +2789,10 @@ class PlaidClient {
|
|
|
2354
2789
|
* @param {string} [opts.entityId] - With entityType, one entity's thread
|
|
2355
2790
|
* @returns {AsyncGenerator<Array>}
|
|
2356
2791
|
*/
|
|
2357
|
-
iterPages: (
|
|
2792
|
+
iterPages: (
|
|
2793
|
+
projectId,
|
|
2794
|
+
{ pageSize, documentId, entityType, entityId } = {},
|
|
2795
|
+
) =>
|
|
2358
2796
|
iterPages(this, `/api/v1/projects/${projectId}/comments`, {
|
|
2359
2797
|
pageSize,
|
|
2360
2798
|
query: {
|
|
@@ -2421,10 +2859,14 @@ class PlaidClient {
|
|
|
2421
2859
|
* @returns {Promise<Object<string, number>>}
|
|
2422
2860
|
*/
|
|
2423
2861
|
countsInVocab: (vocabId, { entityId } = {}) =>
|
|
2424
|
-
this._request(
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2862
|
+
this._request(
|
|
2863
|
+
"GET",
|
|
2864
|
+
`/api/v1/vocab-layers/${vocabId}/comments/counts`,
|
|
2865
|
+
{
|
|
2866
|
+
queryParams: { "entity-id": entityId },
|
|
2867
|
+
skipResponseTransform: true,
|
|
2868
|
+
},
|
|
2869
|
+
),
|
|
2428
2870
|
};
|
|
2429
2871
|
}
|
|
2430
2872
|
|
|
@@ -2580,7 +3022,9 @@ class PlaidClient {
|
|
|
2580
3022
|
// must not fail on its size alone.
|
|
2581
3023
|
const results = [];
|
|
2582
3024
|
for (let i = 0; i < ops.length; i += MAX_BATCH_OPS) {
|
|
2583
|
-
results.push(
|
|
3025
|
+
results.push(
|
|
3026
|
+
...(await this._postBatch(url, ops.slice(i, i + MAX_BATCH_OPS))),
|
|
3027
|
+
);
|
|
2584
3028
|
}
|
|
2585
3029
|
return results;
|
|
2586
3030
|
} finally {
|
|
@@ -2703,6 +3147,27 @@ class PlaidClient {
|
|
|
2703
3147
|
* @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
|
|
2704
3148
|
* @returns {Promise<PlaidClient>} - Authenticated client instance
|
|
2705
3149
|
*/
|
|
3150
|
+
/**
|
|
3151
|
+
* Liveness, version and database size, with NO authentication and no client
|
|
3152
|
+
* instance — for a launcher or status page checking whether a server is up
|
|
3153
|
+
* before anyone logs in.
|
|
3154
|
+
* @param {string} baseUrl - The API base URL
|
|
3155
|
+
* @param {object} [options]
|
|
3156
|
+
*/
|
|
3157
|
+
static async health(baseUrl, options = {}) {
|
|
3158
|
+
return anonymousGet(baseUrl, "/health", options);
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
/**
|
|
3162
|
+
* The limits this server enforces, with NO authentication and no client
|
|
3163
|
+
* instance. See the instance method for the shape.
|
|
3164
|
+
* @param {string} baseUrl - The API base URL
|
|
3165
|
+
* @param {object} [options]
|
|
3166
|
+
*/
|
|
3167
|
+
static async info(baseUrl, options = {}) {
|
|
3168
|
+
return anonymousGet(baseUrl, "/api/v1/info", options);
|
|
3169
|
+
}
|
|
3170
|
+
|
|
2706
3171
|
/**
|
|
2707
3172
|
* Build the link to hand someone for an invite code. The server never sees
|
|
2708
3173
|
* an app URL, so the app that minted the invite is the one that names it.
|
|
@@ -2730,12 +3195,7 @@ class PlaidClient {
|
|
|
2730
3195
|
* @returns {Promise<{kind: string, status: string, expiresAt: string, projectName?: string, projectRole?: string, email?: string, grantAdmin: boolean}>}
|
|
2731
3196
|
*/
|
|
2732
3197
|
static async lookupInvite(baseUrl, code, options = {}) {
|
|
2733
|
-
return anonymousPost(
|
|
2734
|
-
baseUrl,
|
|
2735
|
-
"/api/v1/invite-codes/lookup",
|
|
2736
|
-
{ code },
|
|
2737
|
-
options,
|
|
2738
|
-
);
|
|
3198
|
+
return anonymousPost(baseUrl, "/api/v1/invites/lookup", { code }, options);
|
|
2739
3199
|
}
|
|
2740
3200
|
|
|
2741
3201
|
/**
|
|
@@ -2766,7 +3226,7 @@ class PlaidClient {
|
|
|
2766
3226
|
) {
|
|
2767
3227
|
const data = await anonymousPost(
|
|
2768
3228
|
baseUrl,
|
|
2769
|
-
"/api/v1/
|
|
3229
|
+
"/api/v1/invites/redeem",
|
|
2770
3230
|
bodyOf({ code, email, password, "display-name": displayName }),
|
|
2771
3231
|
options,
|
|
2772
3232
|
);
|
|
@@ -2831,6 +3291,7 @@ export {
|
|
|
2831
3291
|
export {
|
|
2832
3292
|
PLAID_NAMESPACE,
|
|
2833
3293
|
ROLE_KEY,
|
|
3294
|
+
PRESERVE_ON_SPLIT_KEY,
|
|
2834
3295
|
ROLES,
|
|
2835
3296
|
readRole,
|
|
2836
3297
|
findByRole,
|
|
@@ -2847,6 +3308,11 @@ export {
|
|
|
2847
3308
|
buildDefaultValues,
|
|
2848
3309
|
coerceParamValues,
|
|
2849
3310
|
} from "./serviceSchema.js";
|
|
3311
|
+
// Cooperative cancellation for a service AUTHORED in JS: `progress()` is a
|
|
3312
|
+
// checkpoint that throws ServiceCancelled once the requester stops the
|
|
3313
|
+
// request, and `critical()` holds that off around writes. See ./services.js
|
|
3314
|
+
// and the manual, "Stopping a request".
|
|
3315
|
+
export { ServiceCancelled, createCancelScope } from "./services.js";
|
|
2850
3316
|
// Provenance: the cross-app convention for who made an annotation (flat
|
|
2851
3317
|
// prov/provSource/provConfirmed metadata; absence = a verifier; 'inferred' =
|
|
2852
3318
|
// a machine; 'contributed' = a person whose work is reviewed), plus the
|
|
@@ -2855,6 +3321,7 @@ export {
|
|
|
2855
3321
|
PROV,
|
|
2856
3322
|
PROV_STATES,
|
|
2857
3323
|
PROV_CONFIRMED,
|
|
3324
|
+
PROVENANCE_KEYS,
|
|
2858
3325
|
stampInferred,
|
|
2859
3326
|
confirmedInferred,
|
|
2860
3327
|
stampContributed,
|