@larc-iu/plaid-client 0.1.0-alpha.8 → 0.2.0-alpha.0

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/src/index.js CHANGED
@@ -11,6 +11,7 @@ import { listAll, listPage, iterPages } from './pagination.js';
11
11
  import { createSSEConnection } from './sse.js';
12
12
  import {
13
13
  discoverServices,
14
+ discardService,
14
15
  serve,
15
16
  requestService,
16
17
  } from './services.js';
@@ -40,6 +41,14 @@ class PlaidClient {
40
41
  this.batchOperations = [];
41
42
  this.documentVersions = {};
42
43
  this.strictModeDocumentId = null;
44
+ // Ambient custom audit-log message applied to write requests; null = use
45
+ // the server's auto-generated description. See setAuditMessage / withAuditMessage.
46
+ this.auditMessage = null;
47
+ // Optional callback fired once (per client) when any request returns HTTP
48
+ // 401 — i.e. the token is missing/expired/invalid. Apps use it to discard
49
+ // the stored token and route back to login. See makeRequest in http.js.
50
+ this.onAuthError = options.onAuthError || null;
51
+ this._authErrorFired = false;
43
52
 
44
53
  // --- API Bundles ---
45
54
 
@@ -50,27 +59,51 @@ class PlaidClient {
50
59
  * @param {Array} tokens - The tokens to link
51
60
  * @param {any} [metadata] - Metadata for the link. Omit to leave unset; pass null to send JSON null.
52
61
  */
53
- create: (vocabItem, tokens, metadata) =>
54
- this._request('POST', '/api/v1/vocab-links', {
62
+ create: (vocabItem, tokens, metadata, auditMessage) =>
63
+ this._request('POST', '/api/v1/vocab-links', { auditMessage,
55
64
  body: bodyOf({ 'vocab-item': vocabItem, tokens, metadata }),
56
65
  }),
66
+ /**
67
+ * Create multiple vocab links in a single operation. Entries may
68
+ * reference different vocab items, but all tokens across the call must
69
+ * belong to one document.
70
+ * @param {Array<{vocabItem: string, tokens: string[], metadata?: any}>} body - The vocab links to create
71
+ * @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
72
+ */
73
+ bulkCreate: (body, auditMessage) =>
74
+ this._request('POST', '/api/v1/vocab-links/bulk', { auditMessage, body }),
75
+ /**
76
+ * Delete multiple vocab links in a single operation. Provide an array of IDs.
77
+ * @param {string[]} body - The vocab link IDs to delete
78
+ */
79
+ bulkDelete: (body, auditMessage) =>
80
+ this._request('DELETE', '/api/v1/vocab-links/bulk', { auditMessage, body }),
57
81
  /**
58
82
  * Replace all metadata for a vocab link. The entire metadata map is replaced - existing metadata keys not included in the request will be removed.
59
83
  * @param {string} id - The resource ID
60
84
  * @param {any} body - The request body
61
85
  */
62
- setMetadata: (id, body) =>
63
- this._request('PUT', `/api/v1/vocab-links/${id}/metadata`, {
86
+ setMetadata: (id, body, auditMessage) =>
87
+ this._request('PUT', `/api/v1/vocab-links/${id}/metadata`, { auditMessage,
64
88
  rawBody: body, skipResponseTransform: true,
65
89
  }),
66
90
  /**
67
91
  * Remove all metadata from a vocab link.
68
92
  * @param {string} id - The resource ID
69
93
  */
70
- deleteMetadata: (id) =>
71
- this._request('DELETE', `/api/v1/vocab-links/${id}/metadata`, {
94
+ deleteMetadata: (id, auditMessage) =>
95
+ this._request('DELETE', `/api/v1/vocab-links/${id}/metadata`, { auditMessage,
72
96
  skipResponseTransform: true,
73
97
  }),
98
+ /**
99
+ * Patch (shallow-merge) metadata for a vocab link. Keys present in the body are set or overwritten; keys not present are left untouched; a key whose value is null is deleted. Merging is top-level only (nested objects are replaced wholesale, not deep-merged), so a literal null cannot be stored as a value. An empty body changes no metadata.
100
+ * @param {string} id - The resource ID
101
+ * @param {any} body - The metadata patch
102
+ */
103
+ patchMetadata: (id, body, auditMessage) =>
104
+ this._request('PATCH', `/api/v1/vocab-links/${id}/metadata`, { auditMessage,
105
+ rawBody: body, skipResponseTransform: true,
106
+ }),
74
107
  /**
75
108
  * Get a vocab link by ID
76
109
  * @param {string} id - The resource ID
@@ -84,8 +117,8 @@ class PlaidClient {
84
117
  * Delete a vocab link
85
118
  * @param {string} id - The resource ID
86
119
  */
87
- delete: (id) =>
88
- this._request('DELETE', `/api/v1/vocab-links/${id}`),
120
+ delete: (id, auditMessage) =>
121
+ this._request('DELETE', `/api/v1/vocab-links/${id}`, { auditMessage }),
89
122
  };
90
123
 
91
124
  this.vocabLayers = {
@@ -103,15 +136,15 @@ class PlaidClient {
103
136
  * Delete a vocab layer.
104
137
  * @param {string} id - The resource ID
105
138
  */
106
- delete: (id) =>
107
- this._request('DELETE', `/api/v1/vocab-layers/${id}`),
139
+ delete: (id, auditMessage) =>
140
+ this._request('DELETE', `/api/v1/vocab-layers/${id}`, { auditMessage }),
108
141
  /**
109
142
  * Update a vocab layer's name.
110
143
  * @param {string} id - The resource ID
111
144
  * @param {string} name - The name
112
145
  */
113
- update: (id, name) =>
114
- this._request('PATCH', `/api/v1/vocab-layers/${id}`, {
146
+ update: (id, name, auditMessage) =>
147
+ this._request('PATCH', `/api/v1/vocab-layers/${id}`, { auditMessage,
115
148
  body: bodyOf({ name }),
116
149
  }),
117
150
  /**
@@ -121,8 +154,8 @@ class PlaidClient {
121
154
  * @param {string} configKey - The config key
122
155
  * @param {any} configValue - Configuration value to set
123
156
  */
124
- setConfig: (id, namespace, configKey, configValue) =>
125
- this._request('PUT', `/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`, {
157
+ setConfig: (id, namespace, configKey, configValue, auditMessage) =>
158
+ this._request('PUT', `/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`, { auditMessage,
126
159
  rawBody: configValue, skipResponseTransform: true,
127
160
  }),
128
161
  /**
@@ -131,8 +164,8 @@ class PlaidClient {
131
164
  * @param {string} namespace - The config namespace
132
165
  * @param {string} configKey - The config key
133
166
  */
134
- deleteConfig: (id, namespace, configKey) =>
135
- this._request('DELETE', `/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`, {
167
+ deleteConfig: (id, namespace, configKey, auditMessage) =>
168
+ this._request('DELETE', `/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`, { auditMessage,
136
169
  skipResponseTransform: true,
137
170
  }),
138
171
  /**
@@ -167,8 +200,8 @@ class PlaidClient {
167
200
  * Create a new vocab layer. Note: this also registers the user as a maintainer.
168
201
  * @param {string} name - The name
169
202
  */
170
- create: (name) =>
171
- this._request('POST', '/api/v1/vocab-layers', {
203
+ create: (name, auditMessage) =>
204
+ this._request('POST', '/api/v1/vocab-layers', { auditMessage,
172
205
  body: bodyOf({ name }),
173
206
  }),
174
207
  /**
@@ -176,15 +209,15 @@ class PlaidClient {
176
209
  * @param {string} id - The resource ID
177
210
  * @param {string} userId - The user ID
178
211
  */
179
- addMaintainer: (id, userId) =>
180
- this._request('POST', `/api/v1/vocab-layers/${id}/maintainers/${userId}`),
212
+ addMaintainer: (id, userId, auditMessage) =>
213
+ this._request('POST', `/api/v1/vocab-layers/${id}/maintainers/${userId}`, { auditMessage }),
181
214
  /**
182
215
  * Remove a user's maintainer privileges for this vocab layer.
183
216
  * @param {string} id - The resource ID
184
217
  * @param {string} userId - The user ID
185
218
  */
186
- removeMaintainer: (id, userId) =>
187
- this._request('DELETE', `/api/v1/vocab-layers/${id}/maintainers/${userId}`),
219
+ removeMaintainer: (id, userId, auditMessage) =>
220
+ this._request('DELETE', `/api/v1/vocab-layers/${id}/maintainers/${userId}`, { auditMessage }),
188
221
  };
189
222
 
190
223
  this.relations = {
@@ -193,25 +226,34 @@ class PlaidClient {
193
226
  * @param {string} relationId - The relation ID
194
227
  * @param {any} body - The request body
195
228
  */
196
- setMetadata: (relationId, body) =>
197
- this._request('PUT', `/api/v1/relations/${relationId}/metadata`, {
229
+ setMetadata: (relationId, body, auditMessage) =>
230
+ this._request('PUT', `/api/v1/relations/${relationId}/metadata`, { auditMessage,
198
231
  rawBody: body, skipResponseTransform: true,
199
232
  }),
200
233
  /**
201
234
  * Remove all metadata from a relation.
202
235
  * @param {string} relationId - The relation ID
203
236
  */
204
- deleteMetadata: (relationId) =>
205
- this._request('DELETE', `/api/v1/relations/${relationId}/metadata`, {
237
+ deleteMetadata: (relationId, auditMessage) =>
238
+ this._request('DELETE', `/api/v1/relations/${relationId}/metadata`, { auditMessage,
206
239
  skipResponseTransform: true,
207
240
  }),
241
+ /**
242
+ * Patch (shallow-merge) metadata for a relation. Keys present in the body are set or overwritten; keys not present are left untouched; a key whose value is null is deleted. Merging is top-level only (nested objects are replaced wholesale, not deep-merged), so a literal null cannot be stored as a value. An empty body changes no metadata.
243
+ * @param {string} relationId - The relation ID
244
+ * @param {any} body - The metadata patch
245
+ */
246
+ patchMetadata: (relationId, body, auditMessage) =>
247
+ this._request('PATCH', `/api/v1/relations/${relationId}/metadata`, { auditMessage,
248
+ rawBody: body, skipResponseTransform: true,
249
+ }),
208
250
  /**
209
251
  * Update the target span of a relation.
210
252
  * @param {string} relationId - The relation ID
211
253
  * @param {string} spanId - The span ID
212
254
  */
213
- setTarget: (relationId, spanId) =>
214
- this._request('PUT', `/api/v1/relations/${relationId}/target`, {
255
+ setTarget: (relationId, spanId, auditMessage) =>
256
+ this._request('PUT', `/api/v1/relations/${relationId}/target`, { auditMessage,
215
257
  body: bodyOf({ 'span-id': spanId }),
216
258
  }),
217
259
  /**
@@ -227,15 +269,15 @@ class PlaidClient {
227
269
  * Delete a relation.
228
270
  * @param {string} relationId - The relation ID
229
271
  */
230
- delete: (relationId) =>
231
- this._request('DELETE', `/api/v1/relations/${relationId}`),
272
+ delete: (relationId, auditMessage) =>
273
+ this._request('DELETE', `/api/v1/relations/${relationId}`, { auditMessage }),
232
274
  /**
233
275
  * Update a relation's value.
234
276
  * @param {string} relationId - The relation ID
235
277
  * @param {any} value - The value
236
278
  */
237
- update: (relationId, value) =>
238
- this._request('PATCH', `/api/v1/relations/${relationId}`, {
279
+ update: (relationId, value, auditMessage) =>
280
+ this._request('PATCH', `/api/v1/relations/${relationId}`, { auditMessage,
239
281
  body: bodyOf({ value }),
240
282
  }),
241
283
  /**
@@ -243,8 +285,8 @@ class PlaidClient {
243
285
  * @param {string} relationId - The relation ID
244
286
  * @param {string} spanId - The span ID
245
287
  */
246
- setSource: (relationId, spanId) =>
247
- this._request('PUT', `/api/v1/relations/${relationId}/source`, {
288
+ setSource: (relationId, spanId, auditMessage) =>
289
+ this._request('PUT', `/api/v1/relations/${relationId}/source`, { auditMessage,
248
290
  body: bodyOf({ 'span-id': spanId }),
249
291
  }),
250
292
  /**
@@ -257,22 +299,23 @@ class PlaidClient {
257
299
  * @param {any} value - The value
258
300
  * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
259
301
  */
260
- create: (layerId, sourceId, targetId, value, metadata) =>
261
- this._request('POST', '/api/v1/relations', {
302
+ create: (layerId, sourceId, targetId, value, metadata, auditMessage) =>
303
+ this._request('POST', '/api/v1/relations', { auditMessage,
262
304
  body: bodyOf({ 'layer-id': layerId, 'source-id': sourceId, 'target-id': targetId, value, metadata }),
263
305
  }),
264
306
  /**
265
307
  * Create multiple relations in a single operation.
266
308
  * @param {Array} body - The request body
309
+ * @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
267
310
  */
268
- bulkCreate: (body) =>
269
- this._request('POST', '/api/v1/relations/bulk', { body }),
311
+ bulkCreate: (body, auditMessage) =>
312
+ this._request('POST', '/api/v1/relations/bulk', { auditMessage, body }),
270
313
  /**
271
314
  * Delete multiple relations in a single operation. Provide an array of IDs.
272
315
  * @param {Array} body - The request body
273
316
  */
274
- bulkDelete: (body) =>
275
- this._request('DELETE', '/api/v1/relations/bulk', { body }),
317
+ bulkDelete: (body, auditMessage) =>
318
+ this._request('DELETE', '/api/v1/relations/bulk', { auditMessage, body }),
276
319
  };
277
320
 
278
321
  this.spanLayers = {
@@ -283,8 +326,8 @@ class PlaidClient {
283
326
  * @param {string} configKey - The config key
284
327
  * @param {any} configValue - Configuration value to set
285
328
  */
286
- setConfig: (spanLayerId, namespace, configKey, configValue) =>
287
- this._request('PUT', `/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`, {
329
+ setConfig: (spanLayerId, namespace, configKey, configValue, auditMessage) =>
330
+ this._request('PUT', `/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`, { auditMessage,
288
331
  rawBody: configValue, skipResponseTransform: true,
289
332
  }),
290
333
  /**
@@ -293,8 +336,8 @@ class PlaidClient {
293
336
  * @param {string} namespace - The config namespace
294
337
  * @param {string} configKey - The config key
295
338
  */
296
- deleteConfig: (spanLayerId, namespace, configKey) =>
297
- this._request('DELETE', `/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`, {
339
+ deleteConfig: (spanLayerId, namespace, configKey, auditMessage) =>
340
+ this._request('DELETE', `/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`, { auditMessage,
298
341
  skipResponseTransform: true,
299
342
  }),
300
343
  /**
@@ -310,15 +353,15 @@ class PlaidClient {
310
353
  * Delete a span layer.
311
354
  * @param {string} spanLayerId - The span layer ID
312
355
  */
313
- delete: (spanLayerId) =>
314
- this._request('DELETE', `/api/v1/span-layers/${spanLayerId}`),
356
+ delete: (spanLayerId, auditMessage) =>
357
+ this._request('DELETE', `/api/v1/span-layers/${spanLayerId}`, { auditMessage }),
315
358
  /**
316
359
  * Update a span layer's name.
317
360
  * @param {string} spanLayerId - The span layer ID
318
361
  * @param {string} name - The name
319
362
  */
320
- update: (spanLayerId, name) =>
321
- this._request('PATCH', `/api/v1/span-layers/${spanLayerId}`, {
363
+ update: (spanLayerId, name, auditMessage) =>
364
+ this._request('PATCH', `/api/v1/span-layers/${spanLayerId}`, { auditMessage,
322
365
  body: bodyOf({ name }),
323
366
  }),
324
367
  /**
@@ -326,8 +369,8 @@ class PlaidClient {
326
369
  * @param {string} tokenLayerId - The token layer ID
327
370
  * @param {string} name - The name
328
371
  */
329
- create: (tokenLayerId, name) =>
330
- this._request('POST', '/api/v1/span-layers', {
372
+ create: (tokenLayerId, name, auditMessage) =>
373
+ this._request('POST', '/api/v1/span-layers', { auditMessage,
331
374
  body: bodyOf({ 'token-layer-id': tokenLayerId, name }),
332
375
  }),
333
376
  /**
@@ -335,8 +378,8 @@ class PlaidClient {
335
378
  * @param {string} spanLayerId - The span layer ID
336
379
  * @param {string} direction - The direction ("up" or "down")
337
380
  */
338
- shift: (spanLayerId, direction) =>
339
- this._request('POST', `/api/v1/span-layers/${spanLayerId}/shift`, {
381
+ shift: (spanLayerId, direction, auditMessage) =>
382
+ this._request('POST', `/api/v1/span-layers/${spanLayerId}/shift`, { auditMessage,
340
383
  body: bodyOf({ direction }),
341
384
  }),
342
385
  };
@@ -347,8 +390,8 @@ class PlaidClient {
347
390
  * @param {string} spanId - The span ID
348
391
  * @param {Array} tokens - The tokens
349
392
  */
350
- setTokens: (spanId, tokens) =>
351
- this._request('PUT', `/api/v1/spans/${spanId}/tokens`, {
393
+ setTokens: (spanId, tokens, auditMessage) =>
394
+ this._request('PUT', `/api/v1/spans/${spanId}/tokens`, { auditMessage,
352
395
  body: bodyOf({ tokens }),
353
396
  }),
354
397
  /**
@@ -359,8 +402,8 @@ class PlaidClient {
359
402
  * @param {any} value - The value
360
403
  * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
361
404
  */
362
- create: (spanLayerId, tokens, value, metadata) =>
363
- this._request('POST', '/api/v1/spans', {
405
+ create: (spanLayerId, tokens, value, metadata, auditMessage) =>
406
+ this._request('POST', '/api/v1/spans', { auditMessage,
364
407
  body: bodyOf({ 'span-layer-id': spanLayerId, tokens, value, metadata }),
365
408
  }),
366
409
  /**
@@ -376,46 +419,56 @@ class PlaidClient {
376
419
  * Delete a span.
377
420
  * @param {string} spanId - The span ID
378
421
  */
379
- delete: (spanId) =>
380
- this._request('DELETE', `/api/v1/spans/${spanId}`),
422
+ delete: (spanId, auditMessage) =>
423
+ this._request('DELETE', `/api/v1/spans/${spanId}`, { auditMessage }),
381
424
  /**
382
425
  * Update a span's value.
383
426
  * @param {string} spanId - The span ID
384
427
  * @param {any} value - The value
385
428
  */
386
- update: (spanId, value) =>
387
- this._request('PATCH', `/api/v1/spans/${spanId}`, {
429
+ update: (spanId, value, auditMessage) =>
430
+ this._request('PATCH', `/api/v1/spans/${spanId}`, { auditMessage,
388
431
  body: bodyOf({ value }),
389
432
  }),
390
433
  /**
391
434
  * Create multiple spans in a single operation.
392
435
  * @param {Array} body - The request body
436
+ * @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
393
437
  */
394
- bulkCreate: (body) =>
395
- this._request('POST', '/api/v1/spans/bulk', { body }),
438
+ bulkCreate: (body, auditMessage) =>
439
+ this._request('POST', '/api/v1/spans/bulk', { auditMessage, body }),
396
440
  /**
397
441
  * Delete multiple spans in a single operation. Provide an array of IDs.
398
442
  * @param {Array} body - The request body
399
443
  */
400
- bulkDelete: (body) =>
401
- this._request('DELETE', '/api/v1/spans/bulk', { body }),
444
+ bulkDelete: (body, auditMessage) =>
445
+ this._request('DELETE', '/api/v1/spans/bulk', { auditMessage, body }),
402
446
  /**
403
447
  * Replace all metadata for a span.
404
448
  * @param {string} spanId - The span ID
405
449
  * @param {any} body - The request body
406
450
  */
407
- setMetadata: (spanId, body) =>
408
- this._request('PUT', `/api/v1/spans/${spanId}/metadata`, {
451
+ setMetadata: (spanId, body, auditMessage) =>
452
+ this._request('PUT', `/api/v1/spans/${spanId}/metadata`, { auditMessage,
409
453
  rawBody: body, skipResponseTransform: true,
410
454
  }),
411
455
  /**
412
456
  * Remove all metadata from a span.
413
457
  * @param {string} spanId - The span ID
414
458
  */
415
- deleteMetadata: (spanId) =>
416
- this._request('DELETE', `/api/v1/spans/${spanId}/metadata`, {
459
+ deleteMetadata: (spanId, auditMessage) =>
460
+ this._request('DELETE', `/api/v1/spans/${spanId}/metadata`, { auditMessage,
417
461
  skipResponseTransform: true,
418
462
  }),
463
+ /**
464
+ * Patch (shallow-merge) metadata for a span. Keys present in the body are set or overwritten; keys not present are left untouched; a key whose value is null is deleted. Merging is top-level only (nested objects are replaced wholesale, not deep-merged), so a literal null cannot be stored as a value. An empty body changes no metadata.
465
+ * @param {string} spanId - The span ID
466
+ * @param {any} body - The metadata patch
467
+ */
468
+ patchMetadata: (spanId, body, auditMessage) =>
469
+ this._request('PATCH', `/api/v1/spans/${spanId}/metadata`, { auditMessage,
470
+ rawBody: body, skipResponseTransform: true,
471
+ }),
419
472
  };
420
473
 
421
474
  this.batch = {
@@ -424,8 +477,8 @@ class PlaidClient {
424
477
  * changes are rolled back.
425
478
  * @param {Array} body - The request body
426
479
  */
427
- submit: (body) =>
428
- this._request('POST', '/api/v1/batch', {
480
+ submit: (body, auditMessage) =>
481
+ this._request('POST', '/api/v1/batch', { auditMessage,
429
482
  body, noBatch: true,
430
483
  }),
431
484
  };
@@ -436,18 +489,27 @@ class PlaidClient {
436
489
  * @param {string} textId - The text ID
437
490
  * @param {any} body - The request body
438
491
  */
439
- setMetadata: (textId, body) =>
440
- this._request('PUT', `/api/v1/texts/${textId}/metadata`, {
492
+ setMetadata: (textId, body, auditMessage) =>
493
+ this._request('PUT', `/api/v1/texts/${textId}/metadata`, { auditMessage,
441
494
  rawBody: body, skipResponseTransform: true,
442
495
  }),
443
496
  /**
444
497
  * Remove all metadata from a text.
445
498
  * @param {string} textId - The text ID
446
499
  */
447
- deleteMetadata: (textId) =>
448
- this._request('DELETE', `/api/v1/texts/${textId}/metadata`, {
500
+ deleteMetadata: (textId, auditMessage) =>
501
+ this._request('DELETE', `/api/v1/texts/${textId}/metadata`, { auditMessage,
449
502
  skipResponseTransform: true,
450
503
  }),
504
+ /**
505
+ * Patch (shallow-merge) metadata for a text. Keys present in the body are set or overwritten; keys not present are left untouched; a key whose value is null is deleted. Merging is top-level only (nested objects are replaced wholesale, not deep-merged), so a literal null cannot be stored as a value. An empty body changes no metadata.
506
+ * @param {string} textId - The text ID
507
+ * @param {any} body - The metadata patch
508
+ */
509
+ patchMetadata: (textId, body, auditMessage) =>
510
+ this._request('PATCH', `/api/v1/texts/${textId}/metadata`, { auditMessage,
511
+ rawBody: body, skipResponseTransform: true,
512
+ }),
451
513
  /**
452
514
  * Create a new text in a document's text layer. A text is a container for
453
515
  * one long string in `body` for a given layer.
@@ -456,8 +518,8 @@ class PlaidClient {
456
518
  * @param {string} body - The request body
457
519
  * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
458
520
  */
459
- create: (textLayerId, documentId, body, metadata) =>
460
- this._request('POST', '/api/v1/texts', {
521
+ create: (textLayerId, documentId, body, metadata, auditMessage) =>
522
+ this._request('POST', '/api/v1/texts', { auditMessage,
461
523
  body: bodyOf({ 'text-layer-id': textLayerId, 'document-id': documentId, body, metadata }),
462
524
  }),
463
525
  /**
@@ -473,8 +535,8 @@ class PlaidClient {
473
535
  * Delete a text and all dependent data.
474
536
  * @param {string} textId - The text ID
475
537
  */
476
- delete: (textId) =>
477
- this._request('DELETE', `/api/v1/texts/${textId}`),
538
+ delete: (textId, auditMessage) =>
539
+ this._request('DELETE', `/api/v1/texts/${textId}`, { auditMessage }),
478
540
  /**
479
541
  * Update a text's body. A diff is computed and token indices are updated
480
542
  * so that tokens remain intact. Alternatively, `body` can be a list of
@@ -482,49 +544,53 @@ class PlaidClient {
482
544
  * @param {string} textId - The text ID
483
545
  * @param {any} body - The request body
484
546
  */
485
- update: (textId, body) =>
486
- this._request('PATCH', `/api/v1/texts/${textId}`, {
547
+ update: (textId, body, auditMessage) =>
548
+ this._request('PATCH', `/api/v1/texts/${textId}`, { auditMessage,
487
549
  body: bodyOf({ body }),
488
550
  }),
489
551
  };
490
552
 
491
553
  this.users = {
492
554
  /**
493
- * List all users. Transparently follows pagination cursors and returns
494
- * the full flat array.
555
+ * List (or search) users. Transparently follows pagination cursors and
556
+ * returns the full flat array. Admin-or-maintainer only.
495
557
  * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
496
- * @param {string} [asOf] - Temporal query timestamp
558
+ * @param {object} [opts]
559
+ * @param {string} [opts.q] - Filter to usernames containing this text (case-insensitive)
560
+ * @param {string} [opts.asOf] - Temporal query timestamp
497
561
  */
498
- list: (asOf) =>
499
- listAll(this, '/api/v1/users', { query: { 'as-of': asOf } }),
562
+ list: ({ q, asOf } = {}) =>
563
+ listAll(this, '/api/v1/users', { query: { q, 'as-of': asOf } }),
500
564
  /**
501
- * Fetch a single page of users.
565
+ * Fetch a single page of users (optionally filtered by `q`).
502
566
  * @param {object} [opts]
567
+ * @param {string} [opts.q] - Filter to usernames containing this text (case-insensitive)
503
568
  * @param {number} [opts.limit] - Page size (1..1000; server default 100)
504
569
  * @param {string} [opts.cursor] - Opaque cursor from a previous page
505
570
  * @param {string} [opts.asOf] - Temporal query timestamp
506
571
  * @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
507
572
  */
508
- listPage: ({ limit, cursor, asOf } = {}) =>
509
- listPage(this, '/api/v1/users', { limit, cursor, query: { 'as-of': asOf } }),
573
+ listPage: ({ q, limit, cursor, asOf } = {}) =>
574
+ listPage(this, '/api/v1/users', { limit, cursor, query: { q, 'as-of': asOf } }),
510
575
  /**
511
576
  * Async-iterate users page by page; yields each page's entries array.
512
577
  * @param {object} [opts]
578
+ * @param {string} [opts.q] - Filter to usernames containing this text (case-insensitive)
513
579
  * @param {number} [opts.pageSize] - Per-request page size
514
580
  * @param {string} [opts.asOf] - Temporal query timestamp
515
581
  * Cannot be used inside a batch (auto-paginates across requests); throws on first iteration if called while batching — use listPage() for a single page in a batch.
516
582
  * @returns {AsyncGenerator<Array>}
517
583
  */
518
- iterPages: ({ pageSize, asOf } = {}) =>
519
- iterPages(this, '/api/v1/users', { pageSize, query: { 'as-of': asOf } }),
584
+ iterPages: ({ q, pageSize, asOf } = {}) =>
585
+ iterPages(this, '/api/v1/users', { pageSize, query: { q, 'as-of': asOf } }),
520
586
  /**
521
587
  * Create a new user
522
588
  * @param {string} username - The username
523
589
  * @param {string} password - The password
524
590
  * @param {boolean} isAdmin - Whether the user is an admin
525
591
  */
526
- create: (username, password, isAdmin) =>
527
- this._request('POST', '/api/v1/users', {
592
+ create: (username, password, isAdmin, auditMessage) =>
593
+ this._request('POST', '/api/v1/users', { auditMessage,
528
594
  body: bodyOf({ username, password, 'is-admin': isAdmin }),
529
595
  }),
530
596
  /**
@@ -550,11 +616,23 @@ class PlaidClient {
550
616
  queryParams: { 'as-of': asOf },
551
617
  }),
552
618
  /**
553
- * Delete a user
619
+ * Deactivate a user. Users are never hard-deleted: deactivation
620
+ * rejects their logins and tokens, strips their project memberships
621
+ * and vocab maintainerships, and revokes their API tokens. The user
622
+ * stays visible in listings with a deactivated-at timestamp.
623
+ * Reversible via activate(), which restores login only.
554
624
  * @param {string} id - The resource ID
555
625
  */
556
- delete: (id) =>
557
- this._request('DELETE', `/api/v1/users/${id}`),
626
+ delete: (id, auditMessage) =>
627
+ this._request('DELETE', `/api/v1/users/${id}`, { auditMessage }),
628
+ /**
629
+ * Reactivate a deactivated user, restoring their ability to log in.
630
+ * Project memberships, vocab maintainerships, and API tokens removed
631
+ * at deactivation are NOT restored — re-grant them deliberately.
632
+ * @param {string} id - The resource ID
633
+ */
634
+ activate: (id, auditMessage) =>
635
+ this._request('POST', `/api/v1/users/${id}/activate`, { auditMessage }),
558
636
  /**
559
637
  * Modify a user. Admins may change the username, password, and admin
560
638
  * status of any user. All other users may only modify their own username
@@ -564,8 +642,8 @@ class PlaidClient {
564
642
  * @param {string} [username] - New username
565
643
  * @param {boolean} [isAdmin] - New admin status
566
644
  */
567
- update: (id, password, username, isAdmin) =>
568
- this._request('PATCH', `/api/v1/users/${id}`, {
645
+ update: (id, password, username, isAdmin, auditMessage) =>
646
+ this._request('PATCH', `/api/v1/users/${id}`, { auditMessage,
569
647
  body: bodyOf({ password, username, 'is-admin': isAdmin }),
570
648
  }),
571
649
  };
@@ -609,8 +687,8 @@ class PlaidClient {
609
687
  * @param {string} name - A human label, e.g. "Stanza Parser"
610
688
  * @returns {Promise<{id: string, name: string, token: string}>}
611
689
  */
612
- create: (userId, name) =>
613
- this._request('POST', `/api/v1/users/${userId}/tokens`, {
690
+ create: (userId, name, auditMessage) =>
691
+ this._request('POST', `/api/v1/users/${userId}/tokens`, { auditMessage,
614
692
  body: bodyOf({ name }),
615
693
  }),
616
694
  /**
@@ -618,8 +696,8 @@ class PlaidClient {
618
696
  * @param {string} userId - The user ID who owns the token
619
697
  * @param {string} tokenId - The token ID to revoke
620
698
  */
621
- revoke: (userId, tokenId) =>
622
- this._request('DELETE', `/api/v1/users/${userId}/tokens/${tokenId}`),
699
+ revoke: (userId, tokenId, auditMessage) =>
700
+ this._request('DELETE', `/api/v1/users/${userId}/tokens/${tokenId}`, { auditMessage }),
623
701
  };
624
702
 
625
703
  this.tokenLayers = {
@@ -628,8 +706,8 @@ class PlaidClient {
628
706
  * @param {string} tokenLayerId - The token layer ID
629
707
  * @param {string} direction - The direction ("up" or "down")
630
708
  */
631
- shift: (tokenLayerId, direction) =>
632
- this._request('POST', `/api/v1/token-layers/${tokenLayerId}/shift`, {
709
+ shift: (tokenLayerId, direction, auditMessage) =>
710
+ this._request('POST', `/api/v1/token-layers/${tokenLayerId}/shift`, { auditMessage,
633
711
  body: bodyOf({ direction }),
634
712
  }),
635
713
  /**
@@ -639,8 +717,8 @@ class PlaidClient {
639
717
  * @param {string} [overlapMode] - Per-layer, immutable token invariant: "any" (default), "non-overlapping", or "partitioning". On partitioning layers, single token create/update/delete are rejected; use bulkCreate plus split/merge/shift.
640
718
  * @param {string} [parentTokenLayerId] - Optional immutable parent token layer. Tokens in this layer must nest within a parent-layer token; the parent layer must be in the same text layer and be "non-overlapping" or "partitioning" (an "any" parent is rejected). A nested layer may be "any" or "non-overlapping" but not "partitioning" (partitioning is only for root layers), e.g. words (non-overlapping, parent=sentences) within sentences (partitioning).
641
719
  */
642
- create: (textLayerId, name, overlapMode, parentTokenLayerId) =>
643
- this._request('POST', '/api/v1/token-layers', {
720
+ create: (textLayerId, name, overlapMode, parentTokenLayerId, auditMessage) =>
721
+ this._request('POST', '/api/v1/token-layers', { auditMessage,
644
722
  body: bodyOf({ 'text-layer-id': textLayerId, name, 'overlap-mode': overlapMode, 'parent-token-layer-id': parentTokenLayerId }),
645
723
  }),
646
724
  /**
@@ -650,8 +728,8 @@ class PlaidClient {
650
728
  * @param {string} configKey - The config key
651
729
  * @param {any} configValue - Configuration value to set
652
730
  */
653
- setConfig: (tokenLayerId, namespace, configKey, configValue) =>
654
- this._request('PUT', `/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`, {
731
+ setConfig: (tokenLayerId, namespace, configKey, configValue, auditMessage) =>
732
+ this._request('PUT', `/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`, { auditMessage,
655
733
  rawBody: configValue, skipResponseTransform: true,
656
734
  }),
657
735
  /**
@@ -660,8 +738,8 @@ class PlaidClient {
660
738
  * @param {string} namespace - The config namespace
661
739
  * @param {string} configKey - The config key
662
740
  */
663
- deleteConfig: (tokenLayerId, namespace, configKey) =>
664
- this._request('DELETE', `/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`, {
741
+ deleteConfig: (tokenLayerId, namespace, configKey, auditMessage) =>
742
+ this._request('DELETE', `/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`, { auditMessage,
665
743
  skipResponseTransform: true,
666
744
  }),
667
745
  /**
@@ -677,15 +755,15 @@ class PlaidClient {
677
755
  * Delete a token layer.
678
756
  * @param {string} tokenLayerId - The token layer ID
679
757
  */
680
- delete: (tokenLayerId) =>
681
- this._request('DELETE', `/api/v1/token-layers/${tokenLayerId}`),
758
+ delete: (tokenLayerId, auditMessage) =>
759
+ this._request('DELETE', `/api/v1/token-layers/${tokenLayerId}`, { auditMessage }),
682
760
  /**
683
761
  * Update a token layer's name.
684
762
  * @param {string} tokenLayerId - The token layer ID
685
763
  * @param {string} name - The name
686
764
  */
687
- update: (tokenLayerId, name) =>
688
- this._request('PATCH', `/api/v1/token-layers/${tokenLayerId}`, {
765
+ update: (tokenLayerId, name, auditMessage) =>
766
+ this._request('PATCH', `/api/v1/token-layers/${tokenLayerId}`, { auditMessage,
689
767
  body: bodyOf({ name }),
690
768
  }),
691
769
  };
@@ -704,14 +782,14 @@ class PlaidClient {
704
782
  * Acquire or refresh a document lock
705
783
  * @param {string} documentId - The document ID
706
784
  */
707
- acquireLock: (documentId) =>
708
- this._request('POST', `/api/v1/documents/${documentId}/lock`),
785
+ acquireLock: (documentId, auditMessage) =>
786
+ this._request('POST', `/api/v1/documents/${documentId}/lock`, { auditMessage }),
709
787
  /**
710
788
  * Release a document lock
711
789
  * @param {string} documentId - The document ID
712
790
  */
713
- releaseLock: (documentId) =>
714
- this._request('DELETE', `/api/v1/documents/${documentId}/lock`),
791
+ releaseLock: (documentId, auditMessage) =>
792
+ this._request('DELETE', `/api/v1/documents/${documentId}/lock`, { auditMessage }),
715
793
  /**
716
794
  * Get media file for a document
717
795
  * @param {string} documentId - The document ID
@@ -728,10 +806,10 @@ class PlaidClient {
728
806
  * @param {string} documentId - The document ID
729
807
  * @param {File} file - The file to upload
730
808
  */
731
- uploadMedia: (documentId, file) => {
809
+ uploadMedia: (documentId, file, auditMessage) => {
732
810
  const fd = new FormData();
733
811
  fd.append('file', file);
734
- return this._request('PUT', `/api/v1/documents/${documentId}/media`, {
812
+ return this._request('PUT', `/api/v1/documents/${documentId}/media`, { auditMessage,
735
813
  body: fd, formData: true, noBatch: true,
736
814
  });
737
815
  },
@@ -739,8 +817,8 @@ class PlaidClient {
739
817
  * Delete media file for a document
740
818
  * @param {string} documentId - The document ID
741
819
  */
742
- deleteMedia: (documentId) =>
743
- this._request('DELETE', `/api/v1/documents/${documentId}/media`, {
820
+ deleteMedia: (documentId, auditMessage) =>
821
+ this._request('DELETE', `/api/v1/documents/${documentId}/media`, { auditMessage,
744
822
  noBatch: true,
745
823
  }),
746
824
  /**
@@ -748,18 +826,27 @@ class PlaidClient {
748
826
  * @param {string} documentId - The document ID
749
827
  * @param {any} body - The request body
750
828
  */
751
- setMetadata: (documentId, body) =>
752
- this._request('PUT', `/api/v1/documents/${documentId}/metadata`, {
829
+ setMetadata: (documentId, body, auditMessage) =>
830
+ this._request('PUT', `/api/v1/documents/${documentId}/metadata`, { auditMessage,
753
831
  rawBody: body, skipResponseTransform: true,
754
832
  }),
755
833
  /**
756
834
  * Remove all metadata from a document.
757
835
  * @param {string} documentId - The document ID
758
836
  */
759
- deleteMetadata: (documentId) =>
760
- this._request('DELETE', `/api/v1/documents/${documentId}/metadata`, {
837
+ deleteMetadata: (documentId, auditMessage) =>
838
+ this._request('DELETE', `/api/v1/documents/${documentId}/metadata`, { auditMessage,
761
839
  skipResponseTransform: true,
762
840
  }),
841
+ /**
842
+ * Patch (shallow-merge) metadata for a document. Keys present in the body are set or overwritten; keys not present are left untouched; a key whose value is null is deleted. Merging is top-level only (nested objects are replaced wholesale, not deep-merged), so a literal null cannot be stored as a value. An empty body changes no metadata.
843
+ * @param {string} documentId - The document ID
844
+ * @param {any} body - The metadata patch
845
+ */
846
+ patchMetadata: (documentId, body, auditMessage) =>
847
+ this._request('PATCH', `/api/v1/documents/${documentId}/metadata`, { auditMessage,
848
+ rawBody: body, skipResponseTransform: true,
849
+ }),
763
850
  /**
764
851
  * Get audit log for a document. Transparently follows pagination cursors
765
852
  * and returns the full flat array.
@@ -787,15 +874,15 @@ class PlaidClient {
787
874
  * Delete a document and all data contained.
788
875
  * @param {string} documentId - The document ID
789
876
  */
790
- delete: (documentId) =>
791
- this._request('DELETE', `/api/v1/documents/${documentId}`),
877
+ delete: (documentId, auditMessage) =>
878
+ this._request('DELETE', `/api/v1/documents/${documentId}`, { auditMessage }),
792
879
  /**
793
880
  * Update a document's name.
794
881
  * @param {string} documentId - The document ID
795
882
  * @param {string} name - The name
796
883
  */
797
- update: (documentId, name) =>
798
- this._request('PATCH', `/api/v1/documents/${documentId}`, {
884
+ update: (documentId, name, auditMessage) =>
885
+ this._request('PATCH', `/api/v1/documents/${documentId}`, { auditMessage,
799
886
  body: bodyOf({ name }),
800
887
  }),
801
888
  /**
@@ -804,8 +891,8 @@ class PlaidClient {
804
891
  * @param {string} name - The name
805
892
  * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
806
893
  */
807
- create: (projectId, name, metadata) =>
808
- this._request('POST', '/api/v1/documents', {
894
+ create: (projectId, name, metadata, auditMessage) =>
895
+ this._request('POST', '/api/v1/documents', { auditMessage,
809
896
  body: bodyOf({ 'project-id': projectId, name, metadata }),
810
897
  }),
811
898
  };
@@ -816,29 +903,29 @@ class PlaidClient {
816
903
  * @param {string} id - The resource ID
817
904
  * @param {string} userId - The user ID
818
905
  */
819
- addWriter: (id, userId) =>
820
- this._request('POST', `/api/v1/projects/${id}/writers/${userId}`),
906
+ addWriter: (id, userId, auditMessage) =>
907
+ this._request('POST', `/api/v1/projects/${id}/writers/${userId}`, { auditMessage }),
821
908
  /**
822
909
  * Remove a user's writer privileges for this project.
823
910
  * @param {string} id - The resource ID
824
911
  * @param {string} userId - The user ID
825
912
  */
826
- removeWriter: (id, userId) =>
827
- this._request('DELETE', `/api/v1/projects/${id}/writers/${userId}`),
913
+ removeWriter: (id, userId, auditMessage) =>
914
+ this._request('DELETE', `/api/v1/projects/${id}/writers/${userId}`, { auditMessage }),
828
915
  /**
829
916
  * Set a user's access level to read-only for this project.
830
917
  * @param {string} id - The resource ID
831
918
  * @param {string} userId - The user ID
832
919
  */
833
- addReader: (id, userId) =>
834
- this._request('POST', `/api/v1/projects/${id}/readers/${userId}`),
920
+ addReader: (id, userId, auditMessage) =>
921
+ this._request('POST', `/api/v1/projects/${id}/readers/${userId}`, { auditMessage }),
835
922
  /**
836
923
  * Remove a user's reader privileges for this project.
837
924
  * @param {string} id - The resource ID
838
925
  * @param {string} userId - The user ID
839
926
  */
840
- removeReader: (id, userId) =>
841
- this._request('DELETE', `/api/v1/projects/${id}/readers/${userId}`),
927
+ removeReader: (id, userId, auditMessage) =>
928
+ this._request('DELETE', `/api/v1/projects/${id}/readers/${userId}`, { auditMessage }),
842
929
  /**
843
930
  * Set a configuration value for a project in an editor namespace.
844
931
  * @param {string} id - The resource ID
@@ -846,8 +933,8 @@ class PlaidClient {
846
933
  * @param {string} configKey - The config key
847
934
  * @param {any} configValue - Configuration value to set
848
935
  */
849
- setConfig: (id, namespace, configKey, configValue) =>
850
- this._request('PUT', `/api/v1/projects/${id}/config/${namespace}/${configKey}`, {
936
+ setConfig: (id, namespace, configKey, configValue, auditMessage) =>
937
+ this._request('PUT', `/api/v1/projects/${id}/config/${namespace}/${configKey}`, { auditMessage,
851
938
  rawBody: configValue, skipResponseTransform: true,
852
939
  }),
853
940
  /**
@@ -856,8 +943,8 @@ class PlaidClient {
856
943
  * @param {string} namespace - The config namespace
857
944
  * @param {string} configKey - The config key
858
945
  */
859
- deleteConfig: (id, namespace, configKey) =>
860
- this._request('DELETE', `/api/v1/projects/${id}/config/${namespace}/${configKey}`, {
946
+ deleteConfig: (id, namespace, configKey, auditMessage) =>
947
+ this._request('DELETE', `/api/v1/projects/${id}/config/${namespace}/${configKey}`, { auditMessage,
861
948
  skipResponseTransform: true,
862
949
  }),
863
950
  /**
@@ -865,15 +952,15 @@ class PlaidClient {
865
952
  * @param {string} id - The resource ID
866
953
  * @param {string} userId - The user ID
867
954
  */
868
- addMaintainer: (id, userId) =>
869
- this._request('POST', `/api/v1/projects/${id}/maintainers/${userId}`),
955
+ addMaintainer: (id, userId, auditMessage) =>
956
+ this._request('POST', `/api/v1/projects/${id}/maintainers/${userId}`, { auditMessage }),
870
957
  /**
871
958
  * Remove a user's maintainer privileges for this project.
872
959
  * @param {string} id - The resource ID
873
960
  * @param {string} userId - The user ID
874
961
  */
875
- removeMaintainer: (id, userId) =>
876
- this._request('DELETE', `/api/v1/projects/${id}/maintainers/${userId}`),
962
+ removeMaintainer: (id, userId, auditMessage) =>
963
+ this._request('DELETE', `/api/v1/projects/${id}/maintainers/${userId}`, { auditMessage }),
877
964
  /**
878
965
  * Get audit log for a project. Transparently follows pagination cursors
879
966
  * and returns the full flat array.
@@ -892,15 +979,15 @@ class PlaidClient {
892
979
  * @param {string} id - The resource ID
893
980
  * @param {string} vocabId - The vocab layer ID
894
981
  */
895
- linkVocab: (id, vocabId) =>
896
- this._request('POST', `/api/v1/projects/${id}/vocabs/${vocabId}`),
982
+ linkVocab: (id, vocabId, auditMessage) =>
983
+ this._request('POST', `/api/v1/projects/${id}/vocabs/${vocabId}`, { auditMessage }),
897
984
  /**
898
985
  * Unlink a vocabulary from a project.
899
986
  * @param {string} id - The resource ID
900
987
  * @param {string} vocabId - The vocab layer ID
901
988
  */
902
- unlinkVocab: (id, vocabId) =>
903
- this._request('DELETE', `/api/v1/projects/${id}/vocabs/${vocabId}`),
989
+ unlinkVocab: (id, vocabId, auditMessage) =>
990
+ this._request('DELETE', `/api/v1/projects/${id}/vocabs/${vocabId}`, { auditMessage }),
904
991
  /**
905
992
  * Get a project by ID. To fetch the project's documents, use
906
993
  * listDocuments(id) — the include-documents flag has been removed.
@@ -950,18 +1037,25 @@ class PlaidClient {
950
1037
  iterDocuments: (id, { pageSize } = {}) =>
951
1038
  iterPages(this, `/api/v1/projects/${id}/documents`, { pageSize }),
952
1039
  /**
953
- * Delete a project.
1040
+ * Delete a project and everything in it. This is irrecoverable.
1041
+ *
1042
+ * For a large project this can take a long time, so the client's
1043
+ * per-request timeout is DISABLED by default. Pass `{ timeout }` to
1044
+ * impose a finite bound.
954
1045
  * @param {string} id - The resource ID
1046
+ * @param {string} [auditMessage] - Custom audit-log message
1047
+ * @param {object} [options]
1048
+ * @param {number} [options.timeout=0] - Per-request timeout in ms (0/null disables)
955
1049
  */
956
- delete: (id) =>
957
- this._request('DELETE', `/api/v1/projects/${id}`),
1050
+ delete: (id, auditMessage, { timeout = 0 } = {}) =>
1051
+ this._request('DELETE', `/api/v1/projects/${id}`, { auditMessage, timeout }),
958
1052
  /**
959
1053
  * Update a project's name.
960
1054
  * @param {string} id - The resource ID
961
1055
  * @param {string} name - The name
962
1056
  */
963
- update: (id, name) =>
964
- this._request('PATCH', `/api/v1/projects/${id}`, {
1057
+ update: (id, name, auditMessage) =>
1058
+ this._request('PATCH', `/api/v1/projects/${id}`, { auditMessage,
965
1059
  body: bodyOf({ name }),
966
1060
  }),
967
1061
  /**
@@ -996,8 +1090,8 @@ class PlaidClient {
996
1090
  * Create a new project. Note: this also registers the user as a maintainer.
997
1091
  * @param {string} name - The name
998
1092
  */
999
- create: (name) =>
1000
- this._request('POST', '/api/v1/projects', {
1093
+ create: (name, auditMessage) =>
1094
+ this._request('POST', '/api/v1/projects', { auditMessage,
1001
1095
  body: bodyOf({ name }),
1002
1096
  }),
1003
1097
  };
@@ -1010,8 +1104,8 @@ class PlaidClient {
1010
1104
  * @param {string} configKey - The config key
1011
1105
  * @param {any} configValue - Configuration value to set
1012
1106
  */
1013
- setConfig: (textLayerId, namespace, configKey, configValue) =>
1014
- this._request('PUT', `/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`, {
1107
+ setConfig: (textLayerId, namespace, configKey, configValue, auditMessage) =>
1108
+ this._request('PUT', `/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`, { auditMessage,
1015
1109
  rawBody: configValue, skipResponseTransform: true,
1016
1110
  }),
1017
1111
  /**
@@ -1020,8 +1114,8 @@ class PlaidClient {
1020
1114
  * @param {string} namespace - The config namespace
1021
1115
  * @param {string} configKey - The config key
1022
1116
  */
1023
- deleteConfig: (textLayerId, namespace, configKey) =>
1024
- this._request('DELETE', `/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`, {
1117
+ deleteConfig: (textLayerId, namespace, configKey, auditMessage) =>
1118
+ this._request('DELETE', `/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`, { auditMessage,
1025
1119
  skipResponseTransform: true,
1026
1120
  }),
1027
1121
  /**
@@ -1037,15 +1131,15 @@ class PlaidClient {
1037
1131
  * Delete a text layer.
1038
1132
  * @param {string} textLayerId - The text layer ID
1039
1133
  */
1040
- delete: (textLayerId) =>
1041
- this._request('DELETE', `/api/v1/text-layers/${textLayerId}`),
1134
+ delete: (textLayerId, auditMessage) =>
1135
+ this._request('DELETE', `/api/v1/text-layers/${textLayerId}`, { auditMessage }),
1042
1136
  /**
1043
1137
  * Update a text layer's name.
1044
1138
  * @param {string} textLayerId - The text layer ID
1045
1139
  * @param {string} name - The name
1046
1140
  */
1047
- update: (textLayerId, name) =>
1048
- this._request('PATCH', `/api/v1/text-layers/${textLayerId}`, {
1141
+ update: (textLayerId, name, auditMessage) =>
1142
+ this._request('PATCH', `/api/v1/text-layers/${textLayerId}`, { auditMessage,
1049
1143
  body: bodyOf({ name }),
1050
1144
  }),
1051
1145
  /**
@@ -1053,8 +1147,8 @@ class PlaidClient {
1053
1147
  * @param {string} textLayerId - The text layer ID
1054
1148
  * @param {string} direction - The direction ("up" or "down")
1055
1149
  */
1056
- shift: (textLayerId, direction) =>
1057
- this._request('POST', `/api/v1/text-layers/${textLayerId}/shift`, {
1150
+ shift: (textLayerId, direction, auditMessage) =>
1151
+ this._request('POST', `/api/v1/text-layers/${textLayerId}/shift`, { auditMessage,
1058
1152
  body: bodyOf({ direction }),
1059
1153
  }),
1060
1154
  /**
@@ -1062,8 +1156,8 @@ class PlaidClient {
1062
1156
  * @param {string} projectId - The project ID
1063
1157
  * @param {string} name - The name
1064
1158
  */
1065
- create: (projectId, name) =>
1066
- this._request('POST', '/api/v1/text-layers', {
1159
+ create: (projectId, name, auditMessage) =>
1160
+ this._request('POST', '/api/v1/text-layers', { auditMessage,
1067
1161
  body: bodyOf({ 'project-id': projectId, name }),
1068
1162
  }),
1069
1163
  };
@@ -1074,28 +1168,51 @@ class PlaidClient {
1074
1168
  * @param {string} id - The resource ID
1075
1169
  * @param {any} body - The request body
1076
1170
  */
1077
- setMetadata: (id, body) =>
1078
- this._request('PUT', `/api/v1/vocab-items/${id}/metadata`, {
1171
+ setMetadata: (id, body, auditMessage) =>
1172
+ this._request('PUT', `/api/v1/vocab-items/${id}/metadata`, { auditMessage,
1079
1173
  rawBody: body, skipResponseTransform: true,
1080
1174
  }),
1081
1175
  /**
1082
1176
  * Remove all metadata from a vocab item.
1083
1177
  * @param {string} id - The resource ID
1084
1178
  */
1085
- deleteMetadata: (id) =>
1086
- this._request('DELETE', `/api/v1/vocab-items/${id}/metadata`, {
1179
+ deleteMetadata: (id, auditMessage) =>
1180
+ this._request('DELETE', `/api/v1/vocab-items/${id}/metadata`, { auditMessage,
1087
1181
  skipResponseTransform: true,
1088
1182
  }),
1183
+ /**
1184
+ * Patch (shallow-merge) metadata for a vocab item. Keys present in the body are set or overwritten; keys not present are left untouched; a key whose value is null is deleted. Merging is top-level only (nested objects are replaced wholesale, not deep-merged), so a literal null cannot be stored as a value. An empty body changes no metadata.
1185
+ * @param {string} id - The resource ID
1186
+ * @param {any} body - The metadata patch
1187
+ */
1188
+ patchMetadata: (id, body, auditMessage) =>
1189
+ this._request('PATCH', `/api/v1/vocab-items/${id}/metadata`, { auditMessage,
1190
+ rawBody: body, skipResponseTransform: true,
1191
+ }),
1089
1192
  /**
1090
1193
  * Create a new vocab item
1091
1194
  * @param {string} vocabLayerId - The vocab layer ID
1092
1195
  * @param {string} form - The vocab item form
1093
1196
  * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
1094
1197
  */
1095
- create: (vocabLayerId, form, metadata) =>
1096
- this._request('POST', '/api/v1/vocab-items', {
1198
+ create: (vocabLayerId, form, metadata, auditMessage) =>
1199
+ this._request('POST', '/api/v1/vocab-items', { auditMessage,
1097
1200
  body: bodyOf({ 'vocab-layer-id': vocabLayerId, form, metadata }),
1098
1201
  }),
1202
+ /**
1203
+ * Create multiple vocab items in a single operation. Entries may target
1204
+ * different vocab layers; the user must have write access to each.
1205
+ * @param {Array<{vocabLayerId: string, form: string, metadata?: any}>} body - The vocab items to create
1206
+ */
1207
+ bulkCreate: (body, auditMessage) =>
1208
+ this._request('POST', '/api/v1/vocab-items/bulk', { auditMessage, body }),
1209
+ /**
1210
+ * Delete multiple vocab items in a single operation. Each item's
1211
+ * descendant vocab links are deleted too. Provide an array of IDs.
1212
+ * @param {string[]} body - The vocab item IDs to delete
1213
+ */
1214
+ bulkDelete: (body, auditMessage) =>
1215
+ this._request('DELETE', '/api/v1/vocab-items/bulk', { auditMessage, body }),
1099
1216
  /**
1100
1217
  * Get a vocab item by ID
1101
1218
  * @param {string} id - The resource ID
@@ -1109,15 +1226,15 @@ class PlaidClient {
1109
1226
  * Delete a vocab item
1110
1227
  * @param {string} id - The resource ID
1111
1228
  */
1112
- delete: (id) =>
1113
- this._request('DELETE', `/api/v1/vocab-items/${id}`),
1229
+ delete: (id, auditMessage) =>
1230
+ this._request('DELETE', `/api/v1/vocab-items/${id}`, { auditMessage }),
1114
1231
  /**
1115
1232
  * Update a vocab item's form
1116
1233
  * @param {string} id - The resource ID
1117
1234
  * @param {string} form - The vocab item form
1118
1235
  */
1119
- update: (id, form) =>
1120
- this._request('PATCH', `/api/v1/vocab-items/${id}`, {
1236
+ update: (id, form, auditMessage) =>
1237
+ this._request('PATCH', `/api/v1/vocab-items/${id}`, { auditMessage,
1121
1238
  body: bodyOf({ form }),
1122
1239
  }),
1123
1240
  };
@@ -1128,8 +1245,8 @@ class PlaidClient {
1128
1245
  * @param {string} relationLayerId - The relation layer ID
1129
1246
  * @param {string} direction - The direction ("up" or "down")
1130
1247
  */
1131
- shift: (relationLayerId, direction) =>
1132
- this._request('POST', `/api/v1/relation-layers/${relationLayerId}/shift`, {
1248
+ shift: (relationLayerId, direction, auditMessage) =>
1249
+ this._request('POST', `/api/v1/relation-layers/${relationLayerId}/shift`, { auditMessage,
1133
1250
  body: bodyOf({ direction }),
1134
1251
  }),
1135
1252
  /**
@@ -1137,8 +1254,8 @@ class PlaidClient {
1137
1254
  * @param {string} spanLayerId - The span layer ID
1138
1255
  * @param {string} name - The name
1139
1256
  */
1140
- create: (spanLayerId, name) =>
1141
- this._request('POST', '/api/v1/relation-layers', {
1257
+ create: (spanLayerId, name, auditMessage) =>
1258
+ this._request('POST', '/api/v1/relation-layers', { auditMessage,
1142
1259
  body: bodyOf({ 'span-layer-id': spanLayerId, name }),
1143
1260
  }),
1144
1261
  /**
@@ -1148,8 +1265,8 @@ class PlaidClient {
1148
1265
  * @param {string} configKey - The config key
1149
1266
  * @param {any} configValue - Configuration value to set
1150
1267
  */
1151
- setConfig: (relationLayerId, namespace, configKey, configValue) =>
1152
- this._request('PUT', `/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`, {
1268
+ setConfig: (relationLayerId, namespace, configKey, configValue, auditMessage) =>
1269
+ this._request('PUT', `/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`, { auditMessage,
1153
1270
  rawBody: configValue, skipResponseTransform: true,
1154
1271
  }),
1155
1272
  /**
@@ -1158,8 +1275,8 @@ class PlaidClient {
1158
1275
  * @param {string} namespace - The config namespace
1159
1276
  * @param {string} configKey - The config key
1160
1277
  */
1161
- deleteConfig: (relationLayerId, namespace, configKey) =>
1162
- this._request('DELETE', `/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`, {
1278
+ deleteConfig: (relationLayerId, namespace, configKey, auditMessage) =>
1279
+ this._request('DELETE', `/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`, { auditMessage,
1163
1280
  skipResponseTransform: true,
1164
1281
  }),
1165
1282
  /**
@@ -1175,15 +1292,15 @@ class PlaidClient {
1175
1292
  * Delete a relation layer.
1176
1293
  * @param {string} relationLayerId - The relation layer ID
1177
1294
  */
1178
- delete: (relationLayerId) =>
1179
- this._request('DELETE', `/api/v1/relation-layers/${relationLayerId}`),
1295
+ delete: (relationLayerId, auditMessage) =>
1296
+ this._request('DELETE', `/api/v1/relation-layers/${relationLayerId}`, { auditMessage }),
1180
1297
  /**
1181
1298
  * Update a relation layer's name.
1182
1299
  * @param {string} relationLayerId - The relation layer ID
1183
1300
  * @param {string} name - The name
1184
1301
  */
1185
- update: (relationLayerId, name) =>
1186
- this._request('PATCH', `/api/v1/relation-layers/${relationLayerId}`, {
1302
+ update: (relationLayerId, name, auditMessage) =>
1303
+ this._request('PATCH', `/api/v1/relation-layers/${relationLayerId}`, { auditMessage,
1187
1304
  body: bodyOf({ name }),
1188
1305
  }),
1189
1306
  };
@@ -1194,15 +1311,21 @@ class PlaidClient {
1194
1311
  * using begin and end offsets. Tokens may be zero-width and may overlap.
1195
1312
  * For tokens sharing the same begin, precedence controls the linear
1196
1313
  * ordering.
1314
+ *
1315
+ * Offsets are 0-based indices in Unicode CODE POINTS (not UTF-16 code
1316
+ * units): a supplementary-plane character (emoji, SMP script) is one
1317
+ * position. JS strings are UTF-16, so do NOT use `str.length` /
1318
+ * `str.substring` to compute offsets — count code points instead
1319
+ * (e.g. `[...str].length`, or iterate with `codePointAt`).
1197
1320
  * @param {string} tokenLayerId - The token layer ID
1198
1321
  * @param {string} text - The text ID
1199
- * @param {number} begin - Start offset (inclusive)
1200
- * @param {number} end - End offset (exclusive)
1322
+ * @param {number} begin - Start offset, inclusive (Unicode code points)
1323
+ * @param {number} end - End offset, exclusive (Unicode code points)
1201
1324
  * @param {number} [precedence] - Ordering precedence
1202
1325
  * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
1203
1326
  */
1204
- create: (tokenLayerId, text, begin, end, precedence, metadata) =>
1205
- this._request('POST', '/api/v1/tokens', {
1327
+ create: (tokenLayerId, text, begin, end, precedence, metadata, auditMessage) =>
1328
+ this._request('POST', '/api/v1/tokens', { auditMessage,
1206
1329
  body: bodyOf({ 'token-layer-id': tokenLayerId, text, begin, end, precedence, metadata }),
1207
1330
  }),
1208
1331
  /**
@@ -1219,42 +1342,43 @@ class PlaidClient {
1219
1342
  * have no remaining tokens, the span will also be deleted.
1220
1343
  * @param {string} tokenId - The token ID
1221
1344
  */
1222
- delete: (tokenId) =>
1223
- this._request('DELETE', `/api/v1/tokens/${tokenId}`),
1345
+ delete: (tokenId, auditMessage) =>
1346
+ this._request('DELETE', `/api/v1/tokens/${tokenId}`, { auditMessage }),
1224
1347
  /**
1225
1348
  * Update a token.
1226
1349
  * @param {string} tokenId - The token ID
1227
- * @param {number} [begin] - New start offset
1228
- * @param {number} [end] - New end offset
1350
+ * @param {number} [begin] - New start offset, inclusive (Unicode code points)
1351
+ * @param {number} [end] - New end offset, exclusive (Unicode code points)
1229
1352
  * @param {?number} [precedence] - Ordering precedence. Omit (undefined)
1230
1353
  * to leave unchanged; pass a number to set; pass null explicitly to
1231
1354
  * CLEAR it (revert to no explicit ordering). bodyOf keeps null but
1232
1355
  * drops undefined, so the three cases map correctly to the server.
1233
1356
  */
1234
- update: (tokenId, begin, end, precedence) =>
1235
- this._request('PATCH', `/api/v1/tokens/${tokenId}`, {
1357
+ update: (tokenId, begin, end, precedence, auditMessage) =>
1358
+ this._request('PATCH', `/api/v1/tokens/${tokenId}`, { auditMessage,
1236
1359
  body: bodyOf({ begin, end, precedence }),
1237
1360
  }),
1238
1361
  /**
1239
1362
  * Create multiple tokens in a single operation.
1240
1363
  * @param {Array} body - The request body
1364
+ * @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
1241
1365
  */
1242
- bulkCreate: (body) =>
1243
- this._request('POST', '/api/v1/tokens/bulk', { body }),
1366
+ bulkCreate: (body, auditMessage) =>
1367
+ this._request('POST', '/api/v1/tokens/bulk', { auditMessage, body }),
1244
1368
  /**
1245
1369
  * Delete multiple tokens in a single operation. Provide an array of IDs.
1246
1370
  * @param {Array} body - The request body
1247
1371
  */
1248
- bulkDelete: (body) =>
1249
- this._request('DELETE', '/api/v1/tokens/bulk', { body }),
1372
+ bulkDelete: (body, auditMessage) =>
1373
+ this._request('DELETE', '/api/v1/tokens/bulk', { auditMessage, body }),
1250
1374
  /**
1251
- * Split a token at a character offset. The original token becomes the left half
1252
- * (keeps its ID, spans, vocab-links); the new right token's ID is returned.
1375
+ * Split a token at a Unicode code-point offset. The original token becomes the
1376
+ * left half (keeps its ID, spans, vocab-links); the new right token's ID is returned.
1253
1377
  * @param {string} tokenId - The token ID
1254
- * @param {number} position - Offset to split at (strictly between begin and end)
1378
+ * @param {number} position - Code-point offset to split at (strictly between begin and end)
1255
1379
  */
1256
- split: (tokenId, position) =>
1257
- this._request('POST', `/api/v1/tokens/${tokenId}/split`, {
1380
+ split: (tokenId, position, auditMessage) =>
1381
+ this._request('POST', `/api/v1/tokens/${tokenId}/split`, { auditMessage,
1258
1382
  body: bodyOf({ position }),
1259
1383
  }),
1260
1384
  /**
@@ -1265,8 +1389,8 @@ class PlaidClient {
1265
1389
  * @param {string} tokenId - The anchor token ID
1266
1390
  * @param {string} otherTokenId - The other token to merge in
1267
1391
  */
1268
- merge: (tokenId, otherTokenId) =>
1269
- this._request('POST', `/api/v1/tokens/${tokenId}/merge`, {
1392
+ merge: (tokenId, otherTokenId, auditMessage) =>
1393
+ this._request('POST', `/api/v1/tokens/${tokenId}/merge`, { auditMessage,
1270
1394
  body: bodyOf({ 'other-token-id': otherTokenId }),
1271
1395
  }),
1272
1396
  /**
@@ -1274,11 +1398,11 @@ class PlaidClient {
1274
1398
  * auto-adjusted to preserve the partition; on non-overlapping layers a shift that
1275
1399
  * would create an overlap is rejected.
1276
1400
  * @param {string} tokenId - The token ID
1277
- * @param {number} [begin] - New start offset
1278
- * @param {number} [end] - New end offset
1401
+ * @param {number} [begin] - New start offset, inclusive (Unicode code points)
1402
+ * @param {number} [end] - New end offset, exclusive (Unicode code points)
1279
1403
  */
1280
- shift: (tokenId, begin, end) =>
1281
- this._request('POST', `/api/v1/tokens/${tokenId}/shift`, {
1404
+ shift: (tokenId, begin, end, auditMessage) =>
1405
+ this._request('POST', `/api/v1/tokens/${tokenId}/shift`, { auditMessage,
1282
1406
  body: bodyOf({ begin, end }),
1283
1407
  }),
1284
1408
  /**
@@ -1286,18 +1410,27 @@ class PlaidClient {
1286
1410
  * @param {string} tokenId - The token ID
1287
1411
  * @param {any} body - The request body
1288
1412
  */
1289
- setMetadata: (tokenId, body) =>
1290
- this._request('PUT', `/api/v1/tokens/${tokenId}/metadata`, {
1413
+ setMetadata: (tokenId, body, auditMessage) =>
1414
+ this._request('PUT', `/api/v1/tokens/${tokenId}/metadata`, { auditMessage,
1291
1415
  rawBody: body, skipResponseTransform: true,
1292
1416
  }),
1293
1417
  /**
1294
1418
  * Remove all metadata from a token.
1295
1419
  * @param {string} tokenId - The token ID
1296
1420
  */
1297
- deleteMetadata: (tokenId) =>
1298
- this._request('DELETE', `/api/v1/tokens/${tokenId}/metadata`, {
1421
+ deleteMetadata: (tokenId, auditMessage) =>
1422
+ this._request('DELETE', `/api/v1/tokens/${tokenId}/metadata`, { auditMessage,
1299
1423
  skipResponseTransform: true,
1300
1424
  }),
1425
+ /**
1426
+ * Patch (shallow-merge) metadata for a token. Keys present in the body are set or overwritten; keys not present are left untouched; a key whose value is null is deleted. Merging is top-level only (nested objects are replaced wholesale, not deep-merged), so a literal null cannot be stored as a value. An empty body changes no metadata.
1427
+ * @param {string} tokenId - The token ID
1428
+ * @param {any} body - The metadata patch
1429
+ */
1430
+ patchMetadata: (tokenId, body, auditMessage) =>
1431
+ this._request('PATCH', `/api/v1/tokens/${tokenId}/metadata`, { auditMessage,
1432
+ rawBody: body, skipResponseTransform: true,
1433
+ }),
1301
1434
  };
1302
1435
 
1303
1436
  this.messages = {
@@ -1317,19 +1450,30 @@ class PlaidClient {
1317
1450
  * @param {any} data - The message data to send
1318
1451
  * @returns {Promise<any>} Response from the send operation
1319
1452
  */
1320
- sendMessage: (projectId, data) =>
1321
- this._request('POST', `/api/v1/projects/${projectId}/message`, {
1453
+ sendMessage: (projectId, data, auditMessage) =>
1454
+ this._request('POST', `/api/v1/projects/${projectId}/message`, { auditMessage,
1322
1455
  body: { body: data },
1323
1456
  }),
1324
1457
 
1325
1458
  /**
1326
- * Discover the services currently connected to a project (synchronous GET).
1459
+ * Discover the services seen on a project (synchronous GET). Currently
1460
+ * connected services carry `online: true`; previously-seen offline ones
1461
+ * carry `online: false` plus a `lastSeenAt` stamp.
1327
1462
  * @param {string} projectId - The UUID of the project to query
1328
- * @param {number} [timeout] - Ignored; kept for back-compat
1329
1463
  * @returns {Promise<Array>} Array of discovered service information
1330
1464
  */
1331
- discoverServices: (projectId, timeout) =>
1332
- discoverServices(this, projectId, timeout),
1465
+ discoverServices: (projectId) =>
1466
+ discoverServices(this, projectId),
1467
+
1468
+ /**
1469
+ * Forget a previously-seen (offline) service. Maintainer-only; 409 if
1470
+ * the service is currently connected.
1471
+ * @param {string} projectId - The UUID of the project
1472
+ * @param {string} serviceId - The ID of the service to forget
1473
+ * @returns {Promise<void>}
1474
+ */
1475
+ discardService: (projectId, serviceId) =>
1476
+ discardService(this, projectId, serviceId),
1333
1477
 
1334
1478
  /**
1335
1479
  * Register as a service and handle incoming work requests.
@@ -1366,8 +1510,8 @@ class PlaidClient {
1366
1510
  * await client.query({
1367
1511
  * find: ['?s1', '?s2'],
1368
1512
  * where: [
1369
- * ['span', '?s1', { layer: 'pos', value: 'NOUN' }],
1370
- * ['span', '?s2', { layer: 'pos', value: 'VERB' }],
1513
+ * ['span', '?s1', { layer: posLayerId, value: 'NOUN' }],
1514
+ * ['span', '?s2', { layer: posLayerId, value: 'VERB' }],
1371
1515
  * ['covers', '?s1', '?t1'], ['covers', '?s2', '?t2'],
1372
1516
  * ['precedes', '?t1', '?t2'],
1373
1517
  * ],
@@ -1375,13 +1519,23 @@ class PlaidClient {
1375
1519
  * limit: 100,
1376
1520
  * });
1377
1521
  *
1378
- * @param {Object} body - The query AST ({find, where, scope?, limit?, return?}).
1522
+ * A `layer` is referenced by its id (its UUID) only — not by name or
1523
+ * path. To match a layer by name, bind it with a `*-layer` clause (e.g.
1524
+ * `['span-layer', '?sl', { name: 'pos' }]`) and use the variable.
1525
+ *
1526
+ * Optional keys: `scope` (restrict to projects by id, `{projectIds}`), `orderBy`
1527
+ * (sort rows), and `bindings` (substitute `?name` placeholders with literals).
1528
+ * `return` may also be an aggregate spec `{group, aggregates}`. See the query
1529
+ * language reference.
1530
+ *
1531
+ * @param {Object} body - The query AST ({find, where, scope?, limit?, orderBy?,
1532
+ * return?, bindings?}).
1379
1533
  * @returns {Promise<Object>} For 'ids'/'entities': {columns, results, count, truncated}.
1380
1534
  * For 'count': {return: 'count', count}. Entity cells are full entity objects
1381
1535
  * (same shape as the GET endpoints).
1382
1536
  */
1383
- this.query = (body) =>
1384
- this._request('POST', '/api/v1/query', { body });
1537
+ this.query = (body, auditMessage) =>
1538
+ this._request('POST', '/api/v1/query', { auditMessage, body });
1385
1539
  }
1386
1540
 
1387
1541
  // --- Core methods ---
@@ -1404,10 +1558,62 @@ class PlaidClient {
1404
1558
  this.strictModeDocumentId = null;
1405
1559
  }
1406
1560
 
1561
+ /**
1562
+ * Set a custom audit-log message applied to every subsequent write request,
1563
+ * OVERRIDING the server's auto-generated description (e.g. "Patch metadata
1564
+ * on span X"). The message may template the endpoint's own path/query/body
1565
+ * params with `{param}` placeholders, resolved server-side — e.g.
1566
+ * `"Approve span {spanId}"`. Placeholder names are case/separator-insensitive
1567
+ * (`{spanId}` == `{span-id}` == `{span_id}`), so use the camelCase names this
1568
+ * client uses. Applies to GET-less requests
1569
+ * only, including every operation queued in a batch.
1570
+ * @param {string} message - The custom message (template).
1571
+ */
1572
+ setAuditMessage(message) {
1573
+ this.auditMessage = message;
1574
+ return this;
1575
+ }
1576
+
1577
+ /** Clear the ambient custom audit-log message (revert to auto-generated). */
1578
+ clearAuditMessage() {
1579
+ this.auditMessage = null;
1580
+ return this;
1581
+ }
1582
+
1583
+ /**
1584
+ * Run `fn` with a custom audit-log message in effect, restoring the previous
1585
+ * message afterward (supports nesting). Use it to scope ONE call (per-call
1586
+ * precision) or MANY (a logical unit):
1587
+ *
1588
+ * await client.withAuditMessage('Approve span {spanId}', () =>
1589
+ * client.spans.update(spanId, ...));
1590
+ *
1591
+ * await client.withAuditMessage('Import sentence', async () => {
1592
+ * await client.tokens.create(...);
1593
+ * await client.spans.create(...);
1594
+ * });
1595
+ *
1596
+ * @param {string} message - The custom message (template).
1597
+ * @param {() => (Promise<any>|any)} fn - The work to run with the message set.
1598
+ * @returns {Promise<any>} Whatever `fn` resolves to.
1599
+ */
1600
+ async withAuditMessage(message, fn) {
1601
+ const prev = this.auditMessage;
1602
+ this.auditMessage = message;
1603
+ try {
1604
+ return await fn();
1605
+ } finally {
1606
+ this.auditMessage = prev;
1607
+ }
1608
+ }
1609
+
1407
1610
  /** Begin a batch of operations. Subsequent API calls will be queued. */
1408
1611
  beginBatch() {
1409
1612
  this.isBatching = true;
1410
1613
  this.batchOperations = [];
1614
+ // Strict mode stamps the expected document-version on the FIRST write of
1615
+ // the batch only (see _request) — reset the marker per batch.
1616
+ this.batchVersionStamped = false;
1411
1617
  }
1412
1618
 
1413
1619
  /**
@@ -1492,6 +1698,38 @@ class PlaidClient {
1492
1698
  return this.isBatching;
1493
1699
  }
1494
1700
 
1701
+ /**
1702
+ * Run `fn` with a batch open, then submit all queued ops as ONE atomic
1703
+ * request — or abort the batch if `fn` throws, so a half-open batch can never
1704
+ * silently swallow later non-batch calls. `fn` makes the (queued) client
1705
+ * calls; it must NOT call submitBatch itself. Resolves to the batch results
1706
+ * array (`[]` if `fn` queued nothing).
1707
+ *
1708
+ * const [sentRes, wordRes] = await client.batched(async () => {
1709
+ * client.tokens.bulkCreate(sentenceOps);
1710
+ * client.tokens.bulkCreate(wordOps);
1711
+ * });
1712
+ *
1713
+ * Server-side a batch runs sequentially in one transaction (a child op sees
1714
+ * parents created earlier in the same `fn`; any op's failure rolls the whole
1715
+ * batch back). Not nestable. Named `batched()` because `client.batch` is the
1716
+ * low-level batch resource.
1717
+ * @param {() => (void | Promise<void>)} fn
1718
+ * @returns {Promise<Array>}
1719
+ */
1720
+ async batched(fn) {
1721
+ this.beginBatch();
1722
+ try {
1723
+ await fn();
1724
+ } catch (e) {
1725
+ // fn failed: drop the queued ops so the client leaves batch mode and
1726
+ // later plain calls don't queue into a never-submitted batch.
1727
+ if (this.isBatchMode()) this.abortBatch();
1728
+ throw e;
1729
+ }
1730
+ return this.submitBatch();
1731
+ }
1732
+
1495
1733
  /**
1496
1734
  * Authenticate and return a new client instance with token. This is the
1497
1735
  * single auth entry point — there is no `client.login` resource.
@@ -1530,3 +1768,34 @@ class PlaidClient {
1530
1768
 
1531
1769
  export default PlaidClient;
1532
1770
  export { PlaidClient };
1771
+
1772
+ // Unicode code-point helpers for text offsets (token begin/end are code-point
1773
+ // indices). See ./codepoint.js.
1774
+ export { cpLength, cpSlice, cpSlicer, utf16ToCp, cpToUtf16, cpIndexOf } from './codepoint.js';
1775
+ export { PLAID_NAMESPACE, ROLE_KEY, ROLES, readRole, findByRole } from './roles.js';
1776
+ // Service self-description helpers: filter discovered services by task, read a
1777
+ // service's parameter schema/summary, and build/coerce form values. See
1778
+ // ./serviceSchema.js.
1779
+ export {
1780
+ TASKS,
1781
+ servesTask,
1782
+ filterServicesByTask,
1783
+ getParamSchema,
1784
+ getServiceSummary,
1785
+ buildDefaultValues,
1786
+ coerceParamValues,
1787
+ } from './serviceSchema.js';
1788
+ // Provenance: the cross-app convention for machine-provided vs human-labeled
1789
+ // information (flat prov/provSource/provConfirmed metadata; absence = human),
1790
+ // plus the machine-writer contract. See ./provenance.js and the manual,
1791
+ // "Provenance".
1792
+ export {
1793
+ PROV,
1794
+ PROV_STATES,
1795
+ stampInferred,
1796
+ confirmedInferred,
1797
+ provState,
1798
+ isProtected,
1799
+ verifyOnEdit,
1800
+ serviceSource,
1801
+ } from './provenance.js';