@larc-iu/plaid-client 0.2.0-alpha.2 → 0.2.0-alpha.21

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 CHANGED
@@ -1,3 +1,12 @@
1
+ /**
2
+ * A config write's compare-and-set. With `expected` present (null or
3
+ * undefined for a key that was absent) the write is refused with a 409 when
4
+ * the stored value is no longer `expected`.
5
+ */
6
+ interface ConfigWriteOptions {
7
+ expected?: any;
8
+ }
9
+
1
10
  /** A single page of a cursor-paginated collection. */
2
11
  interface Page<T = any> {
3
12
  entries: T[];
@@ -15,7 +24,7 @@ interface ServiceParam {
15
24
  /** Key the value is sent under in the request payload. */
16
25
  key: string;
17
26
  label: string;
18
- type: 'string' | 'number' | 'boolean' | 'enum' | 'multiselect';
27
+ type: "string" | "number" | "boolean" | "enum" | "multiselect" | "field";
19
28
  description?: string;
20
29
  default?: any;
21
30
  required?: boolean;
@@ -25,9 +34,16 @@ interface ServiceParam {
25
34
  min?: number;
26
35
  max?: number;
27
36
  step?: number;
37
+ /** `number` only: render as a slider to drag rather than a box to type in. */
38
+ slider?: boolean;
28
39
  /** `string` only. */
29
40
  placeholder?: string;
30
41
  multiline?: boolean;
42
+ /**
43
+ * `field` only: the scope of the project's annotation fields the value names
44
+ * one of. A `field` value is a string everywhere else.
45
+ */
46
+ scope?: "Sentence" | "Word" | "Morpheme";
31
47
  }
32
48
 
33
49
  /** A service's standardized self-description (lives in `extras`). */
@@ -62,108 +78,340 @@ interface DiscoveredService {
62
78
 
63
79
  interface ServiceRegistration {
64
80
  stop(): void;
81
+ /** Still serving, including while retrying a channel through a server restart. */
65
82
  isRunning(): boolean;
83
+ /** Channel open right now, i.e. the server currently sees this service as online. */
84
+ isConnected(): boolean;
66
85
  serviceInfo: ServiceInfo & { extras: any };
67
86
  }
68
87
 
88
+ /** Connection-state transitions reported by a service registration. */
89
+ /** "stopped": the server refused the channel for a reason retrying cannot fix (a revoked token, no write access, no such project), which ends the registration. */
90
+ type ServiceStatusEvent = "registered" | "reconnected" | "disconnected" | "stopped";
91
+
69
92
  interface ResponseHelper {
70
- progress(percent: number, message: string): void;
93
+ requestId: string;
94
+ requesterId: string | null;
95
+ /** True once the requester has asked this request to stop. */
96
+ readonly cancelled: boolean;
97
+ /**
98
+ * Report progress — and a cancellation CHECKPOINT: throws
99
+ * `ServiceCancelled` once a stop has been asked for, which is what makes a
100
+ * service that already reports progress cancellable for free.
101
+ */
102
+ progress(percent: number, message: string, extra?: Record<string, any>): void;
103
+ /** Stop here if the requester has asked the request to stop. */
104
+ raiseIfCancelled(): void;
105
+ /** Run `fn` as a stretch that must finish once begun, usually the writes. */
106
+ critical<T>(fn: () => T | Promise<T>): Promise<T>;
71
107
  complete(data: any): void;
108
+ /** End the request because it was asked to stop; result carries `stopped: true`. */
109
+ stopped(data?: any): void;
72
110
  error(error: string | Error): void;
73
111
  }
74
112
 
113
+ /** Thrown inside a handler when the requester has asked it to stop. */
114
+ export class ServiceCancelled extends Error {
115
+ constructor(message?: string);
116
+ }
117
+
118
+ /** The key a run's stored record names its requester under. */
119
+ export const REQUESTED_BY: "requestedBy";
120
+
121
+ /** The person who asked for one service run. Both fields are null when nobody asked. */
122
+ export interface Requester {
123
+ id: string | null;
124
+ name: string | null;
125
+ /** `"<text>, requested by <name>"`, or `text` when nobody asked. */
126
+ label(text: string): string;
127
+ /** `{ id, name }` for a stored report, or null when nobody asked. */
128
+ record(): { id: string; name: string | null } | null;
129
+ /** A machine stamp's `provDetail` with `requestedBy: <id>` added. */
130
+ detail(detail?: Record<string, any>): Record<string, any>;
131
+ }
132
+
133
+ export function makeRequester(id?: string | null, name?: string | null): Requester;
134
+
135
+ /** The requester of one request, from the `requesterId` in a `serve` handler's data, named by display name. */
136
+ export function requesterOf(client: { users: { get(id: string): Promise<any> } }, data: any): Promise<Requester>;
137
+
138
+ /** The cancellation half of a responseHelper, on its own. */
139
+ export function createCancelScope(isCancelled: () => boolean): {
140
+ readonly cancelled: boolean;
141
+ raiseIfCancelled(): void;
142
+ critical<T>(fn: () => T | Promise<T>): Promise<T>;
143
+ };
144
+
75
145
  interface SSEConnection {
76
146
  close(): void;
77
147
  getStats(): any;
78
148
  readyState: number;
149
+ /**
150
+ * Once CLOSED, why: null when this side closed it. A refused stream's error
151
+ * carries the HTTP `status`, and one the server ended is named "StreamClosed".
152
+ */
153
+ error: (Error & { status?: number }) | null;
79
154
  }
80
155
 
81
156
  interface VocabLinksBundle {
82
- create(vocabItem: string, tokens: any[], metadata?: any, auditMessage?: string): Promise<any>;
157
+ create(
158
+ vocabItem: string,
159
+ tokens: any[],
160
+ metadata?: any,
161
+ auditMessage?: string,
162
+ opts?: { id?: string },
163
+ ): Promise<any>;
83
164
  bulkCreate(body: any[], auditMessage?: string): Promise<{ ids: string[] }>;
84
165
  bulkDelete(body: any[], auditMessage?: string): Promise<void>;
85
166
  setMetadata(id: string, body: any, auditMessage?: string): Promise<any>;
86
167
  deleteMetadata(id: string, auditMessage?: string): Promise<any>;
87
- patchMetadata(id: string, body: any, auditMessage?: string): Promise<any>;
88
- get(id: string, asOf?: string): Promise<any>;
168
+ patchMetadata(id: string, body: MetadataOp[], auditMessage?: string): Promise<any>;
169
+ get(id: string): Promise<any>;
89
170
  delete(id: string, auditMessage?: string): Promise<any>;
90
171
  }
91
172
 
173
+ /** What `vocabLayers.restoreItem` did, or with `dryRun` would do. */
174
+ interface VocabItemRestoreSummary {
175
+ /** The entry was gone and came back under its old id. */
176
+ inserted: boolean;
177
+ /** A living entry's form was set back. */
178
+ form: boolean;
179
+ /** A living entry's fields were set back. */
180
+ metadata: boolean;
181
+ /** 0 when nothing changes. */
182
+ total: number;
183
+ }
184
+
92
185
  interface VocabLayersBundle {
186
+ /** `asOf` reads the vocabulary as it was at that instant. */
93
187
  get(id: string, includeItems?: boolean, asOf?: string): Promise<any>;
188
+ /** One entry as it was at `asOf`, also when it has been deleted since. */
189
+ getItemAt(id: string, itemId: string, asOf: string): Promise<any>;
190
+ /** `itemId` keeps only the changes that wrote that one entry. */
191
+ audit(
192
+ id: string,
193
+ startTime?: string,
194
+ endTime?: string,
195
+ opTypes?: string[] | string,
196
+ itemId?: string,
197
+ kinds?: OperationKind[] | string,
198
+ ): Promise<any[]>;
199
+ auditPage(id: string, opts?: AuditPageOptions & { itemId?: string }): Promise<Page>;
200
+ /** Put one entry back as it was at `asOf`. Maintainers of the vocabulary only. */
201
+ restoreItem(
202
+ id: string,
203
+ itemId: string,
204
+ asOf: string,
205
+ options?: { dryRun?: boolean },
206
+ auditMessage?: string,
207
+ ): Promise<VocabItemRestoreSummary>;
94
208
  delete(id: string, auditMessage?: string): Promise<any>;
95
209
  update(id: string, name: string, auditMessage?: string): Promise<any>;
96
- setConfig(id: string, namespace: string, configKey: string, configValue: any, auditMessage?: string): Promise<any>;
97
- deleteConfig(id: string, namespace: string, configKey: string, auditMessage?: string): Promise<any>;
98
- list(asOf?: string): Promise<any[]>;
99
- listPage(opts?: { limit?: number; cursor?: string; asOf?: string }): Promise<Page>;
100
- iterPages(opts?: { pageSize?: number; asOf?: string }): AsyncGenerator<any[]>;
101
- create(name: string, auditMessage?: string): Promise<any>;
102
- addMaintainer(id: string, userId: string, auditMessage?: string): Promise<any>;
103
- removeMaintainer(id: string, userId: string, auditMessage?: string): Promise<any>;
210
+ setConfig(
211
+ id: string,
212
+ namespace: string,
213
+ configKey: string,
214
+ configValue: any,
215
+ auditMessage?: string,
216
+ options?: ConfigWriteOptions,
217
+ ): Promise<any>;
218
+ deleteConfig(
219
+ id: string,
220
+ namespace: string,
221
+ configKey: string,
222
+ auditMessage?: string,
223
+ options?: ConfigWriteOptions,
224
+ ): Promise<any>;
225
+ list(): Promise<any[]>;
226
+ listPage(opts?: { limit?: number; cursor?: string }): Promise<Page>;
227
+ iterPages(opts?: { pageSize?: number }): AsyncGenerator<any[]>;
228
+ create(name: string, auditMessage?: string, opts?: { id?: string }): Promise<any>;
229
+ addMaintainer(
230
+ id: string,
231
+ userId: string,
232
+ auditMessage?: string,
233
+ ): Promise<any>;
234
+ removeMaintainer(
235
+ id: string,
236
+ userId: string,
237
+ auditMessage?: string,
238
+ ): Promise<any>;
104
239
  }
105
240
 
106
241
  interface RelationsBundle {
107
- setMetadata(relationId: string, body: any, auditMessage?: string): Promise<any>;
242
+ setMetadata(
243
+ relationId: string,
244
+ body: any,
245
+ auditMessage?: string,
246
+ ): Promise<any>;
108
247
  deleteMetadata(relationId: string, auditMessage?: string): Promise<any>;
109
- patchMetadata(relationId: string, body: any, auditMessage?: string): Promise<any>;
110
- setTarget(relationId: string, spanId: string, auditMessage?: string): Promise<any>;
111
- get(relationId: string, asOf?: string): Promise<any>;
248
+ patchMetadata(
249
+ relationId: string,
250
+ body: MetadataOp[],
251
+ auditMessage?: string,
252
+ ): Promise<any>;
253
+ setTarget(
254
+ relationId: string,
255
+ spanId: string,
256
+ auditMessage?: string,
257
+ ): Promise<any>;
258
+ get(relationId: string): Promise<any>;
112
259
  delete(relationId: string, auditMessage?: string): Promise<any>;
113
260
  update(relationId: string, value: any, auditMessage?: string): Promise<any>;
114
- setSource(relationId: string, spanId: string, auditMessage?: string): Promise<any>;
115
- create(layerId: string, sourceId: string, targetId: string, value: any, metadata?: any, auditMessage?: string): Promise<any>;
261
+ setSource(
262
+ relationId: string,
263
+ spanId: string,
264
+ auditMessage?: string,
265
+ ): Promise<any>;
266
+ create(
267
+ layerId: string,
268
+ sourceId: string,
269
+ targetId: string,
270
+ value: any,
271
+ metadata?: any,
272
+ auditMessage?: string,
273
+ opts?: { id?: string },
274
+ ): Promise<any>;
116
275
  bulkCreate(body: any[], auditMessage?: string): Promise<{ ids: string[] }>;
117
276
  bulkDelete(body: any[], auditMessage?: string): Promise<void>;
277
+ bulkUpdate(body: any[], auditMessage?: string): Promise<{ count: number }>;
118
278
  }
119
279
 
120
280
  interface SpanLayersBundle {
121
- setConfig(spanLayerId: string, namespace: string, configKey: string, configValue: any, auditMessage?: string): Promise<any>;
122
- deleteConfig(spanLayerId: string, namespace: string, configKey: string, auditMessage?: string): Promise<any>;
123
- get(spanLayerId: string, asOf?: string): Promise<any>;
281
+ setConfig(
282
+ spanLayerId: string,
283
+ namespace: string,
284
+ configKey: string,
285
+ configValue: any,
286
+ auditMessage?: string,
287
+ options?: ConfigWriteOptions,
288
+ ): Promise<any>;
289
+ deleteConfig(
290
+ spanLayerId: string,
291
+ namespace: string,
292
+ configKey: string,
293
+ auditMessage?: string,
294
+ options?: ConfigWriteOptions,
295
+ ): Promise<any>;
296
+ get(spanLayerId: string): Promise<any>;
124
297
  delete(spanLayerId: string, auditMessage?: string): Promise<any>;
125
- update(spanLayerId: string, name: string, auditMessage?: string): Promise<any>;
126
- create(tokenLayerId: string, name: string, auditMessage?: string): Promise<any>;
127
- shift(spanLayerId: string, direction: string, auditMessage?: string): Promise<any>;
298
+ update(
299
+ spanLayerId: string,
300
+ name: string,
301
+ auditMessage?: string,
302
+ ): Promise<any>;
303
+ create(
304
+ tokenLayerId: string,
305
+ name: string,
306
+ auditMessage?: string,
307
+ opts?: { id?: string },
308
+ ): Promise<any>;
309
+ shift(
310
+ spanLayerId: string,
311
+ direction: string,
312
+ auditMessage?: string,
313
+ ): Promise<any>;
314
+ setConstraints(
315
+ layerId: string,
316
+ namespace: string,
317
+ constraints: LayerConstraint[],
318
+ auditMessage?: string,
319
+ options?: ConstraintWriteOptions,
320
+ ): Promise<{ constraints: Record<string, LayerConstraint[]> }>;
321
+ deleteConstraints(
322
+ layerId: string,
323
+ namespace: string,
324
+ auditMessage?: string,
325
+ options?: ConstraintWriteOptions,
326
+ ): Promise<any>;
327
+ checkConstraints(
328
+ layerId: string,
329
+ constraints: LayerConstraint[],
330
+ ): Promise<{ violations: ConstraintViolation[]; violationCount: number }>;
331
+ repairConstraints(
332
+ layerId: string,
333
+ constraints: LayerConstraint[],
334
+ auditMessage?: string,
335
+ options?: { document?: string },
336
+ ): Promise<{
337
+ repaired: { document: string; constraint: string; deleted: number; joined: number }[];
338
+ locked: { document: string; lockedBy: string }[];
339
+ violations: ConstraintViolation[];
340
+ violationCount: number;
341
+ }>;
128
342
  }
129
343
 
130
344
  interface SpansBundle {
131
345
  setTokens(spanId: string, tokens: any[], auditMessage?: string): Promise<any>;
132
- create(spanLayerId: string, tokens: any[], value: any, metadata?: any, auditMessage?: string): Promise<any>;
133
- get(spanId: string, asOf?: string): Promise<any>;
346
+ create(
347
+ spanLayerId: string,
348
+ tokens: any[],
349
+ value: any,
350
+ metadata?: any,
351
+ auditMessage?: string,
352
+ opts?: { id?: string },
353
+ ): Promise<any>;
354
+ get(spanId: string): Promise<any>;
134
355
  delete(spanId: string, auditMessage?: string): Promise<any>;
135
356
  update(spanId: string, value: any, auditMessage?: string): Promise<any>;
136
357
  bulkCreate(body: any[], auditMessage?: string): Promise<{ ids: string[] }>;
137
358
  bulkDelete(body: any[], auditMessage?: string): Promise<void>;
359
+ bulkUpdate(body: any[], auditMessage?: string): Promise<{ count: number }>;
138
360
  setMetadata(spanId: string, body: any, auditMessage?: string): Promise<any>;
139
361
  deleteMetadata(spanId: string, auditMessage?: string): Promise<any>;
140
- patchMetadata(spanId: string, body: any, auditMessage?: string): Promise<any>;
141
- }
142
-
143
- interface BatchBundle {
144
- submit(body: any[], auditMessage?: string): Promise<any>;
362
+ patchMetadata(spanId: string, body: MetadataOp[], auditMessage?: string): Promise<any>;
145
363
  }
146
364
 
147
365
  interface TextsBundle {
148
366
  setMetadata(textId: string, body: any, auditMessage?: string): Promise<any>;
149
367
  deleteMetadata(textId: string, auditMessage?: string): Promise<any>;
150
- patchMetadata(textId: string, body: any, auditMessage?: string): Promise<any>;
151
- create(textLayerId: string, documentId: string, body: string, metadata?: any, auditMessage?: string): Promise<any>;
152
- get(textId: string, asOf?: string): Promise<any>;
368
+ patchMetadata(textId: string, body: MetadataOp[], auditMessage?: string): Promise<any>;
369
+ create(
370
+ textLayerId: string,
371
+ documentId: string,
372
+ body: string,
373
+ metadata?: any,
374
+ auditMessage?: string,
375
+ opts?: { id?: string },
376
+ ): Promise<any>;
377
+ get(textId: string): Promise<any>;
153
378
  delete(textId: string, auditMessage?: string): Promise<any>;
154
- update(textId: string, body: any, auditMessage?: string): Promise<any>;
379
+ /** With `base` (a text's `digest`) the update applies only to that body. */
380
+ update(textId: string, body: any, auditMessage?: string, opts?: { base?: string; versioned?: boolean }): Promise<any>;
381
+ /** Edits made at the caret; with `base` they apply only to that body. Answers the text with `digest` and `reshape`. */
382
+ edit(textId: string, edits: TextEditOp[], auditMessage?: string, opts?: { base?: string; versioned?: boolean }): Promise<any>;
155
383
  }
156
384
 
157
385
  interface UsersBundle {
158
- list(asOf?: string): Promise<any[]>;
159
- listPage(opts?: { limit?: number; cursor?: string; asOf?: string }): Promise<Page>;
160
- iterPages(opts?: { pageSize?: number; asOf?: string }): AsyncGenerator<any[]>;
161
- create(username: string, password: string, isAdmin: boolean, auditMessage?: string): Promise<any>;
162
- audit(userId: string, startTime?: string, endTime?: string, asOf?: string): Promise<any[]>;
163
- get(id: string, asOf?: string): Promise<any>;
386
+ /** `q` narrows to users whose id or display name contains it (admin or maintainer). */
387
+ list(opts?: { q?: string }): Promise<any[]>;
388
+ listPage(opts?: { q?: string; limit?: number; cursor?: string }): Promise<Page>;
389
+ iterPages(opts?: { q?: string; pageSize?: number }): AsyncGenerator<any[]>;
390
+ create(
391
+ email: string,
392
+ password: string,
393
+ isAdmin: boolean,
394
+ displayName?: string,
395
+ auditMessage?: string,
396
+ ): Promise<any>;
397
+ audit(
398
+ userId: string,
399
+ startTime?: string,
400
+ endTime?: string,
401
+ opTypes?: string[] | string,
402
+ kinds?: OperationKind[] | string,
403
+ ): Promise<any[]>;
404
+ auditPage(userId: string, opts?: AuditPageOptions): Promise<Page>;
405
+ get(id: string): Promise<any>;
164
406
  delete(id: string, auditMessage?: string): Promise<any>;
165
407
  activate(id: string, auditMessage?: string): Promise<any>;
166
- update(id: string, password?: string, username?: string, isAdmin?: boolean, auditMessage?: string): Promise<any>;
408
+ update(
409
+ id: string,
410
+ password?: string,
411
+ displayName?: string,
412
+ isAdmin?: boolean,
413
+ auditMessage?: string,
414
+ ): Promise<any>;
167
415
  /** URL for a user's profile picture, usable as an <img> src. Null when avatarHash is explicitly null. */
168
416
  avatarUrl(id: string, avatarHash?: string | null): string | null;
169
417
  getAvatar(id: string): Promise<any>;
@@ -171,11 +419,247 @@ interface UsersBundle {
171
419
  deleteAvatar(id: string, auditMessage?: string): Promise<any>;
172
420
  }
173
421
 
422
+ interface UserDataEntry {
423
+ key: string;
424
+ updatedAt: string;
425
+ value?: any;
426
+ }
427
+
428
+ interface UserDataOptions {
429
+ prefix?: string;
430
+ pattern?: string;
431
+ includeValues?: boolean;
432
+ }
433
+
434
+ /** Private per-user key/value storage (owner or admin only; not audited). */
435
+ interface UserDataBundle {
436
+ list(
437
+ userId: string,
438
+ opts?: UserDataOptions & { pageSize?: number },
439
+ ): Promise<UserDataEntry[]>;
440
+ listPage(
441
+ userId: string,
442
+ opts?: UserDataOptions & { limit?: number; cursor?: string },
443
+ ): Promise<Page<UserDataEntry>>;
444
+ iterPages(
445
+ userId: string,
446
+ opts?: UserDataOptions & { pageSize?: number },
447
+ ): AsyncGenerator<UserDataEntry[]>;
448
+ get(userId: string, key: string): Promise<UserDataEntry & { value: any }>;
449
+ put(
450
+ userId: string,
451
+ key: string,
452
+ value: any,
453
+ ): Promise<{ key: string; updatedAt: string }>;
454
+ delete(userId: string, key: string): Promise<any>;
455
+ }
456
+
457
+ interface CommentFilters {
458
+ documentId?: string;
459
+ entityType?: CommentableType;
460
+ entityId?: string;
461
+ }
462
+
463
+ /** Entities that can carry a comment. */
464
+ type CommentableType =
465
+ "document" | "text" | "token" | "span" | "relation" | "vocab-item";
466
+
467
+ /**
468
+ * One entry in a project's annotation manual: a short Markdown document
469
+ * stating a convention the project follows. Read by the people on the
470
+ * project, and by the assistant before it proposes anything.
471
+ */
472
+ interface Guideline {
473
+ id: string;
474
+ projectId: string;
475
+ /** The handle the assistant asks for a guideline by. Not required to be unique. */
476
+ title: string;
477
+ /** The Markdown text. Present on a single read and on a list made with `includeBodies`. */
478
+ body?: string;
479
+ /** The body's length in characters. Present on a list made WITHOUT `includeBodies`. */
480
+ bodyChars?: number;
481
+ /** Whether the assistant is given this one in full on every turn. */
482
+ pinned: boolean;
483
+ createdAt: string;
484
+ updatedAt: string;
485
+ }
486
+
487
+ interface GuidelinesBundle {
488
+ create(
489
+ projectId: string,
490
+ title: string,
491
+ opts?: { body?: string; pinned?: boolean; id?: string },
492
+ auditMessage?: string,
493
+ ): Promise<{ id: string }>;
494
+ get(id: string): Promise<Guideline>;
495
+ update(
496
+ id: string,
497
+ changes?: {
498
+ title?: string;
499
+ body?: string;
500
+ pinned?: boolean;
501
+ /** Write only if this is still the stored updatedAt; 409 otherwise. */
502
+ expectedUpdatedAt?: string;
503
+ },
504
+ auditMessage?: string,
505
+ ): Promise<Guideline>;
506
+ delete(id: string, auditMessage?: string): Promise<any>;
507
+ list(
508
+ projectId: string,
509
+ opts?: { includeBodies?: boolean },
510
+ ): Promise<Guideline[]>;
511
+ listPage(
512
+ projectId: string,
513
+ opts?: { includeBodies?: boolean; limit?: number; cursor?: string },
514
+ ): Promise<Page<Guideline>>;
515
+ iterPages(
516
+ projectId: string,
517
+ opts?: { includeBodies?: boolean; pageSize?: number },
518
+ ): AsyncGenerator<Guideline[]>;
519
+ }
520
+
521
+ interface Comment {
522
+ id: string;
523
+ /** The owning project, or null for a comment on a vocabulary entry. */
524
+ projectId: string | null;
525
+ /** The owning document, or null for a comment on a vocabulary entry. */
526
+ documentId: string | null;
527
+ /** The owning vocab layer, set only for a comment on a vocabulary entry. */
528
+ vocabLayerId: string | null;
529
+ entityType: CommentableType;
530
+ entityId: string;
531
+ /**
532
+ * What the comment is about, in words, captured when it was posted. A
533
+ * comment outlives its anchor, and this is what is left to show once the
534
+ * anchor has been deleted.
535
+ */
536
+ anchorLabel: string | null;
537
+ authorId: string;
538
+ body: string;
539
+ createdAt: string;
540
+ updatedAt: string;
541
+ /** True once the body has been rewritten since it was posted. */
542
+ edited: boolean;
543
+ }
544
+
545
+ /** A research-telemetry event type (core manual, "Research telemetry"). */
546
+ type ClientEventType =
547
+ | "suggestion.shown"
548
+ | "suggestion.adopted"
549
+ | "suggestion.dismissed"
550
+ | "plan.opened";
551
+
552
+ interface ClientEventFields {
553
+ projectId: string;
554
+ documentId?: string;
555
+ targetId?: string;
556
+ /** Keyed by single lowercase words: value, source, field, written, conversation. */
557
+ data?: Record<string, any>;
558
+ }
559
+
560
+ interface ClientEvent {
561
+ id: number;
562
+ projectId: string;
563
+ documentId: string | null;
564
+ userId: string;
565
+ type: ClientEventType;
566
+ targetId: string | null;
567
+ data: Record<string, any> | null;
568
+ clientTs: string | null;
569
+ ts: string;
570
+ }
571
+
572
+ interface ClientEventFilters {
573
+ types?: ClientEventType | ClientEventType[] | string;
574
+ startTime?: string;
575
+ endTime?: string;
576
+ }
577
+
578
+ interface EventsBundle {
579
+ /** Buffer one event; never throws. False when not recorded (switch off, a repeat shown). */
580
+ record(type: ClientEventType, fields: ClientEventFields): boolean;
581
+ flush(): void;
582
+ setEnabled(projectId: string, on: boolean): void;
583
+ create(
584
+ projectId: string,
585
+ events: Array<{
586
+ type: ClientEventType;
587
+ documentId?: string;
588
+ targetId?: string;
589
+ data?: Record<string, any>;
590
+ clientTs?: string;
591
+ }>,
592
+ ): Promise<{ count: number }>;
593
+ list(projectId: string, filters?: ClientEventFilters): Promise<ClientEvent[]>;
594
+ listPage(
595
+ projectId: string,
596
+ opts?: ClientEventFilters & { limit?: number; cursor?: string },
597
+ ): Promise<Page<ClientEvent>>;
598
+ iterPages(
599
+ projectId: string,
600
+ opts?: ClientEventFilters & { pageSize?: number },
601
+ ): AsyncGenerator<ClientEvent[]>;
602
+ }
603
+
604
+ interface CommentsBundle {
605
+ create(
606
+ entityType: CommentableType,
607
+ entityId: string,
608
+ body: string,
609
+ opts?: { anchorLabel?: string; id?: string },
610
+ ): Promise<Comment>;
611
+ get(id: string): Promise<Comment>;
612
+ update(id: string, body: string): Promise<Comment>;
613
+ delete(id: string): Promise<any>;
614
+ list(projectId: string, filters?: CommentFilters): Promise<Comment[]>;
615
+ listPage(
616
+ projectId: string,
617
+ opts?: CommentFilters & { limit?: number; cursor?: string },
618
+ ): Promise<Page<Comment>>;
619
+ iterPages(
620
+ projectId: string,
621
+ opts?: CommentFilters & { pageSize?: number },
622
+ ): AsyncGenerator<Comment[]>;
623
+ /** `{entityId: count}`; keys are raw entity ids, never key-transformed. */
624
+ counts(
625
+ projectId: string,
626
+ filters?: CommentFilters,
627
+ ): Promise<Record<string, number>>;
628
+ /** Comments on a vocabulary's entries, oldest first. */
629
+ listInVocab(
630
+ vocabId: string,
631
+ filters?: { entityId?: string },
632
+ ): Promise<Comment[]>;
633
+ listInVocabPage(
634
+ vocabId: string,
635
+ opts?: { entityId?: string; limit?: number; cursor?: string },
636
+ ): Promise<Page<Comment>>;
637
+ iterInVocabPages(
638
+ vocabId: string,
639
+ opts?: { entityId?: string; pageSize?: number },
640
+ ): AsyncGenerator<Comment[]>;
641
+ /** `{entryId: count}` over a vocabulary; keys are raw entry ids. */
642
+ countsInVocab(
643
+ vocabId: string,
644
+ filters?: { entityId?: string },
645
+ ): Promise<Record<string, number>>;
646
+ }
647
+
174
648
  interface ApiTokensBundle {
175
649
  list(userId: string): Promise<any[]>;
176
- listPage(userId: string, opts?: { limit?: number; cursor?: string }): Promise<Page>;
177
- iterPages(userId: string, opts?: { pageSize?: number }): AsyncGenerator<any[]>;
178
- create(userId: string, name: string, auditMessage?: string): Promise<{ id: string; name: string; token: string }>;
650
+ listPage(
651
+ userId: string,
652
+ opts?: { limit?: number; cursor?: string },
653
+ ): Promise<Page>;
654
+ iterPages(
655
+ userId: string,
656
+ opts?: { pageSize?: number },
657
+ ): AsyncGenerator<any[]>;
658
+ create(
659
+ userId: string,
660
+ name: string,
661
+ auditMessage?: string,
662
+ ): Promise<{ id: string; name: string; token: string }>;
179
663
  revoke(userId: string, tokenId: string, auditMessage?: string): Promise<any>;
180
664
  }
181
665
 
@@ -183,7 +667,7 @@ interface Invite {
183
667
  id: string;
184
668
  /** "signup" | "password-reset" */
185
669
  kind: string;
186
- /** "active" | "used" | "expired" | "revoked" */
670
+ /** "active" | "used" | "expired" | "revoked" | "inactive" */
187
671
  status: string;
188
672
  createdBy: string;
189
673
  createdAt: string;
@@ -201,14 +685,14 @@ interface Invite {
201
685
  interface InvitePreview {
202
686
  /** "signup" | "password-reset" */
203
687
  kind: string;
204
- /** "active" | "used" | "expired" | "revoked" */
688
+ /** "active" | "used" | "expired" | "revoked" | "inactive" */
205
689
  status: string;
206
690
  expiresAt: string;
207
691
  grantAdmin: boolean;
208
692
  projectName?: string;
209
693
  projectRole?: string;
210
- /** Present only for a password reset: whose account the link belongs to. */
211
- username?: string;
694
+ /** Present only for a password reset: the email of the account it belongs to. */
695
+ email?: string;
212
696
  }
213
697
 
214
698
  interface CreateInviteOptions {
@@ -223,52 +707,536 @@ interface CreateInviteOptions {
223
707
  note?: string;
224
708
  }
225
709
 
710
+ /** Server limits, as reported by /api/v1/info. An unset limit is absent. */
711
+ interface ServerLimits {
712
+ mediaFileBytes?: number;
713
+ jsonBodyBytes?: number;
714
+ batchOperations?: number;
715
+ metadataDepth?: number;
716
+ metadataKeyCount?: number;
717
+ metadataStringLength?: number;
718
+ metadataTotalBytes?: number;
719
+ userDataValueBytes?: number;
720
+ guidelineTitleLength?: number;
721
+ guidelineBodyLength?: number;
722
+ /** The shortest password the server accepts on any path. */
723
+ passwordMinLength?: number;
724
+ /** How long a document lock is held before it lapses. */
725
+ lockExpirationMs?: number;
726
+ }
727
+
728
+ interface ServerHealth {
729
+ ok: boolean;
730
+ version: string;
731
+ uptimeMs: number;
732
+ audit: { dbSizeMb?: number; auditRows?: number; error?: string };
733
+ }
734
+
735
+ /** A document lock as the admin lock listing reports it. */
736
+ interface DocumentLockInfo {
737
+ documentId: string;
738
+ userId: string;
739
+ expiresAt: number;
740
+ }
741
+
742
+ interface RateLimitBucket {
743
+ ip: string;
744
+ userId?: string;
745
+ failures: number;
746
+ limit: number;
747
+ blocked: boolean;
748
+ }
749
+
750
+ interface RateLimitSnapshot {
751
+ windowMs: number;
752
+ logins: RateLimitBucket[];
753
+ ips: RateLimitBucket[];
754
+ invites: RateLimitBucket[];
755
+ }
756
+
757
+ interface BackupStatus {
758
+ enabled: boolean;
759
+ directory: string;
760
+ retention: number;
761
+ time: string;
762
+ backups: Array<{ name: string; bytes: number; modified: string }>;
763
+ ok?: boolean;
764
+ }
765
+
766
+ interface ServerReport {
767
+ version: string;
768
+ jvm: {
769
+ uptimeMs: number;
770
+ startedAt: string;
771
+ java: string;
772
+ heapUsed: number;
773
+ heapMax: number;
774
+ };
775
+ database: {
776
+ path: string | null;
777
+ bytes: number | null;
778
+ walBytes: number | null;
779
+ slowQueryThresholdMs?: number;
780
+ journalMode?: string;
781
+ maxPoolSize?: number;
782
+ tables: Record<string, number | null>;
783
+ };
784
+ media: {
785
+ directory: string;
786
+ files: number;
787
+ bytes: number;
788
+ orphans: number;
789
+ orphanBytes: number;
790
+ };
791
+ backup: BackupStatus;
792
+ settings: Record<string, any>;
793
+ }
794
+
795
+ /** The signed-in user's sign-ins. Signing in is `PlaidClient.login`. */
796
+ interface AuthBundle {
797
+ /**
798
+ * End every sign-in of this user on every device, this client's included.
799
+ * Named API tokens are not affected. Goes out at once even on a batch.
800
+ */
801
+ logoutEverywhere(): Promise<void>;
802
+ }
803
+
804
+ /** Server-level facts. `info` is fetched at most once per client. */
805
+ interface ServerBundle {
806
+ info(): Promise<{ limits: ServerLimits }>;
807
+ limits(): Promise<ServerLimits>;
808
+ health(): Promise<ServerHealth>;
809
+ }
810
+
811
+ /** Instance-wide operations. Every method requires a global admin. */
812
+ interface AdminBundle {
813
+ server(): Promise<ServerReport>;
814
+ backup(): Promise<BackupStatus>;
815
+ locks(): Promise<{ entries: DocumentLockInfo[] }>;
816
+ releaseLock(documentId: string): Promise<{ result: string }>;
817
+ rateLimits(): Promise<RateLimitSnapshot>;
818
+ clearRateLimits(opts?: {
819
+ ip?: string;
820
+ userId?: string;
821
+ }): Promise<{ result: string }>;
822
+ logs(opts?: {
823
+ limit?: number;
824
+ q?: string;
825
+ level?: string;
826
+ status?: string;
827
+ user?: string;
828
+ method?: string;
829
+ }): Promise<LiveLog>;
830
+ logFile(opts?: {
831
+ lines?: number;
832
+ }): Promise<{ file: string | null; lines: string[]; error?: string }>;
833
+ userData(opts?: AdminUserDataOptions): Promise<AdminUserDataEntry[]>;
834
+ userDataPage(
835
+ opts?: AdminUserDataOptions & { limit?: number; cursor?: string },
836
+ ): Promise<Page<AdminUserDataEntry>>;
837
+ }
838
+
839
+ /** One HTTP request as the log buffer holds it. */
840
+ interface LoggedRequest {
841
+ /** Epoch milliseconds. */
842
+ ts: number;
843
+ method: string;
844
+ path: string;
845
+ /** Query string with sensitive values replaced, or null. */
846
+ query: string | null;
847
+ /** Null when the handler threw before producing one. */
848
+ status: number | null;
849
+ ms: number;
850
+ /** Null for a request nobody was authenticated for. */
851
+ user: string | null;
852
+ ip: string | null;
853
+ /** Set when a named API token signed the request. */
854
+ token: string | null;
855
+ /** Exception class, when the handler threw. */
856
+ error: string | null;
857
+ }
858
+
859
+ /** One log event that was not a request. */
860
+ interface LoggedEvent {
861
+ ts: number;
862
+ level: string;
863
+ ns: string;
864
+ message: string;
865
+ trace?: string;
866
+ }
867
+
868
+ /**
869
+ * The live log. Requests and events are buffered separately, so a burst of
870
+ * requests cannot evict an error.
871
+ */
872
+ interface LiveLog {
873
+ requests: {
874
+ entries: LoggedRequest[];
875
+ /** How many passed the filters, which may exceed what `entries` holds. */
876
+ matched: number;
877
+ /** How many are buffered in total. */
878
+ held: number;
879
+ capacity: number;
880
+ stats: {
881
+ count: number;
882
+ failures: number;
883
+ serverErrors: number;
884
+ p50: number | null;
885
+ p95: number | null;
886
+ max: number | null;
887
+ perMinute: number | null;
888
+ oldest: number | null;
889
+ newest: number | null;
890
+ };
891
+ };
892
+ events: {
893
+ entries: LoggedEvent[];
894
+ matched: number;
895
+ held: number;
896
+ capacity: number;
897
+ /** Counts per level of what the search matched, before the level filter. */
898
+ byLevel: Record<string, number>;
899
+ };
900
+ /** Absolute path of the configured log file, or null when logging to stdout. */
901
+ file: string | null;
902
+ }
903
+
904
+ /** Narrowings for the cross-account private-data listing. */
905
+ interface AdminUserDataOptions {
906
+ /** Only keys starting with this literal head. */
907
+ prefix?: string;
908
+ /** Only keys matching this GLOB (`*` any run, `?` one character). */
909
+ pattern?: string;
910
+ includeValues?: boolean;
911
+ }
912
+
913
+ type AdminUserDataEntry = UserDataEntry & { userId: string };
914
+
915
+ /** Options for a single page of any audit log. */
916
+ interface AuditPageOptions {
917
+ startTime?: string;
918
+ endTime?: string;
919
+ opTypes?: string[] | string;
920
+ /** Only the entries of operations of these kinds, each whole. */
921
+ kinds?: OperationKind[] | string;
922
+ /** "desc" pages newest-first; a cursor belongs to the direction that made it. */
923
+ order?: "asc" | "desc";
924
+ limit?: number;
925
+ cursor?: string;
926
+ }
927
+
928
+ /** One user's activity over a scope and window. */
929
+ interface ActivityTallyRow {
930
+ user: { id: string; displayName?: string };
931
+ operations: number;
932
+ changes: number;
933
+ documents: number;
934
+ firstTs: string;
935
+ lastTs: string;
936
+ byDay?: Array<{ date: string; changes: number }>;
937
+ }
938
+
939
+ /** Instance-wide audit reads and the per-user aggregate. */
940
+ interface AuditBundle {
941
+ /** Admin only. */
942
+ list(opts?: {
943
+ startTime?: string;
944
+ endTime?: string;
945
+ opTypes?: string[] | string;
946
+ kinds?: OperationKind[] | string;
947
+ }): Promise<any[]>;
948
+ /** Admin only. */
949
+ listPage(opts?: AuditPageOptions): Promise<Page>;
950
+ /** Admin only. */
951
+ iterPages(opts?: {
952
+ startTime?: string;
953
+ endTime?: string;
954
+ opTypes?: string[] | string;
955
+ kinds?: OperationKind[] | string;
956
+ pageSize?: number;
957
+ }): AsyncGenerator<any[]>;
958
+ /** With projectId, open to that project's maintainers; without one, admin only. */
959
+ tally(opts?: {
960
+ projectId?: string;
961
+ startTime?: string;
962
+ endTime?: string;
963
+ daily?: boolean;
964
+ }): Promise<ActivityTallyRow[]>;
965
+ }
966
+
226
967
  interface InvitesBundle {
227
- list(opts?: { projectId?: string }): Promise<Invite[]>;
228
- listPage(opts?: { projectId?: string; limit?: number; cursor?: string }): Promise<Page>;
229
- iterPages(opts?: { projectId?: string; pageSize?: number }): AsyncGenerator<Invite[]>;
230
- /** The `code` is returned ONCE and is not recoverable afterward. */
231
- create(opts?: CreateInviteOptions, auditMessage?: string): Promise<Invite & { code: string }>;
968
+ list(opts?: { projectId?: string; all?: boolean }): Promise<Invite[]>;
969
+ listPage(opts?: {
970
+ projectId?: string;
971
+ all?: boolean;
972
+ limit?: number;
973
+ cursor?: string;
974
+ }): Promise<Page>;
975
+ iterPages(opts?: {
976
+ projectId?: string;
977
+ all?: boolean;
978
+ pageSize?: number;
979
+ }): AsyncGenerator<Invite[]>;
980
+ /**
981
+ * The `code` is returned ONCE and is not recoverable afterward. Needs a
982
+ * signed-in session: a named API token cannot mint any invite (403).
983
+ */
984
+ create(
985
+ opts?: CreateInviteOptions,
986
+ auditMessage?: string,
987
+ ): Promise<Invite & { code: string }>;
232
988
  revoke(id: string, auditMessage?: string): Promise<any>;
233
989
  }
234
990
 
235
991
  interface TokenLayersBundle {
236
- shift(tokenLayerId: string, direction: string, auditMessage?: string): Promise<any>;
237
- create(textLayerId: string, name: string, overlapMode?: string, parentTokenLayerId?: string, auditMessage?: string): Promise<any>;
238
- setConfig(tokenLayerId: string, namespace: string, configKey: string, configValue: any, auditMessage?: string): Promise<any>;
239
- deleteConfig(tokenLayerId: string, namespace: string, configKey: string, auditMessage?: string): Promise<any>;
240
- get(tokenLayerId: string, asOf?: string): Promise<any>;
992
+ shift(
993
+ tokenLayerId: string,
994
+ direction: string,
995
+ auditMessage?: string,
996
+ ): Promise<any>;
997
+ create(
998
+ textLayerId: string,
999
+ name: string,
1000
+ overlapMode?: string,
1001
+ parentTokenLayerId?: string,
1002
+ auditMessage?: string,
1003
+ opts?: { id?: string },
1004
+ ): Promise<any>;
1005
+ setConfig(
1006
+ tokenLayerId: string,
1007
+ namespace: string,
1008
+ configKey: string,
1009
+ configValue: any,
1010
+ auditMessage?: string,
1011
+ options?: ConfigWriteOptions,
1012
+ ): Promise<any>;
1013
+ deleteConfig(
1014
+ tokenLayerId: string,
1015
+ namespace: string,
1016
+ configKey: string,
1017
+ auditMessage?: string,
1018
+ options?: ConfigWriteOptions,
1019
+ ): Promise<any>;
1020
+ get(tokenLayerId: string): Promise<any>;
241
1021
  delete(tokenLayerId: string, auditMessage?: string): Promise<any>;
242
- update(tokenLayerId: string, name: string, auditMessage?: string): Promise<any>;
1022
+ update(
1023
+ tokenLayerId: string,
1024
+ name: string,
1025
+ auditMessage?: string,
1026
+ ): Promise<any>;
1027
+ setConstraints(
1028
+ layerId: string,
1029
+ namespace: string,
1030
+ constraints: LayerConstraint[],
1031
+ auditMessage?: string,
1032
+ options?: ConstraintWriteOptions,
1033
+ ): Promise<{ constraints: Record<string, LayerConstraint[]> }>;
1034
+ deleteConstraints(
1035
+ layerId: string,
1036
+ namespace: string,
1037
+ auditMessage?: string,
1038
+ options?: ConstraintWriteOptions,
1039
+ ): Promise<any>;
1040
+ checkConstraints(
1041
+ layerId: string,
1042
+ constraints: LayerConstraint[],
1043
+ ): Promise<{ violations: ConstraintViolation[]; violationCount: number }>;
1044
+ repairConstraints(
1045
+ layerId: string,
1046
+ constraints: LayerConstraint[],
1047
+ auditMessage?: string,
1048
+ options?: { document?: string },
1049
+ ): Promise<{
1050
+ repaired: { document: string; constraint: string; deleted: number; joined: number }[];
1051
+ locked: { document: string; lockedBy: string }[];
1052
+ violations: ConstraintViolation[];
1053
+ violationCount: number;
1054
+ }>;
1055
+ }
1056
+
1057
+ /** Handle passed to a `documents.locked()` block. */
1058
+ export declare class DocumentLock {
1059
+ readonly documentId: string;
1060
+ /** The holder id the acquire answered with. */
1061
+ readonly lockId: string | null;
1062
+ /** The DocumentLockLost if the lock lapsed, else null. */
1063
+ readonly lost: DocumentLockLost | null;
1064
+ /** Throw if the lock lapsed; call it between steps that have not written. */
1065
+ raiseIfLost(): void;
1066
+ }
1067
+
1068
+ /** The lock a `documents.locked()` block was holding is no longer held. */
1069
+ export declare class DocumentLockLost extends Error {
1070
+ readonly name: "DocumentLockLost";
1071
+ readonly documentId: string;
1072
+ readonly cause?: unknown;
243
1073
  }
244
1074
 
245
1075
  interface DocumentsBundle {
246
- checkLock(documentId: string, asOf?: string): Promise<any>;
247
- acquireLock(documentId: string, auditMessage?: string): Promise<any>;
248
- releaseLock(documentId: string, auditMessage?: string): Promise<any>;
249
- getMedia(documentId: string, asOf?: string): Promise<ArrayBuffer>;
250
- uploadMedia(documentId: string, file: File, auditMessage?: string): Promise<any>;
1076
+ checkLock(documentId: string): Promise<any>;
1077
+ /**
1078
+ * Take the lock as a new holder. The answer's `lockId` names it:
1079
+ * `newLockId` when the client minted one, which makes a retried acquire
1080
+ * idempotent and a lost answer releasable.
1081
+ */
1082
+ acquireLock(
1083
+ documentId: string,
1084
+ auditMessage?: string,
1085
+ newLockId?: string,
1086
+ ): Promise<any>;
1087
+ /** Renew `lockId`'s lock while it is live. 423 once it has lapsed. */
1088
+ renewLock(
1089
+ documentId: string,
1090
+ lockId: string,
1091
+ auditMessage?: string,
1092
+ ): Promise<any>;
1093
+ releaseLock(
1094
+ documentId: string,
1095
+ lockId: string,
1096
+ auditMessage?: string,
1097
+ ): Promise<any>;
1098
+ /**
1099
+ * Hold the document's lock for the length of `fn`, renewing it while `fn`
1100
+ * runs and releasing it on the way out. Rejects with a 423 if it is already
1101
+ * held, and with DocumentLockLost if a renewal fails.
1102
+ */
1103
+ locked<T>(
1104
+ documentId: string,
1105
+ fn: (lock: DocumentLock) => T | Promise<T>,
1106
+ options?: { keepAlive?: boolean },
1107
+ ): Promise<T>;
1108
+ getMedia(documentId: string): Promise<ArrayBuffer>;
1109
+ uploadMedia(
1110
+ documentId: string,
1111
+ file: File,
1112
+ auditMessage?: string,
1113
+ options?: {
1114
+ /** Called with the bytes sent so far as the file goes up (`total` is null when unknown). */
1115
+ onProgress?: (progress: { loaded: number; total: number | null }) => void;
1116
+ },
1117
+ ): Promise<any>;
251
1118
  deleteMedia(documentId: string, auditMessage?: string): Promise<any>;
252
- setMetadata(documentId: string, body: any, auditMessage?: string): Promise<any>;
1119
+ setMetadata(
1120
+ documentId: string,
1121
+ body: any,
1122
+ auditMessage?: string,
1123
+ ): Promise<any>;
253
1124
  deleteMetadata(documentId: string, auditMessage?: string): Promise<any>;
254
- patchMetadata(documentId: string, body: any, auditMessage?: string): Promise<any>;
255
- audit(documentId: string, startTime?: string, endTime?: string, asOf?: string): Promise<any[]>;
256
- get(documentId: string, includeBody?: boolean, asOf?: string): Promise<any>;
1125
+ patchMetadata(
1126
+ documentId: string,
1127
+ body: MetadataOp[],
1128
+ auditMessage?: string,
1129
+ ): Promise<any>;
1130
+ audit(
1131
+ documentId: string,
1132
+ startTime?: string,
1133
+ endTime?: string,
1134
+ opTypes?: string[] | string,
1135
+ kinds?: OperationKind[] | string,
1136
+ ): Promise<any[]>;
1137
+ auditPage(documentId: string, opts?: AuditPageOptions): Promise<Page>;
1138
+ /**
1139
+ * `asOf` reads the document as it was at that instant. `layers` narrows a
1140
+ * body read to the named layers (ids of any kind), and needs `includeBody`.
1141
+ */
1142
+ get(
1143
+ documentId: string,
1144
+ includeBody?: boolean,
1145
+ asOf?: string,
1146
+ layers?: string[] | string,
1147
+ ): Promise<any>;
257
1148
  delete(documentId: string, auditMessage?: string): Promise<any>;
258
1149
  update(documentId: string, name: string, auditMessage?: string): Promise<any>;
259
- create(projectId: string, name: string, metadata?: any, auditMessage?: string): Promise<any>;
1150
+ create(
1151
+ projectId: string,
1152
+ name: string,
1153
+ metadata?: any,
1154
+ auditMessage?: string,
1155
+ opts?: { id?: string },
1156
+ ): Promise<any>;
1157
+ /** Copy a document and everything in it into a new document of the same project. */
1158
+ copy(
1159
+ documentId: string,
1160
+ name: string,
1161
+ options?: { includeMedia?: boolean; id?: string },
1162
+ auditMessage?: string,
1163
+ ): Promise<{ id: string; mediaError?: string }>;
1164
+ restore(
1165
+ documentId: string,
1166
+ asOf: string,
1167
+ options?: { dryRun?: boolean },
1168
+ auditMessage?: string,
1169
+ ): Promise<any>;
260
1170
  }
261
1171
 
262
1172
  interface MessagesBundle {
263
- sendMessage(projectId: string, data: any, auditMessage?: string): Promise<any>;
264
- listen(projectId: string, onEvent: (eventType: string, data: any) => void | boolean, path?: string): SSEConnection;
1173
+ /** Broadcast to the project's listeners. Never saved and never part of an operation. */
1174
+ sendMessage(projectId: string, data: any): Promise<any>;
1175
+ listen(
1176
+ projectId: string,
1177
+ onEvent: (eventType: string, data: any) => void | boolean,
1178
+ path?: string,
1179
+ ): SSEConnection;
265
1180
  /** Discover the services seen on a project: online ones plus previously-seen offline ones (check `online`). */
266
1181
  discoverServices(projectId: string): Promise<DiscoveredService[]>;
267
1182
  /** Forget a previously-seen (offline) service. Maintainer-only; 409 if currently connected. */
268
1183
  discardService(projectId: string, serviceId: string): Promise<void>;
269
- serve(projectId: string, serviceInfo: ServiceInfo, onServiceRequest: (data: any, responseHelper: ResponseHelper) => void, extras?: any): ServiceRegistration;
270
- /** Submit work to a service; streams progress to `onProgress`, resolves with the result. */
271
- requestService(projectId: string, serviceId: string, data: any, timeout?: number, onProgress?: (progress: any) => void): Promise<any>;
1184
+ /**
1185
+ * Register as a service and handle work requests. The registration reopens
1186
+ * its channel whenever it drops, so a server restart needs no service restart.
1187
+ */
1188
+ serve(
1189
+ projectId: string,
1190
+ serviceInfo: ServiceInfo,
1191
+ onServiceRequest: (data: any, responseHelper: ResponseHelper) => void,
1192
+ extras?: any,
1193
+ onStatus?: (
1194
+ event: ServiceStatusEvent,
1195
+ projectId: string,
1196
+ detail?: string,
1197
+ ) => void,
1198
+ ): ServiceRegistration;
1199
+ /**
1200
+ * Submit work to a service; streams progress to `onProgress`, resolves with
1201
+ * the result. Aborting `signal` stops waiting and rejects with an AbortError;
1202
+ * the service is not told and finishes its work regardless.
1203
+ */
1204
+ requestService(
1205
+ projectId: string,
1206
+ serviceId: string,
1207
+ data: any,
1208
+ timeout?: number,
1209
+ onProgress?: (progress: any) => void,
1210
+ signal?: AbortSignal,
1211
+ opts?: {
1212
+ /** A request id you mint, so you know it before submitting; resubmitting one rejoins that request. */
1213
+ requestId?: string;
1214
+ /** Called with the request id as soon as the server has taken the request. */
1215
+ onAccepted?: (requestId: string) => void;
1216
+ /** Other projects the request is about. A delegating service's token reaches `projectId` and those of these the requester can read, nothing else. */
1217
+ projectIds?: string[];
1218
+ /** Carry no open operation: the service's writes are a group of their own, not part of whatever operation this client has open. */
1219
+ noOperation?: boolean;
1220
+ },
1221
+ ): Promise<any>;
1222
+ /**
1223
+ * Rejoin a request made earlier: the latest progress is replayed, then the
1224
+ * result, or at once if it already finished. Only its submitter (or an
1225
+ * admin). Rejects with a 404 when the request is unknown or expired.
1226
+ */
1227
+ attachServiceRequest(
1228
+ projectId: string,
1229
+ requestId: string,
1230
+ timeout?: number,
1231
+ onProgress?: (progress: any) => void,
1232
+ signal?: AbortSignal,
1233
+ ): Promise<any>;
1234
+ /**
1235
+ * Ask the service to stop a request made earlier. The request still ends
1236
+ * with whatever the service then reports. 404 if unknown or expired, 409
1237
+ * once finished.
1238
+ */
1239
+ cancelServiceRequest(projectId: string, requestId: string): Promise<void>;
272
1240
  }
273
1241
 
274
1242
  interface ProjectsBundle {
@@ -276,75 +1244,257 @@ interface ProjectsBundle {
276
1244
  removeWriter(id: string, userId: string, auditMessage?: string): Promise<any>;
277
1245
  addReader(id: string, userId: string, auditMessage?: string): Promise<any>;
278
1246
  removeReader(id: string, userId: string, auditMessage?: string): Promise<any>;
279
- setConfig(id: string, namespace: string, configKey: string, configValue: any, auditMessage?: string): Promise<any>;
280
- deleteConfig(id: string, namespace: string, configKey: string, auditMessage?: string): Promise<any>;
281
- addMaintainer(id: string, userId: string, auditMessage?: string): Promise<any>;
282
- removeMaintainer(id: string, userId: string, auditMessage?: string): Promise<any>;
283
- audit(projectId: string, startTime?: string, endTime?: string, asOf?: string): Promise<any[]>;
1247
+ setConfig(
1248
+ id: string,
1249
+ namespace: string,
1250
+ configKey: string,
1251
+ configValue: any,
1252
+ auditMessage?: string,
1253
+ options?: ConfigWriteOptions,
1254
+ ): Promise<any>;
1255
+ deleteConfig(
1256
+ id: string,
1257
+ namespace: string,
1258
+ configKey: string,
1259
+ auditMessage?: string,
1260
+ options?: ConfigWriteOptions,
1261
+ ): Promise<any>;
1262
+ addMaintainer(
1263
+ id: string,
1264
+ userId: string,
1265
+ auditMessage?: string,
1266
+ ): Promise<any>;
1267
+ removeMaintainer(
1268
+ id: string,
1269
+ userId: string,
1270
+ auditMessage?: string,
1271
+ ): Promise<any>;
1272
+ audit(
1273
+ projectId: string,
1274
+ startTime?: string,
1275
+ endTime?: string,
1276
+ opTypes?: string[] | string,
1277
+ kinds?: OperationKind[] | string,
1278
+ ): Promise<any[]>;
1279
+ auditPage(projectId: string, opts?: AuditPageOptions): Promise<Page>;
1280
+ /**
1281
+ * When the calling user last wrote to each document of the project, as a
1282
+ * `{documentId: timestamp}` map; documents they never wrote to are absent.
1283
+ * The keys are document ids, so the response is not key-transformed.
1284
+ */
1285
+ myLastEdits(projectId: string): Promise<Record<string, string>>;
284
1286
  linkVocab(id: string, vocabId: string, auditMessage?: string): Promise<any>;
285
1287
  unlinkVocab(id: string, vocabId: string, auditMessage?: string): Promise<any>;
286
- get(id: string, asOf?: string): Promise<any>;
1288
+ get(id: string): Promise<any>;
287
1289
  listDocuments(id: string): Promise<any[]>;
288
- listDocumentsPage(id: string, opts?: { limit?: number; cursor?: string }): Promise<Page>;
289
- iterDocuments(id: string, opts?: { pageSize?: number }): AsyncGenerator<any[]>;
290
- delete(id: string, auditMessage?: string): Promise<any>;
1290
+ listDocumentsPage(
1291
+ id: string,
1292
+ opts?: { limit?: number; cursor?: string },
1293
+ ): Promise<Page>;
1294
+ iterDocuments(
1295
+ id: string,
1296
+ opts?: { pageSize?: number },
1297
+ ): AsyncGenerator<any[]>;
291
1298
  update(id: string, name: string, auditMessage?: string): Promise<any>;
292
- list(asOf?: string): Promise<any[]>;
293
- listPage(opts?: { limit?: number; cursor?: string; asOf?: string }): Promise<Page>;
294
- iterPages(opts?: { pageSize?: number; asOf?: string }): AsyncGenerator<any[]>;
295
- create(name: string, auditMessage?: string): Promise<any>;
1299
+ /** The project is gone when this returns, and what it holds is removed on the server afterwards. The timeout defaults to the client's. */
1300
+ delete(
1301
+ id: string,
1302
+ auditMessage?: string,
1303
+ options?: { timeout?: number | null },
1304
+ ): Promise<any>;
1305
+ list(): Promise<any[]>;
1306
+ listPage(opts?: { limit?: number; cursor?: string }): Promise<Page>;
1307
+ iterPages(opts?: { pageSize?: number }): AsyncGenerator<any[]>;
1308
+ create(name: string, auditMessage?: string, opts?: { id?: string }): Promise<any>;
296
1309
  }
297
1310
 
298
1311
  interface TextLayersBundle {
299
- setConfig(textLayerId: string, namespace: string, configKey: string, configValue: any, auditMessage?: string): Promise<any>;
300
- deleteConfig(textLayerId: string, namespace: string, configKey: string, auditMessage?: string): Promise<any>;
301
- get(textLayerId: string, asOf?: string): Promise<any>;
1312
+ setConfig(
1313
+ textLayerId: string,
1314
+ namespace: string,
1315
+ configKey: string,
1316
+ configValue: any,
1317
+ auditMessage?: string,
1318
+ options?: ConfigWriteOptions,
1319
+ ): Promise<any>;
1320
+ deleteConfig(
1321
+ textLayerId: string,
1322
+ namespace: string,
1323
+ configKey: string,
1324
+ auditMessage?: string,
1325
+ options?: ConfigWriteOptions,
1326
+ ): Promise<any>;
1327
+ get(textLayerId: string): Promise<any>;
302
1328
  delete(textLayerId: string, auditMessage?: string): Promise<any>;
303
- update(textLayerId: string, name: string, auditMessage?: string): Promise<any>;
304
- shift(textLayerId: string, direction: string, auditMessage?: string): Promise<any>;
305
- create(projectId: string, name: string, auditMessage?: string): Promise<any>;
1329
+ update(
1330
+ textLayerId: string,
1331
+ name: string,
1332
+ auditMessage?: string,
1333
+ ): Promise<any>;
1334
+ shift(
1335
+ textLayerId: string,
1336
+ direction: string,
1337
+ auditMessage?: string,
1338
+ ): Promise<any>;
1339
+ create(projectId: string, name: string, auditMessage?: string, opts?: { id?: string }): Promise<any>;
306
1340
  }
307
1341
 
308
1342
  interface VocabItemsBundle {
309
1343
  setMetadata(id: string, body: any, auditMessage?: string): Promise<any>;
310
1344
  deleteMetadata(id: string, auditMessage?: string): Promise<any>;
311
- patchMetadata(id: string, body: any, auditMessage?: string): Promise<any>;
312
- create(vocabLayerId: string, form: string, metadata?: any, auditMessage?: string): Promise<any>;
1345
+ patchMetadata(id: string, body: MetadataOp[], auditMessage?: string): Promise<any>;
1346
+ create(
1347
+ vocabLayerId: string,
1348
+ form: string,
1349
+ metadata?: any,
1350
+ auditMessage?: string,
1351
+ opts?: { id?: string },
1352
+ ): Promise<any>;
313
1353
  bulkCreate(body: any[], auditMessage?: string): Promise<{ ids: string[] }>;
314
1354
  bulkDelete(body: any[], auditMessage?: string): Promise<void>;
315
- get(id: string, asOf?: string): Promise<any>;
316
- delete(id: string, auditMessage?: string): Promise<any>;
1355
+ bulkUpdate(body: any[], auditMessage?: string): Promise<{ count: number }>;
1356
+ get(id: string): Promise<any>;
1357
+ delete(
1358
+ id: string,
1359
+ auditMessage?: string,
1360
+ options?: { expectedLinkCount?: number },
1361
+ ): Promise<any>;
1362
+ merge(
1363
+ survivorId: string,
1364
+ loserIds: string[],
1365
+ auditMessage?: string,
1366
+ ): Promise<{ moved: number; duplicates: number; removed: string[] }>;
317
1367
  update(id: string, form: string, auditMessage?: string): Promise<any>;
318
1368
  }
319
1369
 
320
1370
  interface RelationLayersBundle {
321
- shift(relationLayerId: string, direction: string, auditMessage?: string): Promise<any>;
322
- create(spanLayerId: string, name: string, auditMessage?: string): Promise<any>;
323
- setConfig(relationLayerId: string, namespace: string, configKey: string, configValue: any, auditMessage?: string): Promise<any>;
324
- deleteConfig(relationLayerId: string, namespace: string, configKey: string, auditMessage?: string): Promise<any>;
325
- get(relationLayerId: string, asOf?: string): Promise<any>;
1371
+ shift(
1372
+ relationLayerId: string,
1373
+ direction: string,
1374
+ auditMessage?: string,
1375
+ ): Promise<any>;
1376
+ create(
1377
+ spanLayerId: string,
1378
+ name: string,
1379
+ auditMessage?: string,
1380
+ opts?: { id?: string },
1381
+ ): Promise<any>;
1382
+ setConfig(
1383
+ relationLayerId: string,
1384
+ namespace: string,
1385
+ configKey: string,
1386
+ configValue: any,
1387
+ auditMessage?: string,
1388
+ options?: ConfigWriteOptions,
1389
+ ): Promise<any>;
1390
+ deleteConfig(
1391
+ relationLayerId: string,
1392
+ namespace: string,
1393
+ configKey: string,
1394
+ auditMessage?: string,
1395
+ options?: ConfigWriteOptions,
1396
+ ): Promise<any>;
1397
+ get(relationLayerId: string): Promise<any>;
326
1398
  delete(relationLayerId: string, auditMessage?: string): Promise<any>;
327
- update(relationLayerId: string, name: string, auditMessage?: string): Promise<any>;
1399
+ update(
1400
+ relationLayerId: string,
1401
+ name: string,
1402
+ auditMessage?: string,
1403
+ ): Promise<any>;
1404
+ setConstraints(
1405
+ layerId: string,
1406
+ namespace: string,
1407
+ constraints: LayerConstraint[],
1408
+ auditMessage?: string,
1409
+ options?: ConstraintWriteOptions,
1410
+ ): Promise<{ constraints: Record<string, LayerConstraint[]> }>;
1411
+ deleteConstraints(
1412
+ layerId: string,
1413
+ namespace: string,
1414
+ auditMessage?: string,
1415
+ options?: ConstraintWriteOptions,
1416
+ ): Promise<any>;
1417
+ checkConstraints(
1418
+ layerId: string,
1419
+ constraints: LayerConstraint[],
1420
+ ): Promise<{ violations: ConstraintViolation[]; violationCount: number }>;
1421
+ repairConstraints(
1422
+ layerId: string,
1423
+ constraints: LayerConstraint[],
1424
+ auditMessage?: string,
1425
+ options?: { document?: string },
1426
+ ): Promise<{
1427
+ repaired: { document: string; constraint: string; deleted: number; joined: number }[];
1428
+ locked: { document: string; lockedBy: string }[];
1429
+ violations: ConstraintViolation[];
1430
+ violationCount: number;
1431
+ }>;
328
1432
  }
329
1433
 
330
1434
  interface TokensBundle {
331
- create(tokenLayerId: string, text: string, begin: number, end: number, precedence?: number | null, metadata?: any, auditMessage?: string): Promise<any>;
332
- get(tokenId: string, asOf?: string): Promise<any>;
1435
+ create(
1436
+ tokenLayerId: string,
1437
+ text: string,
1438
+ begin: number,
1439
+ end: number,
1440
+ precedence?: number | null,
1441
+ metadata?: any,
1442
+ auditMessage?: string,
1443
+ opts?: { id?: string },
1444
+ ): Promise<any>;
1445
+ get(tokenId: string): Promise<any>;
333
1446
  delete(tokenId: string, auditMessage?: string): Promise<any>;
334
- update(tokenId: string, begin?: number, end?: number, precedence?: number | null, auditMessage?: string): Promise<any>;
1447
+ update(
1448
+ tokenId: string,
1449
+ begin?: number,
1450
+ end?: number,
1451
+ precedence?: number | null,
1452
+ auditMessage?: string,
1453
+ ): Promise<any>;
335
1454
  bulkCreate(body: any[], auditMessage?: string): Promise<{ ids: string[] }>;
336
1455
  bulkDelete(body: any[], auditMessage?: string): Promise<void>;
337
- split(tokenId: string, position: number, auditMessage?: string): Promise<any>;
338
- merge(tokenId: string, otherTokenId: string, auditMessage?: string): Promise<any>;
339
- shift(tokenId: string, begin?: number, end?: number, auditMessage?: string): Promise<any>;
1456
+ bulkUpdate(body: any[], auditMessage?: string): Promise<{ count: number }>;
1457
+ split(
1458
+ tokenId: string,
1459
+ position: number,
1460
+ auditMessage?: string,
1461
+ options?: { id?: string },
1462
+ ): Promise<any>;
1463
+ merge(
1464
+ tokenId: string,
1465
+ otherTokenId: string,
1466
+ auditMessage?: string,
1467
+ ): Promise<any>;
1468
+ shift(
1469
+ tokenId: string,
1470
+ begin?: number,
1471
+ end?: number,
1472
+ auditMessage?: string,
1473
+ ): Promise<any>;
340
1474
  setMetadata(tokenId: string, body: any, auditMessage?: string): Promise<any>;
341
1475
  deleteMetadata(tokenId: string, auditMessage?: string): Promise<any>;
342
- patchMetadata(tokenId: string, body: any, auditMessage?: string): Promise<any>;
1476
+ patchMetadata(
1477
+ tokenId: string,
1478
+ body: MetadataOp[],
1479
+ auditMessage?: string,
1480
+ ): Promise<any>;
343
1481
  }
344
1482
 
345
1483
  interface PlaidClientOptions {
346
1484
  /** Per-request timeout in ms (default 30000; 0 or null disables it). */
347
1485
  timeout?: number | null;
1486
+ /**
1487
+ * Timeout for batch submissions in ms (default 180000; 0 or null disables it).
1488
+ * Batches get their own, longer budget: aborting one does NOT stop the
1489
+ * server, which keeps running the transaction and holding the single SQLite
1490
+ * write lock. Defaults to `timeout` when that is given and this is not.
1491
+ */
1492
+ batchTimeout?: number | null;
1493
+ /**
1494
+ * Delays in ms before sending a keyed write again when its answer was lost
1495
+ * (no response, 502 or 504). Default [1000, 3000, 9000].
1496
+ */
1497
+ retryDelaysMs?: number[];
348
1498
  /**
349
1499
  * Fired once when a request returns HTTP 401 (missing/expired/invalid token).
350
1500
  * Use it to discard the stored token and route back to login. 403 (forbidden)
@@ -353,36 +1503,137 @@ interface PlaidClientOptions {
353
1503
  onAuthError?: ((error: Error) => void) | null;
354
1504
  }
355
1505
 
1506
+ /** What kind of operation a logical-operation group is (`?group-kind=`). */
1507
+ export type OperationKind =
1508
+ | 'assistant-plan'
1509
+ | 'service-run'
1510
+ | 'import'
1511
+ | 'bulk-edit'
1512
+ | 'guess-adoption'
1513
+ | 'repair'
1514
+ | 'review';
1515
+
356
1516
  export interface OperationGroupsBundle {
357
1517
  get(id: string): Promise<any>;
358
1518
  update(id: string, message: string | null): Promise<any>;
359
1519
  }
360
1520
 
1521
+ /**
1522
+ * A batch: the client's bundles, on which every write of project data queues
1523
+ * until `submit()`. See `PlaidClient.batch()`.
1524
+ */
1525
+ export interface PlaidBatch extends PlaidClient {
1526
+ /** The client this batch was opened on. */
1527
+ readonly client: PlaidClient;
1528
+ /** The queued operations, in order. */
1529
+ readonly operations: Array<{ path: string; method: string; body?: any }>;
1530
+ /**
1531
+ * Send the queued operations and resolve to one result per operation, in
1532
+ * order. Up to MAX_BATCH_OPS operations go as one atomic request. A larger
1533
+ * batch goes as consecutive requests, each atomic on its own, so a failure
1534
+ * in a later one leaves the earlier ones committed. The error then carries
1535
+ * `committed`, how many operations were saved (0 when none were), and
1536
+ * `committedResults`, their results in queue order.
1537
+ */
1538
+ submit(): Promise<any[]>;
1539
+ /** Drop the queued operations without sending them. */
1540
+ abort(): void;
1541
+ /**
1542
+ * A stand-in for the id a queued operation will create, to put in a later
1543
+ * operation's body on this batch, so a create and the write that uses it go
1544
+ * in one transaction: op n's `id`, or with `index` the k-th of the `ids` a
1545
+ * bulk create answers. `opIndex` counts from 0, or from the end when
1546
+ * negative (-1, the default, is the op queued last). It goes only in the
1547
+ * body of a later write on this batch, at any depth. Anywhere else (a path,
1548
+ * another batch, a call made on the client) the client refuses it.
1549
+ */
1550
+ ref(opIndex?: number, index?: number): BatchRef;
1551
+ }
1552
+
1553
+ /**
1554
+ * See `PlaidBatch.ref`. A marker object, not data: the client takes it out of
1555
+ * the body when its write is queued and sends where it was beside the body.
1556
+ */
1557
+ export interface BatchRef {
1558
+ /** The op, counted from 0 in the batch, whose id this stands for. */
1559
+ readonly $ref: number;
1560
+ /** For a bulk create, which of its ids. */
1561
+ readonly index?: number;
1562
+ }
1563
+
361
1564
  export declare class PlaidClient {
362
1565
  constructor(baseUrl: string, token: string, options?: PlaidClientOptions);
363
- static login(baseUrl: string, userId: string, password: string, options?: PlaidClientOptions): Promise<PlaidClient>;
1566
+ /** Liveness, version and database size, with no authentication. */
1567
+ static health(
1568
+ baseUrl: string,
1569
+ options?: { timeout?: number | null },
1570
+ ): Promise<ServerHealth>;
1571
+ /** The limits this server enforces, with no authentication. */
1572
+ static info(
1573
+ baseUrl: string,
1574
+ options?: { timeout?: number | null },
1575
+ ): Promise<{ limits: ServerLimits }>;
1576
+ static login(
1577
+ baseUrl: string,
1578
+ userId: string,
1579
+ password: string,
1580
+ options?: PlaidClientOptions,
1581
+ ): Promise<PlaidClient>;
364
1582
  /** Build the link to hand someone for an invite code. */
365
1583
  static inviteUrl(appUrl: string, code: string): string;
366
1584
  /** Describe an invite code with no authentication (for a signup page). */
367
- static lookupInvite(baseUrl: string, code: string, options?: PlaidClientOptions): Promise<InvitePreview>;
1585
+ static lookupInvite(
1586
+ baseUrl: string,
1587
+ code: string,
1588
+ options?: PlaidClientOptions,
1589
+ ): Promise<InvitePreview>;
368
1590
  /** Redeem an invite code with no authentication; resolves to a logged-in client. */
369
1591
  static redeemInvite(
370
1592
  baseUrl: string,
371
1593
  code: string,
372
- credentials: { username?: string; password: string },
1594
+ credentials: { email?: string; password: string; displayName?: string },
373
1595
  options?: PlaidClientOptions,
374
1596
  ): Promise<{ client: PlaidClient; userId: string; kind: string }>;
1597
+ /** The server's base URL, without a trailing slash. */
1598
+ readonly baseUrl: string;
1599
+ /** The bearer token every request carries. */
1600
+ token: string;
375
1601
  timeout: number | null;
1602
+ batchTimeout: number | null;
1603
+ retryDelaysMs: number[] | undefined;
1604
+ /** The document strict mode is entered for, or null. */
1605
+ readonly strictModeDocumentId: string | null;
1606
+ /** The DocumentLockLost of the open `locked()` block whose lock lapsed, or null. Cleared when that block exits. */
1607
+ documentLockLost: DocumentLockLost | null;
376
1608
  /** Fired once on HTTP 401 (see PlaidClientOptions.onAuthError). */
377
1609
  onAuthError: ((error: Error) => void) | null;
1610
+ /** The latest version seen for each document written through this client, keyed by document id. */
1611
+ documentVersions: Record<string, number>;
1612
+ /** True once a response left its version list out (X-Document-Versions-Omitted, past fifty documents). Every version held was then forgotten, and the strict-mode document's is read again before the next write. */
1613
+ documentVersionsOmitted?: boolean;
1614
+ /** The server's clock minus this machine's, in ms, from the last response with a Date header, or null before one. */
1615
+ serverClockOffsetMs: number | null;
1616
+ /** The server's time now, from its last response's Date header (to the second), else this machine's. Judge a time the server stamped, such as an audit entry's `ts`, against this rather than `Date.now()`. */
1617
+ serverNow(): Date;
1618
+ /** Hear every time a request is sent again: after a 503, or for a keyed write after its answer was lost (no response, 502, 504). Called before the wait. Returns the unsubscribe. */
1619
+ onRetry(
1620
+ listener: (info: { attempt: number; retries: number; delay: number; error: Error }) => void,
1621
+ ): () => void;
378
1622
 
379
- // Batch control methods
380
- beginBatch(): void;
381
- submitBatch(): Promise<any[]>;
382
- abortBatch(): void;
383
- isBatchMode(): boolean;
384
- /** Run `fn` with a batch open, then submit atomically (or abort if `fn` throws). Resolves to the results array. */
385
- batched(fn: () => void | Promise<void>): Promise<any[]>;
1623
+ // Batches.
1624
+ //
1625
+ // A batch is a view of the client with the same bundles. A write of project
1626
+ // data made on the batch queues; a call made on the client itself always
1627
+ // goes over the wire, whatever batches are open, so a write by code that
1628
+ // knows nothing about a batch can never land inside it. A read, or a signal
1629
+ // that carries no project data (stopping a service request, a service
1630
+ // reporting its progress, taking and dropping a document lock, the admin
1631
+ // actions on the server itself), goes over the wire even when made on the
1632
+ // batch.
1633
+ /** Open a batch. Queue writes on it, then `submit()` them (one atomic request up to MAX_BATCH_OPS operations), or `abort()`. Not nestable. */
1634
+ batch(): PlaidBatch;
1635
+ /** Run `fn` with a batch, then submit it as `submit()` does (or abort if `fn` throws). Resolves to the results array, one entry per queued write. */
1636
+ batched(fn: (batch: PlaidBatch) => void | Promise<void>): Promise<any[]>;
386
1637
 
387
1638
  // Strict mode methods
388
1639
  enterStrictMode(documentId: string): void;
@@ -391,25 +1642,48 @@ export declare class PlaidClient {
391
1642
  // Logical operations (audit-log grouping). While one is open every write is
392
1643
  // stamped with its group id so the audit log folds them into one entry.
393
1644
  // Not atomic; nesting flattens into the outer operation.
394
- operationGroup: { id: string; message: string | null } | null;
395
- beginOperation(message: string, opts?: { id?: string }): string;
1645
+ // `kind` is one of OperationKind (the server refuses any other) and `ref`
1646
+ // names what the operation came from; nesting keeps the outer ones.
1647
+ operationGroup: {
1648
+ id: string;
1649
+ message: string | null;
1650
+ kind: OperationKind | null;
1651
+ ref: string | null;
1652
+ } | null;
1653
+ beginOperation(
1654
+ message: string,
1655
+ opts?: { id?: string; kind?: OperationKind; ref?: string; keys?: KeySeed; minted?: Set<string> },
1656
+ ): string;
1657
+ /** A seed for the Idempotency-Keys of an operation that may be run again (see beginOperation's `keys`). */
1658
+ keySeed(): KeySeed;
396
1659
  endOperation(message?: string): Promise<void>;
397
- withOperation<T>(message: string, fn: (setMessage: (msg: string) => void) => Promise<T> | T): Promise<T>;
1660
+ withOperation<T>(
1661
+ message: string,
1662
+ fn: (setMessage: (msg: string) => void) => Promise<T> | T,
1663
+ opts?: { kind?: OperationKind; ref?: string; id?: string; keys?: KeySeed; minted?: Set<string> },
1664
+ ): Promise<T>;
398
1665
  operationGroups: OperationGroupsBundle;
399
1666
 
400
1667
  // Query
401
- query(body: any, auditMessage?: string): Promise<any>;
1668
+ query(body: any): Promise<any>;
402
1669
 
403
1670
  vocabLinks: VocabLinksBundle;
404
1671
  vocabLayers: VocabLayersBundle;
405
1672
  relations: RelationsBundle;
406
1673
  spanLayers: SpanLayersBundle;
407
1674
  spans: SpansBundle;
408
- batch: BatchBundle;
409
1675
  texts: TextsBundle;
410
1676
  users: UsersBundle;
411
1677
  apiTokens: ApiTokensBundle;
1678
+ userData: UserDataBundle;
412
1679
  invites: InvitesBundle;
1680
+ server: ServerBundle;
1681
+ auth: AuthBundle;
1682
+ admin: AdminBundle;
1683
+ audit: AuditBundle;
1684
+ comments: CommentsBundle;
1685
+ events: EventsBundle;
1686
+ guidelines: GuidelinesBundle;
413
1687
  tokenLayers: TokenLayersBundle;
414
1688
  documents: DocumentsBundle;
415
1689
  messages: MessagesBundle;
@@ -432,31 +1706,60 @@ export function cpSlice(s: string, begin: number, end?: number): string;
432
1706
  export function cpSlicer(s: string): (begin: number, end?: number) => string;
433
1707
  /** UTF-16 index -> code-point index in `s`. */
434
1708
  export function utf16ToCp(s: string, u: number): number;
435
- /** Code-point index -> UTF-16 index in `s` (clamps past the end). */
436
- export function cpToUtf16(s: string, cp: number): number;
437
1709
  /** Like indexOf, but the result and `fromCp` are code-point indices; -1 if absent. */
438
1710
  export function cpIndexOf(s: string, sub: string, fromCp?: number): number;
439
1711
 
1712
+ // --- Text edit operations (code points, running coordinates) ---------------
1713
+ /** One op as `PATCH /texts/:id` takes it; each index is in the body the ops before it left. */
1714
+ export type TextEditOp =
1715
+ | { type: "insert"; index: number; value: string }
1716
+ | { type: "delete"; index: number; value: number }
1717
+ | { type: "replace"; index: number; length: number; value: string };
1718
+ /** Old-body code points [start, end) give way to `value`. */
1719
+ export interface TextGap {
1720
+ start: number;
1721
+ end: number;
1722
+ value: string;
1723
+ }
1724
+ /** The net change of `ops` on `body` as gaps: sorted, never touching, no-op gaps dropped. Throws on a bad op. */
1725
+ export function composeTextEdits(body: string, ops: TextEditOp[]): TextGap[];
1726
+ /** Gaps as running ops: insert, delete or replace. */
1727
+ export function gapsToOps(gaps: TextGap[]): TextEditOp[];
1728
+ /** `body` with `ops` applied in turn. Throws on a bad op. */
1729
+ export function applyTextOps(body: string, ops: TextEditOp[]): string;
1730
+
440
1731
  // --- Shared layer-role vocabulary (cross-app interoperability) --------------
441
1732
  // Substrate layers are tagged with a role at `config.plaid.role` (a scalar) so
442
1733
  // that different apps can share a project. See the manual, "Layer Interoperability".
443
1734
  /** The reserved config namespace for cross-app conventions. */
444
- export const PLAID_NAMESPACE: 'plaid';
1735
+ export const PLAID_NAMESPACE: "plaid";
445
1736
  /** The config key, under `plaid`, holding a layer's role. */
446
- export const ROLE_KEY: 'role';
1737
+ export const ROLE_KEY: "role";
1738
+ /** Layer config key naming the metadata keys a token born of a SPLIT inherits. */
1739
+ export const PRESERVE_ON_SPLIT_KEY: "preserveOnSplit";
1740
+ /** Layer config key: a space typed inside a word of the text splits it (see the manual's "Changing a text's body"). */
1741
+ export const SPLIT_ON_SPACE_KEY: "splitOnSpace";
1742
+
1743
+ /** The server's cap on operations per batch request; a larger batch goes as consecutive requests. */
1744
+ export const MAX_BATCH_OPS: 1000;
1745
+ /** How long `admin.backup()` waits for the server to finish writing a backup, in ms. */
1746
+ export const BACKUP_TIMEOUT_MS: number;
447
1747
  /** The fixed role inventory; only these values are interoperable across apps. */
448
1748
  export const ROLES: {
449
- readonly BASELINE: 'baseline';
450
- readonly SENTENCE: 'sentence';
451
- readonly WORD: 'word';
452
- readonly SYNTACTIC_WORD: 'syntactic-word';
453
- readonly MORPHEME: 'morpheme';
454
- readonly TIME_ALIGNMENT: 'time-alignment';
1749
+ readonly BASELINE: "baseline";
1750
+ readonly SENTENCE: "sentence";
1751
+ readonly WORD: "word";
1752
+ readonly SYNTACTIC_WORD: "syntactic-word";
1753
+ readonly MORPHEME: "morpheme";
1754
+ readonly TIME_ALIGNMENT: "time-alignment";
455
1755
  };
456
1756
  /** The role recorded on a layer's `config`, or null if none. */
457
1757
  export function readRole(config?: object): string | null;
458
1758
  /** The first layer in `layers` carrying the given role, or null. */
459
- export function findByRole<T extends { config?: object }>(layers: T[] | undefined, role: string): T | null;
1759
+ export function findByRole<T extends { config?: object }>(
1760
+ layers: T[] | undefined,
1761
+ role: string,
1762
+ ): T | null;
460
1763
 
461
1764
  // --- Service self-description helpers ----------------------------------------
462
1765
  // Standardize how a service advertises (in `extras`) the tasks it serves, a
@@ -465,16 +1768,24 @@ export function findByRole<T extends { config?: object }>(layers: T[] | undefine
465
1768
  // "Describing a service".
466
1769
  /** The controlled task vocabulary — the fixed integration-point goals. */
467
1770
  export const TASKS: {
468
- readonly TOKENIZE: 'tokenize';
469
- readonly PARSE: 'parse';
470
- readonly TRANSCRIBE: 'transcribe';
471
- readonly LINK_VOCAB: 'link-vocab';
472
- readonly ANALYZE: 'analyze';
1771
+ readonly TOKENIZE: "tokenize";
1772
+ readonly PARSE: "parse";
1773
+ readonly TRANSCRIBE: "transcribe";
1774
+ readonly LINK_VOCAB: "link-vocab";
1775
+ readonly ANALYZE: "analyze";
1776
+ readonly DRAFT_GRAPH: "draft-graph";
1777
+ readonly TRANSLATE: "translate";
1778
+ readonly ASSIST: "assist";
1779
+ readonly DETECT_SPEECH: "detect-speech";
1780
+ readonly COMPARE: "compare";
473
1781
  };
474
1782
  /** Whether a service serves a task (declared `extras.tasks`, legacy id-prefix fallback). */
475
1783
  export function servesTask(service: DiscoveredService, task: string): boolean;
476
1784
  /** The discovered services that serve `task`. */
477
- export function filterServicesByTask(services: DiscoveredService[] | undefined, task: string): DiscoveredService[];
1785
+ export function filterServicesByTask(
1786
+ services: DiscoveredService[] | undefined,
1787
+ task: string,
1788
+ ): DiscoveredService[];
478
1789
  /** The parameter schema a service declares (ordered), or []. */
479
1790
  export function getParamSchema(service: DiscoveredService): ServiceParam[];
480
1791
  /** A service's human summary: `extras.summary`, else `description`, else ''. */
@@ -482,7 +1793,10 @@ export function getServiceSummary(service: DiscoveredService): string;
482
1793
  /** Default form values keyed by param key. */
483
1794
  export function buildDefaultValues(schema: ServiceParam[]): Record<string, any>;
484
1795
  /** Coerce/validate raw form values against the schema. */
485
- export function coerceParamValues(schema: ServiceParam[], raw: Record<string, any>): { values: Record<string, any>; errors: Record<string, string> };
1796
+ export function coerceParamValues(
1797
+ schema: ServiceParam[],
1798
+ raw: Record<string, any>,
1799
+ ): { values: Record<string, any>; errors: Record<string, string> };
486
1800
 
487
1801
  // --- Provenance ---------------------------------------------------------------
488
1802
  // Cross-app convention for machine-provided vs human-labeled information,
@@ -492,41 +1806,199 @@ export function coerceParamValues(schema: ServiceParam[], raw: Record<string, an
492
1806
  // replace unverified machine material but must never touch human/verified
493
1807
  // material without an explicit overwrite opt-in; any human edit verifies.
494
1808
  // See the manual, "Provenance".
495
- type ProvState = 'human' | 'machine' | 'verified';
1809
+ type ProvState = "human" | "machine" | "contributed" | "verified";
496
1810
  export const PROV: {
497
- readonly key: 'prov';
498
- readonly sourceKey: 'provSource';
499
- readonly confirmedKey: 'provConfirmed';
500
- readonly probKey: 'provProb';
501
- readonly detailKey: 'provDetail';
502
- readonly INFERRED: 'inferred';
1811
+ readonly key: "prov";
1812
+ readonly sourceKey: "provSource";
1813
+ readonly confirmedKey: "provConfirmed";
1814
+ readonly probKey: "provProb";
1815
+ readonly detailKey: "provDetail";
1816
+ readonly INFERRED: "inferred";
1817
+ readonly CONTRIBUTED: "contributed";
503
1818
  };
504
1819
  export const PROV_STATES: {
505
- readonly HUMAN: 'human';
506
- readonly MACHINE: 'machine';
507
- readonly VERIFIED: 'verified';
1820
+ readonly HUMAN: "human";
1821
+ readonly MACHINE: "machine";
1822
+ readonly CONTRIBUTED: "contributed";
1823
+ readonly VERIFIED: "verified";
508
1824
  };
1825
+ /** The provenance keys Plaid itself owns — what a split carries and a reshape keeps. */
1826
+ export const PROVENANCE_KEYS: readonly string[];
509
1827
  /** Optional prediction extras: prob = a probability in [0,1] for the chosen value
510
1828
  * (flat + queryable; omit unless it honestly is one); detail = an open map of
511
1829
  * producer extras (top-k alternatives, model version, raw scores; keep it small).
512
1830
  * Both describe the ORIGINAL prediction — check provConfirmed before presenting
513
1831
  * provProb as confidence in the current value. */
514
- interface ProvExtras { prob?: number; detail?: Record<string, any> }
1832
+ interface ProvExtras {
1833
+ prob?: number;
1834
+ detail?: Record<string, any>;
1835
+ }
515
1836
  /** The metadata fragment a machine writer merges into everything it creates. */
516
- export function stampInferred(source: string, extras?: ProvExtras):
517
- { prov: 'inferred'; provSource: string; provProb?: number; provDetail?: Record<string, any> };
1837
+ export function stampInferred(
1838
+ source: string,
1839
+ extras?: ProvExtras,
1840
+ ): {
1841
+ prov: "inferred";
1842
+ provSource: string;
1843
+ provProb?: number;
1844
+ provDetail?: Record<string, any>;
1845
+ };
518
1846
  /** stampInferred + provConfirmed — for machine material born verified (e.g. imports with upstream approval). */
519
- export function confirmedInferred(source: string, extras?: ProvExtras):
520
- { prov: 'inferred'; provSource: string; provConfirmed: true; provProb?: number; provDetail?: Record<string, any> };
1847
+ export function confirmedInferred(
1848
+ source: string,
1849
+ extras?: ProvExtras,
1850
+ ): {
1851
+ prov: "inferred";
1852
+ provSource: string;
1853
+ provConfirmed: true;
1854
+ provProb?: number;
1855
+ provDetail?: Record<string, any>;
1856
+ };
521
1857
  /** Classify an entity's metadata into one of the three provenance states. */
522
1858
  export function provState(metadata: object | null | undefined): ProvState;
523
- /** The verifying fragment, { provConfirmed: true }: PATCH it over existing metadata. */
524
- export const PROV_CONFIRMED: { readonly provConfirmed: true };
525
1859
  /** Machine-made and not yet human-verified (needs review, replaceable, confirmable). */
526
1860
  export function isMachine(metadata: object | null | undefined): boolean;
527
1861
  /** Whether a machine writer must leave this entity alone (human or verified). !isMachine. */
528
1862
  export function isProtected(metadata: object | null | undefined): boolean;
529
- /** The fragment a HUMAN edit should merge in: PROV_CONFIRMED iff machine-unverified, else null. */
530
- export function verifyOnEdit(metadata: object | null | undefined): { readonly provConfirmed: true } | null;
531
1863
  /** Canonical provSource for a service: 'service:<serviceId>'. */
532
1864
  export function serviceSource(serviceId: string): string;
1865
+ /** The metadata fragment a contributor's work carries. */
1866
+ export function stampContributed(userId: string): {
1867
+ prov: "contributed";
1868
+ provSource: string;
1869
+ };
1870
+ /** Where an entity came from, confirmed or not: what a verified entity's tooltip needs. */
1871
+ export function provOrigin(
1872
+ metadata: object | null | undefined,
1873
+ ): null | "inferred" | "contributed";
1874
+ /** Whether a verifier still has to look at this entity: machine-made or contributed, unconfirmed. */
1875
+ export function needsReview(metadata: object | null | undefined): boolean;
1876
+ /** The fragment a CONTRIBUTOR's edit merges in: the contributed stamp, dropping any confirmation. */
1877
+ export function contributeOnEdit(
1878
+ metadata: object | null | undefined,
1879
+ userId: string,
1880
+ ): { prov: "contributed"; provSource: string; provConfirmed: null };
1881
+ /** Merge a fragment into a local copy, a null value deleting the key: applyMetadataOps(metadata, metadataOps(fragment)), refused where the server would refuse it. Returns a new object. */
1882
+ export function mergeMetadata(
1883
+ metadata: object | null | undefined,
1884
+ fragment: object | null | undefined,
1885
+ ): Record<string, any>;
1886
+
1887
+ // --- Create responses ---------------------------------------------------------
1888
+ /** The id a single create answered with, read off the call's response or its batch result (`{status, body}`). Undefined when it gave none. */
1889
+ /** Idempotency-Key seed for an operation run more than once (client.keySeed()). */
1890
+ export interface KeySeed {
1891
+ seed: string;
1892
+ stamps: Map<number, number | null>;
1893
+ }
1894
+ /** A fresh UUIDv7, for the id of something a client creates. */
1895
+ export function uuidv7(): string;
1896
+ /** Whether a write's answer was replayed from its Idempotency-Key's first send (`Idempotent-Replayed: true`), so it wrote nothing new. Marked on an object or list answer as a non-enumerable `replayed: true`. */
1897
+ export function wasReplayed(answer: unknown): boolean;
1898
+ export function createdId(result: any): string | undefined;
1899
+ /** The ids a bulk create answered with, in input order, read off the call's response or its batch result. Empty when it gave none. */
1900
+ export function createdIds(result: any): string[];
1901
+
1902
+ // --- Metadata ops -------------------------------------------------------------
1903
+ /** One metadata edit: the body of a metadata PATCH is a list of these. */
1904
+ export type MetadataOp =
1905
+ | { op: "set"; path: string[]; value: any }
1906
+ | { op: "delete"; path: string[] };
1907
+ /** The ops that set each top-level key of a fragment, a null value deleting it. */
1908
+ export function metadataOps(
1909
+ fragment: object | null | undefined,
1910
+ ): MetadataOp[];
1911
+ /** Apply ops to a local copy the way the server does. Returns a new object; throws where the server would refuse. */
1912
+ export function applyMetadataOps(
1913
+ metadata: object | null | undefined,
1914
+ ops: MetadataOp[],
1915
+ ): Record<string, any>;
1916
+ /** True for a top-level metadata key Plaid keeps for itself (`plaid` and the provenance keys), never a user's field. */
1917
+ export function isReservedMetadataKey(key: string): boolean;
1918
+
1919
+ // --- Review: whose work is reviewed (a project-config norm) -------------------
1920
+ /** The config key, under the `plaid` namespace, holding the review lists. */
1921
+ export const REVIEW_KEY: "review";
1922
+ /** A project's review lists, normalized. */
1923
+ export function readReview(config?: object | null): {
1924
+ users: string[];
1925
+ roles: string[];
1926
+ };
1927
+ /** A person's role in a project from its ACL lists; an admin with no entry is a maintainer. */
1928
+ export function projectRole(
1929
+ project: object | null | undefined,
1930
+ userId: string | null | undefined,
1931
+ opts?: { isAdmin?: boolean },
1932
+ ): "maintainer" | "writer" | "reader" | null;
1933
+ /** Whether this person's work is reviewed here: named in review.users, or holding a role in review.roles. */
1934
+ export function isReviewed(
1935
+ project: object | null | undefined,
1936
+ userId: string | null | undefined,
1937
+ opts?: { isAdmin?: boolean },
1938
+ ): boolean;
1939
+ /** The review lists with one person added to or removed from `users`. Pure. */
1940
+ export function withReviewedUser(
1941
+ review: { users?: string[]; roles?: string[] } | null | undefined,
1942
+ userId: string,
1943
+ reviewed: boolean,
1944
+ ): { users: string[]; roles: string[] };
1945
+ /** What one writer's writes carry and what their review gestures act on. */
1946
+ export function writerPolicy(contributorId?: string | null): {
1947
+ readonly contributorId: string | null;
1948
+ readonly isContributor: boolean;
1949
+ /** The metadata a NEW entity carries: null for a verifier. */
1950
+ readonly createStamp: Record<string, any> | null;
1951
+ /** The fragment an EDIT merges, or null when there is nothing to merge. */
1952
+ readonly editStamp: (
1953
+ metadata: object | null | undefined,
1954
+ ) => Record<string, any> | null;
1955
+ /** The fragment an explicit confirm gesture merges, or null. */
1956
+ readonly confirmStamp: (
1957
+ metadata: object | null | undefined,
1958
+ ) => Record<string, any> | null;
1959
+ /** What an adopted suggestion is written with. */
1960
+ readonly adoptStamp: (
1961
+ source: string,
1962
+ detail?: Record<string, any>,
1963
+ ) => Record<string, any>;
1964
+ /** Whether this writer's review gesture acts on the entity. */
1965
+ readonly reviewable: (metadata: object | null | undefined) => boolean;
1966
+ /** The same test over a provState. */
1967
+ readonly reviewableState: (state: string) => boolean;
1968
+ };
1969
+
1970
+ // --- Layer constraints -------------------------------------------------------
1971
+ /** A constraint an app declares on a layer, e.g. {type: "value-set", values: ["N"]}. */
1972
+ export interface LayerConstraint {
1973
+ type:
1974
+ | "max-in-degree"
1975
+ | "acyclic"
1976
+ | "same-ancestor"
1977
+ | "single-span"
1978
+ | "value-set"
1979
+ | "coextensive"
1980
+ | "single-link";
1981
+ [param: string]: any;
1982
+ }
1983
+ /** `expected`: the list read for the namespace, null when it was absent. */
1984
+ export interface ConstraintWriteOptions {
1985
+ expected?: LayerConstraint[] | null;
1986
+ }
1987
+ /** One violation a 422 lists. */
1988
+ export interface ConstraintViolation {
1989
+ constraint: string;
1990
+ namespace: string | null;
1991
+ layer: string;
1992
+ layerName: string;
1993
+ document: string | null;
1994
+ at: string | null;
1995
+ ids: string[];
1996
+ value?: any;
1997
+ parts?: string[];
1998
+ }
1999
+ /** The camelCased violations of a write refused by a layer constraint, or null. */
2000
+ export function violationsOf(err: any): ConstraintViolation[] | null;
2001
+ export function valueSetAllows(
2002
+ constraint: { values: string[]; delimiters?: string; parts?: "all" | "first" },
2003
+ value: any,
2004
+ ): boolean;