@larc-iu/plaid-client 0.2.0-alpha.3 → 0.2.0-alpha.32

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.
Files changed (73) hide show
  1. package/README.md +39 -0
  2. package/package.json +13 -6
  3. package/src/apiAnnotations.d.ts +508 -0
  4. package/src/apiAnnotations.js +543 -0
  5. package/src/apiDocuments.d.ts +569 -0
  6. package/src/apiDocuments.js +551 -0
  7. package/src/apiLayers.d.ts +369 -0
  8. package/src/apiLayers.js +222 -0
  9. package/src/apiMessages.d.ts +293 -0
  10. package/src/apiMessages.js +265 -0
  11. package/src/apiPeople.d.ts +626 -0
  12. package/src/apiPeople.js +698 -0
  13. package/src/apiProject.d.ts +761 -0
  14. package/src/apiProject.js +774 -0
  15. package/src/apiServer.d.ts +457 -0
  16. package/src/apiServer.js +424 -0
  17. package/src/apiVocab.d.ts +456 -0
  18. package/src/apiVocab.js +425 -0
  19. package/src/batch.d.ts +54 -0
  20. package/src/batch.js +323 -0
  21. package/src/batchRef.d.ts +30 -0
  22. package/src/batchRef.js +128 -0
  23. package/src/client.d.ts +1321 -0
  24. package/src/client.js +743 -0
  25. package/src/codepoint.d.ts +94 -0
  26. package/src/codepoint.js +131 -1
  27. package/src/constraints.d.ts +85 -0
  28. package/src/constraints.js +112 -0
  29. package/src/created.d.ts +2 -0
  30. package/src/created.js +29 -0
  31. package/src/documentLock.d.ts +172 -0
  32. package/src/documentLock.js +384 -0
  33. package/src/entryForms.d.ts +68 -0
  34. package/src/entryForms.js +164 -0
  35. package/src/events.d.ts +60 -0
  36. package/src/events.js +271 -0
  37. package/src/families.d.ts +349 -0
  38. package/src/families.js +396 -0
  39. package/src/http.d.ts +233 -0
  40. package/src/http.js +809 -93
  41. package/src/ids.d.ts +26 -0
  42. package/src/ids.js +96 -0
  43. package/src/index.d.ts +101 -0
  44. package/src/index.js +183 -2105
  45. package/src/metadataOps.d.ts +15 -0
  46. package/src/metadataOps.js +124 -0
  47. package/src/operations.d.ts +36 -0
  48. package/src/operations.js +164 -0
  49. package/src/pagination.d.ts +49 -0
  50. package/src/pagination.js +20 -53
  51. package/src/provenance.d.ts +243 -0
  52. package/src/provenance.js +458 -36
  53. package/src/replayed.d.ts +3 -0
  54. package/src/replayed.js +31 -0
  55. package/src/requester.d.ts +60 -0
  56. package/src/requester.js +74 -0
  57. package/src/roles.d.ts +104 -0
  58. package/src/roles.js +64 -2
  59. package/src/serviceSchema.d.ts +337 -0
  60. package/src/serviceSchema.js +177 -60
  61. package/src/services.d.ts +319 -0
  62. package/src/services.js +742 -58
  63. package/src/sse.d.ts +30 -0
  64. package/src/sse.js +80 -3
  65. package/src/testing.d.ts +128 -0
  66. package/src/testing.js +379 -0
  67. package/src/textEdits.d.ts +59 -0
  68. package/src/textEdits.js +234 -0
  69. package/src/transforms.d.ts +25 -0
  70. package/src/transforms.js +11 -1
  71. package/src/types.d.ts +867 -0
  72. package/src/types.js +663 -0
  73. package/index.d.ts +0 -532
@@ -0,0 +1,424 @@
1
+ /**
2
+ * The server bundles: the server's facts and health, the instance admin
3
+ * controls and the instance-wide audit log, each a factory of the client
4
+ * (client.js installs them).
5
+ */
6
+
7
+ import { auditQuery } from "./families.js";
8
+ import { iterPages, listAll, listPage } from "./pagination.js";
9
+ import { PlaidClient } from "./client.js";
10
+
11
+ /**
12
+ * How long `admin.backup()` waits for the server to finish writing a backup:
13
+ * minutes on a large database, past the usual per-request timeout.
14
+ */
15
+ export const BACKUP_TIMEOUT_MS = 30 * 60 * 1000;
16
+
17
+ /**
18
+ * @typedef {import("./types.js").TextEditOp} TextEditOp
19
+ * @typedef {import("./types.js").TextGap} TextGap
20
+ * @typedef {import("./types.js").MetadataOp} MetadataOp
21
+ * @typedef {import("./types.js").LayerConstraint} LayerConstraint
22
+ * @typedef {import("./types.js").ConstraintWriteOptions} ConstraintWriteOptions
23
+ * @typedef {import("./types.js").ConstraintViolation} ConstraintViolation
24
+ * @typedef {import("./types.js").Requester} Requester
25
+ * @typedef {import("./types.js").BatchRef} BatchRef
26
+ * @typedef {import("./types.js").ServiceParam} ServiceParam
27
+ * @typedef {import("./types.js").ServiceInfo} ServiceInfo
28
+ * @typedef {import("./types.js").DiscoveredService} DiscoveredService
29
+ * @typedef {import("./types.js").ResponseHelper} ResponseHelper
30
+ * @typedef {import("./types.js").ServiceRegistration} ServiceRegistration
31
+ * @typedef {import("./types.js").ServiceStatusEvent} ServiceStatusEvent
32
+ * @typedef {import("./types.js").OperationGroupsBundle} OperationGroupsBundle
33
+ * @typedef {import("./types.js").PlaidClientOptions} PlaidClientOptions
34
+ * @typedef {import("./types.js").OperationKind} OperationKind
35
+ * @typedef {import("./types.js").KeySeed} KeySeed
36
+ * @typedef {import("./types.js").PlaidBatch} PlaidBatch
37
+ * @typedef {import("./types.js").AuditPageOptions} AuditPageOptions
38
+ * @typedef {import("./types.js").VocabItemRestoreSummary} VocabItemRestoreSummary
39
+ * @typedef {import("./types.js").UserDataEntry} UserDataEntry
40
+ * @typedef {import("./types.js").AdminUserDataEntry} AdminUserDataEntry
41
+ * @typedef {import("./types.js").ActivityTallyRow} ActivityTallyRow
42
+ * @typedef {import("./types.js").ConstraintRepair} ConstraintRepair
43
+ * @typedef {import("./types.js").ValueSetRule} ValueSetRule
44
+ * @typedef {import("./types.js").RefusedValues} RefusedValues
45
+ * @typedef {import("./types.js").CommentableType} CommentableType
46
+ * @typedef {import("./types.js").Comment} Comment
47
+ * @typedef {import("./types.js").Guideline} Guideline
48
+ * @typedef {import("./types.js").ClientEventType} ClientEventType
49
+ * @typedef {import("./types.js").ClientEvent} ClientEvent
50
+ * @typedef {import("./types.js").Invite} Invite
51
+ * @typedef {import("./types.js").ServerLimits} ServerLimits
52
+ * @typedef {import("./types.js").ServerHealth} ServerHealth
53
+ * @typedef {import("./types.js").RateLimitBucket} RateLimitBucket
54
+ * @typedef {import("./types.js").RateLimitSnapshot} RateLimitSnapshot
55
+ * @typedef {import("./types.js").BackupStatus} BackupStatus
56
+ * @typedef {import("./types.js").ServerReport} ServerReport
57
+ * @typedef {import("./types.js").LiveLog} LiveLog
58
+ * @typedef {import("./types.js").SSEConnection} SSEConnection
59
+ * @typedef {import("./types.js").SSEStats} SSEStats
60
+ * @typedef {import("./types.js").AtomicValue} AtomicValue
61
+ * @typedef {import("./types.js").Created} Created
62
+ * @typedef {import("./types.js").Project} Project
63
+ * @typedef {import("./types.js").ProjectTextLayer} ProjectTextLayer
64
+ * @typedef {import("./types.js").ProjectTokenLayer} ProjectTokenLayer
65
+ * @typedef {import("./types.js").ProjectSpanLayer} ProjectSpanLayer
66
+ * @typedef {import("./types.js").ProjectRelationLayer} ProjectRelationLayer
67
+ * @typedef {import("./types.js").ProjectVocab} ProjectVocab
68
+ * @typedef {import("./types.js").PlaidDocument} PlaidDocument
69
+ * @typedef {import("./types.js").DocumentTextLayer} DocumentTextLayer
70
+ * @typedef {import("./types.js").DocumentTokenLayer} DocumentTokenLayer
71
+ * @typedef {import("./types.js").DocumentSpanLayer} DocumentSpanLayer
72
+ * @typedef {import("./types.js").PlaidText} PlaidText
73
+ * @typedef {import("./types.js").TextReshape} TextReshape
74
+ * @typedef {import("./types.js").Token} Token
75
+ * @typedef {import("./types.js").Span} Span
76
+ * @typedef {import("./types.js").Relation} Relation
77
+ * @typedef {import("./types.js").VocabItem} VocabItem
78
+ * @typedef {import("./types.js").VocabLink} VocabLink
79
+ * @typedef {import("./types.js").VocabLayer} VocabLayer
80
+ * @typedef {import("./types.js").TextLayer} TextLayer
81
+ * @typedef {import("./types.js").TokenLayer} TokenLayer
82
+ * @typedef {import("./types.js").SpanLayer} SpanLayer
83
+ * @typedef {import("./types.js").RelationLayer} RelationLayer
84
+ * @typedef {import("./types.js").MetadataRecords} MetadataRecords
85
+ * @typedef {import("./types.js").LayerRecords} LayerRecords
86
+ * @typedef {import("./types.js").User} User
87
+ * @typedef {import("./types.js").ProjectListEntry} ProjectListEntry
88
+ * @typedef {import("./types.js").DocumentListEntry} DocumentListEntry
89
+ * @typedef {import("./types.js").ApiToken} ApiToken
90
+ * @typedef {import("./types.js").AuditUser} AuditUser
91
+ * @typedef {import("./types.js").AuditTarget} AuditTarget
92
+ * @typedef {import("./types.js").AuditOp} AuditOp
93
+ * @typedef {import("./types.js").AuditEntry} AuditEntry
94
+ * @typedef {import("./types.js").LockGrant} LockGrant
95
+ * @typedef {import("./types.js").DocumentRestoreSummary} DocumentRestoreSummary
96
+ * @typedef {import("./types.js").RestoreCounts} RestoreCounts
97
+ * @typedef {import("./types.js").OperationGroup} OperationGroup
98
+ * @typedef {import("./types.js").QueryResult} QueryResult
99
+ */
100
+
101
+ /**
102
+ * @template [T=any]
103
+ * @typedef {import("./types.js").Page<T>} Page
104
+ */
105
+
106
+ // Server-level facts. Read once, and again only when they may have
107
+ // changed: after this client's connection to the server was lost and came
108
+ // back (a restart, the only way the limits change, always drops it), or
109
+ // after the server refused a private data write as too large. A caller
110
+ // asking "will this file be accepted" does not pay a round trip to find
111
+ // out. The facts live on the client (`_serverFacts`), so a batch opened
112
+ // on it reads the same.
113
+ /** @param {import("./client.js").PlaidClient} client */
114
+ export function serverApi(client) {
115
+ return {
116
+ /**
117
+ * This server's version and the limits it enforces, e.g.
118
+ * `{ limits: { mediaFileBytes, jsonBodyBytes, batchOperations, … } }`.
119
+ * Sizes are in bytes. Unauthenticated.
120
+ * @returns {Promise<{limits: ServerLimits}>}
121
+ */
122
+ info: () => {
123
+ const facts = client._serverFacts;
124
+ if (!facts.info) {
125
+ const asked = client._request("GET", "/api/v1/info").catch((err) => {
126
+ // A failure must not be kept: a client that starts before the
127
+ // server is up would never see the limits at all.
128
+ if (facts.info === asked) facts.info = null;
129
+ throw err;
130
+ });
131
+ facts.info = asked;
132
+ }
133
+ return facts.info;
134
+ },
135
+ /**
136
+ * Read `GET /info` again and resolve to it. Until it answers, a reader
137
+ * waits for it. The facts known before are kept when the server does
138
+ * not answer, and the error is thrown.
139
+ * @returns {Promise<{limits: ServerLimits}>}
140
+ */
141
+ refresh: () => {
142
+ const facts = client._serverFacts;
143
+ const before = facts.info;
144
+ facts.stale = false;
145
+ const fresh = client._request("GET", "/api/v1/info");
146
+ const held = fresh.catch((err) => {
147
+ if (facts.info === held) facts.info = before;
148
+ if (before) return before;
149
+ throw err;
150
+ });
151
+ held.catch(() => {});
152
+ facts.info = held;
153
+ return fresh;
154
+ },
155
+ /**
156
+ * Just the limits, which is what a caller almost always wants.
157
+ * @returns {Promise<ServerLimits>}
158
+ */
159
+ limits: async () => {
160
+ // Through `self`: tsc types a bundle that names itself as
161
+ // `this.server` in its own methods `any` in the declarations.
162
+ const self = client;
163
+ return (await self.server.info()).limits;
164
+ },
165
+ /**
166
+ * Liveness, version and database size. Unauthenticated, and served
167
+ * outside the REST router at /health, so it answers even while the API
168
+ * is refusing requests. Never cached — the point is that it is current.
169
+ * @returns {Promise<ServerHealth>}
170
+ */
171
+ health: () => PlaidClient.health(client.baseUrl, { timeout: client.timeout }),
172
+ };
173
+ }
174
+
175
+ /** @param {import("./client.js").PlaidClient} client */
176
+ export function adminApi(client) {
177
+ return {
178
+ /**
179
+ * Everything about the server in one read: version and JVM uptime,
180
+ * database size and per-table row counts, media directory usage, backup
181
+ * configuration and the backups on disk, and the settings an operator
182
+ * gets asked to confirm. Carries no secrets. Runs a count per table and
183
+ * walks the media directory, so open it, do not poll it. Admin only.
184
+ * @returns {Promise<ServerReport>}
185
+ */
186
+ server: () => client._request("GET", "/api/v1/admin/server"),
187
+ /**
188
+ * Take a database backup right now, outside the nightly schedule.
189
+ * Resolves to the backup block, with `ok` true, once the backup is
190
+ * written. A backup the server could not write rejects with status 500,
191
+ * and the error's `responseData` is the backup block with `ok` false (the
192
+ * reason is in the server log). Uses VACUUM INTO, which only reads, so it
193
+ * is safe while people are working. Admin only.
194
+ *
195
+ * The server answers once the backup is written, which takes minutes on
196
+ * a large database, so this waits up to `BACKUP_TIMEOUT_MS` (30 minutes)
197
+ * rather than the client's usual timeout. A proxy in front of the server
198
+ * may still give up first (a 504): the backup goes on, and its file
199
+ * shows in `server().backup.backups` once it is written.
200
+ * @returns {Promise<BackupStatus>}
201
+ *
202
+ * outOfBand, as every admin action on the server itself is: none of them
203
+ * writes project data (see the note at the top of http.js).
204
+ */
205
+ backup: () =>
206
+ client._request("POST", "/api/v1/admin/backup", {
207
+ outOfBand: true,
208
+ timeout: BACKUP_TIMEOUT_MS,
209
+ }),
210
+ /**
211
+ * Documents currently held by an editing lock, with who holds each and
212
+ * when it expires on its own. Admin only.
213
+ * @returns {Promise<{entries: Array<{documentId: string, userId: string, expiresAt: number}>}>}
214
+ */
215
+ locks: () => client._request("GET", "/api/v1/admin/locks"),
216
+ /**
217
+ * Drop the lock on a document whoever holds it. Idempotent. For a client
218
+ * that went away without releasing one. Admin only.
219
+ * @param {string} documentId - The document ID
220
+ * @returns {Promise<{result: string}>}
221
+ */
222
+ releaseLock: (documentId) =>
223
+ client._request("DELETE", `/api/v1/admin/locks/${documentId}`, {
224
+ outOfBand: true,
225
+ }),
226
+ /**
227
+ * Live login and invite rate-limit buckets: the address, the account
228
+ * where there is one, failures inside the window, the limit, and whether
229
+ * it is currently blocking. Admin only.
230
+ * @returns {Promise<RateLimitSnapshot>}
231
+ */
232
+ rateLimits: () => client._request("GET", "/api/v1/admin/rate-limits"),
233
+ /**
234
+ * Forget recorded rate-limit failures. With `ip`, clears that address,
235
+ * narrowed to one account with `userId`. With neither, clears every
236
+ * bucket. Only ever unblocks. Admin only.
237
+ * @param {object} [opts]
238
+ * @param {string} [opts.ip] - Clear only this address
239
+ * @param {string} [opts.userId] - Narrow to one account on that address
240
+ * @returns {Promise<{result: string}>}
241
+ */
242
+ clearRateLimits: ({ ip, userId } = {}) =>
243
+ client._request("DELETE", "/api/v1/admin/rate-limits", {
244
+ outOfBand: true,
245
+ queryParams: { ip, "user-id": userId },
246
+ }),
247
+ /**
248
+ * What the server has logged, structured and filtered, from an in-memory
249
+ * buffer kept whether or not a log file is configured. `requests` is one
250
+ * entry per HTTP request (method, path, status, duration, and who made
251
+ * it), `events` is everything else, with a stack trace where there was
252
+ * one. They are buffered separately so a burst of requests cannot evict
253
+ * an error. Both come newest first, `matched` counts what passed the
254
+ * filters, and request `stats` describe the filtered set. Covers Plaid's
255
+ * own log stream since the last restart: for library messages and older
256
+ * history, see `logFile`. Admin only.
257
+ * @param {object} [opts]
258
+ * @param {number} [opts.limit] - Max entries per kind (default 200, max 2000)
259
+ * @param {string} [opts.q] - Substring of any field, case-insensitive
260
+ * @param {string} [opts.level] - Minimum level for events, e.g. "warn"
261
+ * @param {string} [opts.status] - "2xx".."5xx", an exact code, or "failures"
262
+ * @param {string} [opts.user] - Only requests made by this account
263
+ * @param {string} [opts.method] - Only requests with this HTTP method
264
+ * @returns {Promise<LiveLog>}
265
+ */
266
+ logs: ({ limit, q, level, status, user, method } = {}) =>
267
+ client._request("GET", "/api/v1/admin/logs", {
268
+ queryParams: { limit, q, level, status, user, method },
269
+ }),
270
+ /**
271
+ * The tail of the configured log file, as text lines. The only place
272
+ * third-party library messages and anything from before the last restart
273
+ * can be read. Resolves with an `error` string instead of lines when no
274
+ * log file is configured or it does not exist yet. Admin only.
275
+ * @param {object} [opts]
276
+ * @param {number} [opts.lines] - How many lines (default 200, max 2000)
277
+ * @returns {Promise<{file: (string|null), lines: string[], error?: string}>}
278
+ */
279
+ logFile: ({ lines } = {}) =>
280
+ client._request("GET", "/api/v1/admin/logs/file", {
281
+ queryParams: { lines },
282
+ }),
283
+ /**
284
+ * Private user-data entries across every account, each carrying its
285
+ * `userId`. `/users/:userId/data` has always been owner-or-admin; this
286
+ * is the same reach across accounts at once. Transparently follows
287
+ * pagination cursors and returns the full flat array. Admin only.
288
+ *
289
+ * Narrow with `prefix` (the literal head of a key) and/or `pattern`, a
290
+ * GLOB over the whole key (`*` any run, `?` one character) — the way to
291
+ * ask for a key convention identified by a segment in the middle, e.g.
292
+ * `igt:assistant:*:meta:*`. Values come only with `includeValues`, and
293
+ * are recased like any other body (see `userData.put`).
294
+ * @param {object} [opts]
295
+ * @param {string} [opts.prefix] - Only keys starting with this
296
+ * @param {string} [opts.pattern] - Only keys matching this GLOB
297
+ * @param {boolean} [opts.includeValues] - Also return each entry's value
298
+ * @returns {Promise<AdminUserDataEntry[]>}
299
+ */
300
+ userData: ({ prefix, pattern, includeValues } = {}) =>
301
+ listAll(client, "/api/v1/admin/user-data", {
302
+ query: {
303
+ prefix,
304
+ pattern,
305
+ "include-values": includeValues,
306
+ },
307
+ }),
308
+ /**
309
+ * One page of private user-data entries across accounts, ordered by
310
+ * (user, key). Admin only.
311
+ * @param {object} [opts]
312
+ * @param {string} [opts.prefix] - Only keys starting with this
313
+ * @param {string} [opts.pattern] - Only keys matching this GLOB
314
+ * @param {boolean} [opts.includeValues] - Also return each entry's value
315
+ * @param {number} [opts.limit] - Page size (1..1000; server default 100)
316
+ * @param {string} [opts.cursor] - Opaque cursor from a previous page
317
+ * @returns {Promise<Page<AdminUserDataEntry>>}
318
+ */
319
+ userDataPage: ({ prefix, pattern, includeValues, limit, cursor } = {}) =>
320
+ listPage(client, "/api/v1/admin/user-data", {
321
+ limit,
322
+ cursor,
323
+ query: {
324
+ prefix,
325
+ pattern,
326
+ "include-values": includeValues,
327
+ },
328
+ }),
329
+ };
330
+ }
331
+
332
+ /** @param {import("./client.js").PlaidClient} client */
333
+ export function auditApi(client) {
334
+ return {
335
+ /**
336
+ * The audit log across every project, oldest first. Same fold, window
337
+ * and op-type filter as the per-project read, with the entity scope
338
+ * dropped. Admin only. Transparently follows pagination cursors and
339
+ * returns the full flat array.
340
+ * @param {object} [opts]
341
+ * @param {string} [opts.startTime] - Only operations at or after this instant
342
+ * @param {string} [opts.endTime] - Only operations at or before this instant
343
+ * @param {string[]|string} [opts.opTypes] - Only these op types (e.g.
344
+ * `['span-layer/create']`). An entry appears when one of its operations
345
+ * matches, carrying only the ones that did.
346
+ * @param {string[]|string} [opts.kinds] - Only the entries of operations
347
+ * of these kinds (e.g. `['review']`), each whole
348
+ * @returns {Promise<AuditEntry[]>}
349
+ */
350
+ list: ({ startTime, endTime, opTypes, kinds } = {}) =>
351
+ listAll(client, "/api/v1/audit", {
352
+ query: auditQuery({ startTime, endTime, opTypes, kinds }),
353
+ }),
354
+ /**
355
+ * Fetch a single page of the instance-wide audit log. Admin only.
356
+ * @param {object} [opts]
357
+ * @param {string} [opts.startTime] - Only entries at or after this instant
358
+ * @param {string} [opts.endTime] - Only entries at or before this instant
359
+ * @param {string[]|string} [opts.opTypes] - Only these op types
360
+ * @param {OperationKind[]|string} [opts.kinds] - Only the entries of operations of these kinds, each whole
361
+ * @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first, which
362
+ * is what a feed wants. A cursor belongs to the direction that made it.
363
+ * @param {number} [opts.limit] - Page size (1..1000; server default 100)
364
+ * @param {string} [opts.cursor] - Opaque cursor from a previous page
365
+ * @param {number} [opts.opsLimit] - Keep only each entry's oldest N
366
+ * operations in `ops` (1..1000). `opCount` on every entry says how
367
+ * many it has
368
+ * @param {string} [opts.entryId] - Read only this entry, not a page
369
+ * @returns {Promise<Page<AuditEntry>>}
370
+ */
371
+ listPage: ({ startTime, endTime, opTypes, kinds, order, limit, cursor, opsLimit, entryId } = {}) =>
372
+ listPage(client, "/api/v1/audit", {
373
+ limit,
374
+ cursor,
375
+ query: auditQuery({ startTime, endTime, opTypes, kinds, order, opsLimit, entryId }),
376
+ }),
377
+ /**
378
+ * Async-iterate the instance-wide audit log page by page. Admin only.
379
+ * @param {object} [opts]
380
+ * @param {string} [opts.startTime] - Only entries at or after this instant
381
+ * @param {string} [opts.endTime] - Only entries at or before this instant
382
+ * @param {string[]|string} [opts.opTypes] - Only these op types
383
+ * @param {OperationKind[]|string} [opts.kinds] - Only the entries of operations of these kinds, each whole
384
+ * @param {number} [opts.pageSize] - Per-request page size
385
+ * @returns {AsyncGenerator<AuditEntry[]>}
386
+ */
387
+ iterPages: ({ startTime, endTime, opTypes, kinds, pageSize } = {}) =>
388
+ iterPages(client, "/api/v1/audit", {
389
+ pageSize,
390
+ query: auditQuery({ startTime, endTime, opTypes, kinds }),
391
+ }),
392
+ /**
393
+ * Per-user activity counts. `changes` is the number of logical actions,
394
+ * folded the way the audit feed folds them, so one "Confirm word
395
+ * analysis" counts once however many rows it wrote; `operations` is the
396
+ * unfolded row count. Only users who did something appear — subtract
397
+ * from the roster you already hold to find the ones who did not.
398
+ *
399
+ * With `projectId`, scoped to that project and open to its maintainers.
400
+ * Without one, instance-wide and admin only.
401
+ * @param {object} [opts]
402
+ * @param {string} [opts.projectId] - Scope to one project
403
+ * @param {string} [opts.startTime] - Only count at or after this instant
404
+ * @param {string} [opts.endTime] - Only count at or before this instant
405
+ * @param {boolean} [opts.daily] - Also return `byDay`, a list of
406
+ * `{date, changes}` per user, oldest first, at the cost of a second
407
+ * grouped scan
408
+ * @returns {Promise<ActivityTallyRow[]>}
409
+ */
410
+ tally: async ({ projectId, startTime, endTime, daily } = {}) => {
411
+ const path = projectId
412
+ ? `/api/v1/projects/${projectId}/audit/tally`
413
+ : "/api/v1/audit/tally";
414
+ const result = await client._request("GET", path, {
415
+ queryParams: {
416
+ "start-time": startTime,
417
+ "end-time": endTime,
418
+ daily,
419
+ },
420
+ });
421
+ return result.entries;
422
+ },
423
+ };
424
+ }