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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +39 -0
  2. package/package.json +13 -6
  3. package/src/apiAnnotations.d.ts +508 -0
  4. package/src/apiAnnotations.js +543 -0
  5. package/src/apiDocuments.d.ts +569 -0
  6. package/src/apiDocuments.js +551 -0
  7. package/src/apiLayers.d.ts +369 -0
  8. package/src/apiLayers.js +222 -0
  9. package/src/apiMessages.d.ts +293 -0
  10. package/src/apiMessages.js +265 -0
  11. package/src/apiPeople.d.ts +626 -0
  12. package/src/apiPeople.js +698 -0
  13. package/src/apiProject.d.ts +761 -0
  14. package/src/apiProject.js +774 -0
  15. package/src/apiServer.d.ts +455 -0
  16. package/src/apiServer.js +422 -0
  17. package/src/apiVocab.d.ts +456 -0
  18. package/src/apiVocab.js +425 -0
  19. package/src/batch.d.ts +54 -0
  20. package/src/batch.js +323 -0
  21. package/src/batchRef.d.ts +30 -0
  22. package/src/batchRef.js +128 -0
  23. package/src/client.d.ts +1321 -0
  24. package/src/client.js +743 -0
  25. package/src/codepoint.d.ts +94 -0
  26. package/src/codepoint.js +131 -1
  27. package/src/constraints.d.ts +85 -0
  28. package/src/constraints.js +112 -0
  29. package/src/created.d.ts +2 -0
  30. package/src/created.js +29 -0
  31. package/src/documentLock.d.ts +172 -0
  32. package/src/documentLock.js +384 -0
  33. package/src/entryForms.d.ts +63 -0
  34. package/src/entryForms.js +152 -0
  35. package/src/events.d.ts +60 -0
  36. package/src/events.js +271 -0
  37. package/src/families.d.ts +349 -0
  38. package/src/families.js +396 -0
  39. package/src/http.d.ts +233 -0
  40. package/src/http.js +809 -93
  41. package/src/ids.d.ts +26 -0
  42. package/src/ids.js +96 -0
  43. package/src/index.d.ts +101 -0
  44. package/src/index.js +183 -2105
  45. package/src/metadataOps.d.ts +15 -0
  46. package/src/metadataOps.js +124 -0
  47. package/src/operations.d.ts +36 -0
  48. package/src/operations.js +164 -0
  49. package/src/pagination.d.ts +49 -0
  50. package/src/pagination.js +20 -53
  51. package/src/provenance.d.ts +243 -0
  52. package/src/provenance.js +458 -36
  53. package/src/replayed.d.ts +3 -0
  54. package/src/replayed.js +31 -0
  55. package/src/requester.d.ts +60 -0
  56. package/src/requester.js +74 -0
  57. package/src/roles.d.ts +104 -0
  58. package/src/roles.js +64 -2
  59. package/src/serviceSchema.d.ts +337 -0
  60. package/src/serviceSchema.js +177 -60
  61. package/src/services.d.ts +319 -0
  62. package/src/services.js +742 -58
  63. package/src/sse.d.ts +30 -0
  64. package/src/sse.js +80 -3
  65. package/src/testing.d.ts +128 -0
  66. package/src/testing.js +379 -0
  67. package/src/textEdits.d.ts +59 -0
  68. package/src/textEdits.js +234 -0
  69. package/src/transforms.d.ts +25 -0
  70. package/src/transforms.js +11 -1
  71. package/src/types.d.ts +867 -0
  72. package/src/types.js +663 -0
  73. package/index.d.ts +0 -532
package/src/index.js CHANGED
@@ -2,2112 +2,131 @@
2
2
  * plaid-client - JavaScript client for the Plaid annotation API
3
3
  */
4
4
 
5
- import { transformRequest, transformResponse } from './transforms.js';
6
- import {
7
- makeRequest, extractDocumentVersions, parseErrorBody, makeHttpError,
8
- makeNetworkError, timeoutSignal, DEFAULT_TIMEOUT_MS,
9
- } from './http.js';
10
- import { listAll, listPage, iterPages } from './pagination.js';
11
- import { createSSEConnection } from './sse.js';
12
- import {
13
- discoverServices,
14
- discardService,
15
- serve,
16
- requestService,
17
- } from './services.js';
5
+ // ---- Types --------------------------------------------------------------
6
+ // The shapes the API answers with and takes are in types.js, named here so
7
+ // this module's JSDoc can use them and the package root exports them.
18
8
 
19
- // Helper: normalize a document-read `layers` filter to the wire's
20
- // comma-separated form. Accepts an array of layer ids or a ready-made string;
21
- // anything empty becomes undefined so no `?layers=` is sent at all.
22
- function layersParam(layers) {
23
- if (layers === undefined || layers === null) return undefined;
24
- const joined = Array.isArray(layers) ? layers.join(',') : String(layers);
25
- return joined.length > 0 ? joined : undefined;
26
- }
27
-
28
- // Helper: normalize an audit `opTypes` filter to the wire's comma-separated
29
- // form. Accepts an array of op types or a ready-made string; anything empty
30
- // becomes undefined so no `?op-types=` is sent at all.
31
- function opTypesParam(opTypes) {
32
- if (opTypes === undefined || opTypes === null) return undefined;
33
- const joined = Array.isArray(opTypes) ? opTypes.join(',') : String(opTypes);
34
- return joined.length > 0 ? joined : undefined;
35
- }
36
-
37
- // Helper: build body object, filtering out undefined values
38
- function bodyOf(obj) {
39
- const result = {};
40
- for (const [key, value] of Object.entries(obj)) {
41
- if (value !== undefined) result[key] = value;
42
- }
43
- return result;
44
- }
45
-
46
- // POST to an endpoint that takes no Authorization header (login, invite lookup
47
- // and redemption). Deliberately does not go through PlaidClient._request: these
48
- // are called before any client exists, which is the whole point.
49
- async function anonymousPost(baseUrl, path, body, options = {}) {
50
- const base = baseUrl.replace(/\/$/, '');
51
- const url = `${base}${path}`;
52
- try {
53
- const fetchOptions = {
54
- method: 'POST',
55
- headers: { 'Content-Type': 'application/json' },
56
- body: JSON.stringify(body),
57
- };
58
- const signal = timeoutSignal(options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS);
59
- if (signal) fetchOptions.signal = signal;
60
-
61
- const response = await fetch(url, fetchOptions);
62
- if (!response.ok) {
63
- throw makeHttpError(response, await parseErrorBody(response), url, 'POST');
64
- }
65
- return transformResponse(await response.json());
66
- } catch (error) {
67
- if (error.status) throw error;
68
- throw makeNetworkError(error, url, 'POST');
69
- }
70
- }
71
-
72
- class PlaidClient {
73
- /**
74
- * Create a new PlaidClient instance
75
- * @param {string} baseUrl - The base URL for the API
76
- * @param {string} token - The authentication token
77
- * @param {object} [options] - Client options
78
- * @param {number} [options.timeout=30000] - Per-request timeout in ms (0 or null disables it)
79
- */
80
- constructor(baseUrl, token, options = {}) {
81
- this.baseUrl = baseUrl.replace(/\/$/, '');
82
- this.token = token;
83
- this.timeout = options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS;
84
- this.isBatching = false;
85
- this.batchOperations = [];
86
- this.documentVersions = {};
87
- this.strictModeDocumentId = null;
88
- // The open logical operation (audit-log group), or null. While set, every
89
- // write is stamped with `?group-id=` (+ `group-message`) so the audit log
90
- // folds them into ONE expandable entry. See beginOperation / withOperation.
91
- // Shape: { id, message, depth, written, refined }.
92
- this.operationGroup = null;
93
- // Optional callback fired once (per client) when any request returns HTTP
94
- // 401 — i.e. the token is missing/expired/invalid. Apps use it to discard
95
- // the stored token and route back to login. See makeRequest in http.js.
96
- this.onAuthError = options.onAuthError || null;
97
- this._authErrorFired = false;
98
-
99
- // --- API Bundles ---
100
-
101
- this.vocabLinks = {
102
- /**
103
- * Create a new vocab link between tokens and a vocab item.
104
- * @param {string} vocabItem - The vocab item to link
105
- * @param {Array} tokens - The tokens to link
106
- * @param {any} [metadata] - Metadata for the link. Omit to leave unset; pass null to send JSON null.
107
- */
108
- create: (vocabItem, tokens, metadata, auditMessage) =>
109
- this._request('POST', '/api/v1/vocab-links', { auditMessage,
110
- body: bodyOf({ 'vocab-item': vocabItem, tokens, metadata }),
111
- }),
112
- /**
113
- * Create multiple vocab links in a single operation. Entries may
114
- * reference different vocab items, but all tokens across the call must
115
- * belong to one document.
116
- * @param {Array<{vocabItem: string, tokens: string[], metadata?: any}>} body - The vocab links to create
117
- * @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
118
- */
119
- bulkCreate: (body, auditMessage) =>
120
- this._request('POST', '/api/v1/vocab-links/bulk', { auditMessage, body }),
121
- /**
122
- * Delete multiple vocab links in a single operation. Provide an array of IDs.
123
- * @param {string[]} body - The vocab link IDs to delete
124
- */
125
- bulkDelete: (body, auditMessage) =>
126
- this._request('DELETE', '/api/v1/vocab-links/bulk', { auditMessage, body }),
127
- /**
128
- * Replace all metadata for a vocab link. The entire metadata map is replaced - existing metadata keys not included in the request will be removed.
129
- * @param {string} id - The resource ID
130
- * @param {any} body - The request body
131
- */
132
- setMetadata: (id, body, auditMessage) =>
133
- this._request('PUT', `/api/v1/vocab-links/${id}/metadata`, { auditMessage,
134
- rawBody: body, skipResponseTransform: true,
135
- }),
136
- /**
137
- * Remove all metadata from a vocab link.
138
- * @param {string} id - The resource ID
139
- */
140
- deleteMetadata: (id, auditMessage) =>
141
- this._request('DELETE', `/api/v1/vocab-links/${id}/metadata`, { auditMessage,
142
- skipResponseTransform: true,
143
- }),
144
- /**
145
- * 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.
146
- * @param {string} id - The resource ID
147
- * @param {any} body - The metadata patch
148
- */
149
- patchMetadata: (id, body, auditMessage) =>
150
- this._request('PATCH', `/api/v1/vocab-links/${id}/metadata`, { auditMessage,
151
- rawBody: body, skipResponseTransform: true,
152
- }),
153
- /**
154
- * Get a vocab link by ID
155
- * @param {string} id - The resource ID
156
- * @param {string} [asOf] - Temporal query timestamp
157
- */
158
- get: (id, asOf) =>
159
- this._request('GET', `/api/v1/vocab-links/${id}`, {
160
- queryParams: { 'as-of': asOf },
161
- }),
162
- /**
163
- * Delete a vocab link
164
- * @param {string} id - The resource ID
165
- */
166
- delete: (id, auditMessage) =>
167
- this._request('DELETE', `/api/v1/vocab-links/${id}`, { auditMessage }),
168
- };
169
-
170
- this.vocabLayers = {
171
- /**
172
- * Get a vocab layer by ID
173
- * @param {string} id - The resource ID
174
- * @param {boolean} [includeItems] - Include vocab items
175
- * @param {string} [asOf] - Temporal query timestamp
176
- */
177
- get: (id, includeItems, asOf) =>
178
- this._request('GET', `/api/v1/vocab-layers/${id}`, {
179
- queryParams: { 'include-items': includeItems, 'as-of': asOf },
180
- }),
181
- /**
182
- * Delete a vocab layer.
183
- * @param {string} id - The resource ID
184
- */
185
- delete: (id, auditMessage) =>
186
- this._request('DELETE', `/api/v1/vocab-layers/${id}`, { auditMessage }),
187
- /**
188
- * Update a vocab layer's name.
189
- * @param {string} id - The resource ID
190
- * @param {string} name - The name
191
- */
192
- update: (id, name, auditMessage) =>
193
- this._request('PATCH', `/api/v1/vocab-layers/${id}`, { auditMessage,
194
- body: bodyOf({ name }),
195
- }),
196
- /**
197
- * Set a configuration value for a layer in an editor namespace.
198
- * @param {string} id - The resource ID
199
- * @param {string} namespace - The config namespace
200
- * @param {string} configKey - The config key
201
- * @param {any} configValue - Configuration value to set
202
- */
203
- setConfig: (id, namespace, configKey, configValue, auditMessage) =>
204
- this._request('PUT', `/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`, { auditMessage,
205
- rawBody: configValue, skipResponseTransform: true,
206
- }),
207
- /**
208
- * Remove a configuration value for a layer.
209
- * @param {string} id - The resource ID
210
- * @param {string} namespace - The config namespace
211
- * @param {string} configKey - The config key
212
- */
213
- deleteConfig: (id, namespace, configKey, auditMessage) =>
214
- this._request('DELETE', `/api/v1/vocab-layers/${id}/config/${namespace}/${configKey}`, { auditMessage,
215
- skipResponseTransform: true,
216
- }),
217
- /**
218
- * List all vocab layers accessible to user. Transparently follows
219
- * pagination cursors and returns the full flat array.
220
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
221
- * @param {string} [asOf] - Temporal query timestamp
222
- */
223
- list: (asOf) =>
224
- listAll(this, '/api/v1/vocab-layers', { query: { 'as-of': asOf } }),
225
- /**
226
- * Fetch a single page of vocab layers.
227
- * @param {object} [opts]
228
- * @param {number} [opts.limit] - Page size (1..1000; server default 100)
229
- * @param {string} [opts.cursor] - Opaque cursor from a previous page
230
- * @param {string} [opts.asOf] - Temporal query timestamp
231
- * @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
232
- */
233
- listPage: ({ limit, cursor, asOf } = {}) =>
234
- listPage(this, '/api/v1/vocab-layers', { limit, cursor, query: { 'as-of': asOf } }),
235
- /**
236
- * Async-iterate vocab layers page by page; yields each page's entries array.
237
- * @param {object} [opts]
238
- * @param {number} [opts.pageSize] - Per-request page size
239
- * @param {string} [opts.asOf] - Temporal query timestamp
240
- * 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.
241
- * @returns {AsyncGenerator<Array>}
242
- */
243
- iterPages: ({ pageSize, asOf } = {}) =>
244
- iterPages(this, '/api/v1/vocab-layers', { pageSize, query: { 'as-of': asOf } }),
245
- /**
246
- * Create a new vocab layer. Note: this also registers the user as a maintainer.
247
- * @param {string} name - The name
248
- */
249
- create: (name, auditMessage) =>
250
- this._request('POST', '/api/v1/vocab-layers', { auditMessage,
251
- body: bodyOf({ name }),
252
- }),
253
- /**
254
- * Assign a user as a maintainer for this vocab layer.
255
- * @param {string} id - The resource ID
256
- * @param {string} userId - The user ID
257
- */
258
- addMaintainer: (id, userId, auditMessage) =>
259
- this._request('POST', `/api/v1/vocab-layers/${id}/maintainers/${userId}`, { auditMessage }),
260
- /**
261
- * Remove a user's maintainer privileges for this vocab layer.
262
- * @param {string} id - The resource ID
263
- * @param {string} userId - The user ID
264
- */
265
- removeMaintainer: (id, userId, auditMessage) =>
266
- this._request('DELETE', `/api/v1/vocab-layers/${id}/maintainers/${userId}`, { auditMessage }),
267
- };
268
-
269
- this.relations = {
270
- /**
271
- * Replace all metadata for a relation.
272
- * @param {string} relationId - The relation ID
273
- * @param {any} body - The request body
274
- */
275
- setMetadata: (relationId, body, auditMessage) =>
276
- this._request('PUT', `/api/v1/relations/${relationId}/metadata`, { auditMessage,
277
- rawBody: body, skipResponseTransform: true,
278
- }),
279
- /**
280
- * Remove all metadata from a relation.
281
- * @param {string} relationId - The relation ID
282
- */
283
- deleteMetadata: (relationId, auditMessage) =>
284
- this._request('DELETE', `/api/v1/relations/${relationId}/metadata`, { auditMessage,
285
- skipResponseTransform: true,
286
- }),
287
- /**
288
- * 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.
289
- * @param {string} relationId - The relation ID
290
- * @param {any} body - The metadata patch
291
- */
292
- patchMetadata: (relationId, body, auditMessage) =>
293
- this._request('PATCH', `/api/v1/relations/${relationId}/metadata`, { auditMessage,
294
- rawBody: body, skipResponseTransform: true,
295
- }),
296
- /**
297
- * Update the target span of a relation.
298
- * @param {string} relationId - The relation ID
299
- * @param {string} spanId - The span ID
300
- */
301
- setTarget: (relationId, spanId, auditMessage) =>
302
- this._request('PUT', `/api/v1/relations/${relationId}/target`, { auditMessage,
303
- body: bodyOf({ 'span-id': spanId }),
304
- }),
305
- /**
306
- * Get a relation by ID.
307
- * @param {string} relationId - The relation ID
308
- * @param {string} [asOf] - Temporal query timestamp
309
- */
310
- get: (relationId, asOf) =>
311
- this._request('GET', `/api/v1/relations/${relationId}`, {
312
- queryParams: { 'as-of': asOf },
313
- }),
314
- /**
315
- * Delete a relation.
316
- * @param {string} relationId - The relation ID
317
- */
318
- delete: (relationId, auditMessage) =>
319
- this._request('DELETE', `/api/v1/relations/${relationId}`, { auditMessage }),
320
- /**
321
- * Update a relation's value.
322
- * @param {string} relationId - The relation ID
323
- * @param {any} value - The value
324
- */
325
- update: (relationId, value, auditMessage) =>
326
- this._request('PATCH', `/api/v1/relations/${relationId}`, { auditMessage,
327
- body: bodyOf({ value }),
328
- }),
329
- /**
330
- * Update the source span of a relation.
331
- * @param {string} relationId - The relation ID
332
- * @param {string} spanId - The span ID
333
- */
334
- setSource: (relationId, spanId, auditMessage) =>
335
- this._request('PUT', `/api/v1/relations/${relationId}/source`, { auditMessage,
336
- body: bodyOf({ 'span-id': spanId }),
337
- }),
338
- /**
339
- * Create a new relation. A relation is a directed edge between two spans
340
- * with a value, useful for expressing phenomena such as syntactic or
341
- * semantic relations.
342
- * @param {string} layerId - The relation layer ID
343
- * @param {string} sourceId - The source span ID
344
- * @param {string} targetId - The target span ID
345
- * @param {any} value - The value
346
- * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
347
- */
348
- create: (layerId, sourceId, targetId, value, metadata, auditMessage) =>
349
- this._request('POST', '/api/v1/relations', { auditMessage,
350
- body: bodyOf({ 'layer-id': layerId, 'source-id': sourceId, 'target-id': targetId, value, metadata }),
351
- }),
352
- /**
353
- * Create multiple relations in a single operation.
354
- * @param {Array} body - The request body
355
- * @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
356
- */
357
- bulkCreate: (body, auditMessage) =>
358
- this._request('POST', '/api/v1/relations/bulk', { auditMessage, body }),
359
- /**
360
- * Delete multiple relations in a single operation. Provide an array of IDs.
361
- * @param {Array} body - The request body
362
- */
363
- bulkDelete: (body, auditMessage) =>
364
- this._request('DELETE', '/api/v1/relations/bulk', { auditMessage, body }),
365
- };
366
-
367
- this.spanLayers = {
368
- /**
369
- * Set a configuration value for a layer in an editor namespace.
370
- * @param {string} spanLayerId - The span layer ID
371
- * @param {string} namespace - The config namespace
372
- * @param {string} configKey - The config key
373
- * @param {any} configValue - Configuration value to set
374
- */
375
- setConfig: (spanLayerId, namespace, configKey, configValue, auditMessage) =>
376
- this._request('PUT', `/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`, { auditMessage,
377
- rawBody: configValue, skipResponseTransform: true,
378
- }),
379
- /**
380
- * Remove a configuration value for a layer.
381
- * @param {string} spanLayerId - The span layer ID
382
- * @param {string} namespace - The config namespace
383
- * @param {string} configKey - The config key
384
- */
385
- deleteConfig: (spanLayerId, namespace, configKey, auditMessage) =>
386
- this._request('DELETE', `/api/v1/span-layers/${spanLayerId}/config/${namespace}/${configKey}`, { auditMessage,
387
- skipResponseTransform: true,
388
- }),
389
- /**
390
- * Get a span layer by ID.
391
- * @param {string} spanLayerId - The span layer ID
392
- * @param {string} [asOf] - Temporal query timestamp
393
- */
394
- get: (spanLayerId, asOf) =>
395
- this._request('GET', `/api/v1/span-layers/${spanLayerId}`, {
396
- queryParams: { 'as-of': asOf },
397
- }),
398
- /**
399
- * Delete a span layer.
400
- * @param {string} spanLayerId - The span layer ID
401
- */
402
- delete: (spanLayerId, auditMessage) =>
403
- this._request('DELETE', `/api/v1/span-layers/${spanLayerId}`, { auditMessage }),
404
- /**
405
- * Update a span layer's name.
406
- * @param {string} spanLayerId - The span layer ID
407
- * @param {string} name - The name
408
- */
409
- update: (spanLayerId, name, auditMessage) =>
410
- this._request('PATCH', `/api/v1/span-layers/${spanLayerId}`, { auditMessage,
411
- body: bodyOf({ name }),
412
- }),
413
- /**
414
- * Create a new span layer.
415
- * @param {string} tokenLayerId - The token layer ID
416
- * @param {string} name - The name
417
- */
418
- create: (tokenLayerId, name, auditMessage) =>
419
- this._request('POST', '/api/v1/span-layers', { auditMessage,
420
- body: bodyOf({ 'token-layer-id': tokenLayerId, name }),
421
- }),
422
- /**
423
- * Shift a span layer's display order.
424
- * @param {string} spanLayerId - The span layer ID
425
- * @param {string} direction - The direction ("up" or "down")
426
- */
427
- shift: (spanLayerId, direction, auditMessage) =>
428
- this._request('POST', `/api/v1/span-layers/${spanLayerId}/shift`, { auditMessage,
429
- body: bodyOf({ direction }),
430
- }),
431
- };
432
-
433
- this.spans = {
434
- /**
435
- * Replace tokens for a span.
436
- * @param {string} spanId - The span ID
437
- * @param {Array} tokens - The tokens
438
- */
439
- setTokens: (spanId, tokens, auditMessage) =>
440
- this._request('PUT', `/api/v1/spans/${spanId}/tokens`, { auditMessage,
441
- body: bodyOf({ tokens }),
442
- }),
443
- /**
444
- * Create a new span. A span holds a primary atomic value and optional
445
- * metadata, and must at all times be associated with one or more tokens.
446
- * @param {string} spanLayerId - The span layer ID
447
- * @param {Array} tokens - The tokens
448
- * @param {any} value - The value
449
- * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
450
- */
451
- create: (spanLayerId, tokens, value, metadata, auditMessage) =>
452
- this._request('POST', '/api/v1/spans', { auditMessage,
453
- body: bodyOf({ 'span-layer-id': spanLayerId, tokens, value, metadata }),
454
- }),
455
- /**
456
- * Get a span by ID.
457
- * @param {string} spanId - The span ID
458
- * @param {string} [asOf] - Temporal query timestamp
459
- */
460
- get: (spanId, asOf) =>
461
- this._request('GET', `/api/v1/spans/${spanId}`, {
462
- queryParams: { 'as-of': asOf },
463
- }),
464
- /**
465
- * Delete a span.
466
- * @param {string} spanId - The span ID
467
- */
468
- delete: (spanId, auditMessage) =>
469
- this._request('DELETE', `/api/v1/spans/${spanId}`, { auditMessage }),
470
- /**
471
- * Update a span's value.
472
- * @param {string} spanId - The span ID
473
- * @param {any} value - The value
474
- */
475
- update: (spanId, value, auditMessage) =>
476
- this._request('PATCH', `/api/v1/spans/${spanId}`, { auditMessage,
477
- body: bodyOf({ value }),
478
- }),
479
- /**
480
- * Create multiple spans in a single operation.
481
- * @param {Array} body - The request body
482
- * @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
483
- */
484
- bulkCreate: (body, auditMessage) =>
485
- this._request('POST', '/api/v1/spans/bulk', { auditMessage, body }),
486
- /**
487
- * Delete multiple spans in a single operation. Provide an array of IDs.
488
- * @param {Array} body - The request body
489
- */
490
- bulkDelete: (body, auditMessage) =>
491
- this._request('DELETE', '/api/v1/spans/bulk', { auditMessage, body }),
492
- /**
493
- * Replace all metadata for a span.
494
- * @param {string} spanId - The span ID
495
- * @param {any} body - The request body
496
- */
497
- setMetadata: (spanId, body, auditMessage) =>
498
- this._request('PUT', `/api/v1/spans/${spanId}/metadata`, { auditMessage,
499
- rawBody: body, skipResponseTransform: true,
500
- }),
501
- /**
502
- * Remove all metadata from a span.
503
- * @param {string} spanId - The span ID
504
- */
505
- deleteMetadata: (spanId, auditMessage) =>
506
- this._request('DELETE', `/api/v1/spans/${spanId}/metadata`, { auditMessage,
507
- skipResponseTransform: true,
508
- }),
509
- /**
510
- * 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.
511
- * @param {string} spanId - The span ID
512
- * @param {any} body - The metadata patch
513
- */
514
- patchMetadata: (spanId, body, auditMessage) =>
515
- this._request('PATCH', `/api/v1/spans/${spanId}/metadata`, { auditMessage,
516
- rawBody: body, skipResponseTransform: true,
517
- }),
518
- };
519
-
520
- this.batch = {
521
- /**
522
- * Execute multiple API operations atomically. If any operation fails, all
523
- * changes are rolled back.
524
- * @param {Array} body - The request body
525
- */
526
- submit: (body, auditMessage) =>
527
- this._request('POST', '/api/v1/batch', { auditMessage,
528
- body, noBatch: true,
529
- }),
530
- };
531
-
532
- this.texts = {
533
- /**
534
- * Replace all metadata for a text.
535
- * @param {string} textId - The text ID
536
- * @param {any} body - The request body
537
- */
538
- setMetadata: (textId, body, auditMessage) =>
539
- this._request('PUT', `/api/v1/texts/${textId}/metadata`, { auditMessage,
540
- rawBody: body, skipResponseTransform: true,
541
- }),
542
- /**
543
- * Remove all metadata from a text.
544
- * @param {string} textId - The text ID
545
- */
546
- deleteMetadata: (textId, auditMessage) =>
547
- this._request('DELETE', `/api/v1/texts/${textId}/metadata`, { auditMessage,
548
- skipResponseTransform: true,
549
- }),
550
- /**
551
- * 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.
552
- * @param {string} textId - The text ID
553
- * @param {any} body - The metadata patch
554
- */
555
- patchMetadata: (textId, body, auditMessage) =>
556
- this._request('PATCH', `/api/v1/texts/${textId}/metadata`, { auditMessage,
557
- rawBody: body, skipResponseTransform: true,
558
- }),
559
- /**
560
- * Create a new text in a document's text layer. A text is a container for
561
- * one long string in `body` for a given layer.
562
- * @param {string} textLayerId - The text layer ID
563
- * @param {string} documentId - The document ID
564
- * @param {string} body - The request body
565
- * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
566
- */
567
- create: (textLayerId, documentId, body, metadata, auditMessage) =>
568
- this._request('POST', '/api/v1/texts', { auditMessage,
569
- body: bodyOf({ 'text-layer-id': textLayerId, 'document-id': documentId, body, metadata }),
570
- }),
571
- /**
572
- * Get a text.
573
- * @param {string} textId - The text ID
574
- * @param {string} [asOf] - Temporal query timestamp
575
- */
576
- get: (textId, asOf) =>
577
- this._request('GET', `/api/v1/texts/${textId}`, {
578
- queryParams: { 'as-of': asOf },
579
- }),
580
- /**
581
- * Delete a text and all dependent data.
582
- * @param {string} textId - The text ID
583
- */
584
- delete: (textId, auditMessage) =>
585
- this._request('DELETE', `/api/v1/texts/${textId}`, { auditMessage }),
586
- /**
587
- * Update a text's body. A diff is computed and token indices are updated
588
- * so that tokens remain intact. Alternatively, `body` can be a list of
589
- * edit directives.
590
- * @param {string} textId - The text ID
591
- * @param {any} body - The request body
592
- */
593
- update: (textId, body, auditMessage) =>
594
- this._request('PATCH', `/api/v1/texts/${textId}`, { auditMessage,
595
- body: bodyOf({ body }),
596
- }),
597
- };
598
-
599
- this.users = {
600
- /**
601
- * List (or search) users. Transparently follows pagination cursors and
602
- * returns the full flat array. Admin-or-maintainer only.
603
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
604
- * @param {object} [opts]
605
- * @param {string} [opts.q] - Filter to usernames containing this text (case-insensitive)
606
- * @param {string} [opts.asOf] - Temporal query timestamp
607
- */
608
- list: ({ q, asOf } = {}) =>
609
- listAll(this, '/api/v1/users', { query: { q, 'as-of': asOf } }),
610
- /**
611
- * Fetch a single page of users (optionally filtered by `q`).
612
- * @param {object} [opts]
613
- * @param {string} [opts.q] - Filter to usernames containing this text (case-insensitive)
614
- * @param {number} [opts.limit] - Page size (1..1000; server default 100)
615
- * @param {string} [opts.cursor] - Opaque cursor from a previous page
616
- * @param {string} [opts.asOf] - Temporal query timestamp
617
- * @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
618
- */
619
- listPage: ({ q, limit, cursor, asOf } = {}) =>
620
- listPage(this, '/api/v1/users', { limit, cursor, query: { q, 'as-of': asOf } }),
621
- /**
622
- * Async-iterate users page by page; yields each page's entries array.
623
- * @param {object} [opts]
624
- * @param {string} [opts.q] - Filter to usernames containing this text (case-insensitive)
625
- * @param {number} [opts.pageSize] - Per-request page size
626
- * @param {string} [opts.asOf] - Temporal query timestamp
627
- * 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.
628
- * @returns {AsyncGenerator<Array>}
629
- */
630
- iterPages: ({ q, pageSize, asOf } = {}) =>
631
- iterPages(this, '/api/v1/users', { pageSize, query: { q, 'as-of': asOf } }),
632
- /**
633
- * Create a new user
634
- * @param {string} username - The username
635
- * @param {string} password - The password
636
- * @param {boolean} isAdmin - Whether the user is an admin
637
- */
638
- create: (username, password, isAdmin, auditMessage) =>
639
- this._request('POST', '/api/v1/users', { auditMessage,
640
- body: bodyOf({ username, password, 'is-admin': isAdmin }),
641
- }),
642
- /**
643
- * Get audit log for a user's actions. Transparently follows pagination
644
- * cursors and returns the full flat array.
645
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
646
- * @param {string} userId - The user ID
647
- * @param {string} [startTime] - Start of time range
648
- * @param {string} [endTime] - End of time range
649
- * @param {string} [asOf] - Temporal query timestamp
650
- * @param {string[]|string} [opTypes] - Only return operations of these
651
- * types, spelled as in an entry's `op/type` (e.g.
652
- * `['span-layer/create', 'span-layer/delete']`). An entry appears when
653
- * one of its operations matches, carrying only the ones that did.
654
- */
655
- audit: (userId, startTime, endTime, asOf, opTypes) =>
656
- listAll(this, `/api/v1/users/${userId}/audit`, {
657
- query: {
658
- 'start-time': startTime,
659
- 'end-time': endTime,
660
- 'as-of': asOf,
661
- 'op-types': opTypesParam(opTypes),
662
- },
663
- }),
664
- /**
665
- * Get a user by ID
666
- * @param {string} id - The resource ID
667
- * @param {string} [asOf] - Temporal query timestamp
668
- */
669
- get: (id, asOf) =>
670
- this._request('GET', `/api/v1/users/${id}`, {
671
- queryParams: { 'as-of': asOf },
672
- }),
673
- /**
674
- * Deactivate a user. Users are never hard-deleted: deactivation
675
- * rejects their logins and tokens, strips their project memberships
676
- * and vocab maintainerships, and revokes their API tokens. The user
677
- * stays visible in listings with a deactivated-at timestamp.
678
- * Reversible via activate(), which restores login only.
679
- * @param {string} id - The resource ID
680
- */
681
- delete: (id, auditMessage) =>
682
- this._request('DELETE', `/api/v1/users/${id}`, { auditMessage }),
683
- /**
684
- * Reactivate a deactivated user, restoring their ability to log in.
685
- * Project memberships, vocab maintainerships, and API tokens removed
686
- * at deactivation are NOT restored — re-grant them deliberately.
687
- * @param {string} id - The resource ID
688
- */
689
- activate: (id, auditMessage) =>
690
- this._request('POST', `/api/v1/users/${id}/activate`, { auditMessage }),
691
- /**
692
- * Modify a user. Admins may change the username, password, and admin
693
- * status of any user. All other users may only modify their own username
694
- * or password.
695
- * @param {string} id - The resource ID
696
- * @param {string} [password] - New password
697
- * @param {string} [username] - New username
698
- * @param {boolean} [isAdmin] - New admin status
699
- */
700
- update: (id, password, username, isAdmin, auditMessage) =>
701
- this._request('PATCH', `/api/v1/users/${id}`, { auditMessage,
702
- body: bodyOf({ password, username, 'is-admin': isAdmin }),
703
- }),
704
- /**
705
- * Build a URL for a user's profile picture, suitable for use directly as
706
- * an `<img>` src. An image element cannot send an Authorization header,
707
- * so the session token rides in the query string, the same way document
708
- * media does.
709
- *
710
- * Pass the user record's `avatarHash` as the second argument whenever you
711
- * have it: the URL then addresses that exact picture, so the browser can
712
- * cache it indefinitely and still pick up a replacement the moment the
713
- * user changes it. Returns null when the user has no picture, so callers
714
- * can fall back to initials without a wasted request.
715
- * @param {string} id - The user ID
716
- * @param {string} [avatarHash] - The user record's `avatarHash`
717
- * @returns {string|null}
718
- */
719
- avatarUrl: (id, avatarHash) => {
720
- if (avatarHash === null) return null;
721
- const params = new URLSearchParams({ token: this.token });
722
- if (avatarHash) params.set('v', avatarHash);
723
- return `${this.baseUrl}/api/v1/users/${id}/avatar?${params}`;
724
- },
725
- /**
726
- * Fetch a user's profile picture as raw bytes. Most callers want
727
- * avatarUrl() instead. This one is for non-browser consumers.
728
- * @param {string} id - The user ID
729
- */
730
- getAvatar: (id) =>
731
- this._request('GET', `/api/v1/users/${id}/avatar`, {
732
- noBatch: true,
733
- binaryResponse: true,
734
- }),
735
- /**
736
- * Upload a profile picture. Your own, or anyone's if you are an admin.
737
- * The server center-crops to a square, scales to the configured edge
738
- * length, and re-encodes, so no client-side resizing is needed. Accepts
739
- * PNG, JPEG, WebP, and GIF. Resolves to the updated user record.
740
- * @param {string} id - The user ID
741
- * @param {File} file - The image to upload
742
- */
743
- setAvatar: (id, file, auditMessage) => {
744
- const fd = new FormData();
745
- fd.append('file', file);
746
- return this._request('PUT', `/api/v1/users/${id}/avatar`, { auditMessage,
747
- body: fd, formData: true, noBatch: true,
748
- });
749
- },
750
- /**
751
- * Remove a profile picture. Your own, or anyone's if you are an admin.
752
- * @param {string} id - The user ID
753
- */
754
- deleteAvatar: (id, auditMessage) =>
755
- this._request('DELETE', `/api/v1/users/${id}/avatar`, { auditMessage,
756
- noBatch: true,
757
- }),
758
- };
759
-
760
- this.apiTokens = {
761
- /**
762
- * List a user's named API tokens. Never includes the signed token
763
- * string itself — that is only returned once, by create().
764
- * Transparently follows pagination cursors and returns the full flat array.
765
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
766
- * @param {string} userId - The user ID who owns the tokens
767
- */
768
- list: (userId) =>
769
- listAll(this, `/api/v1/users/${userId}/tokens`),
770
- /**
771
- * Fetch a single page of a user's named API tokens.
772
- * @param {string} userId - The user ID who owns the tokens
773
- * @param {object} [opts]
774
- * @param {number} [opts.limit] - Page size (1..1000; server default 100)
775
- * @param {string} [opts.cursor] - Opaque cursor from a previous page
776
- * @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
777
- */
778
- listPage: (userId, { limit, cursor } = {}) =>
779
- listPage(this, `/api/v1/users/${userId}/tokens`, { limit, cursor }),
780
- /**
781
- * Async-iterate a user's named API tokens page by page; yields each
782
- * page's entries array.
783
- * @param {string} userId - The user ID who owns the tokens
784
- * @param {object} [opts]
785
- * @param {number} [opts.pageSize] - Per-request page size
786
- * 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.
787
- * @returns {AsyncGenerator<Array>}
788
- */
789
- iterPages: (userId, { pageSize } = {}) =>
790
- iterPages(this, `/api/v1/users/${userId}/tokens`, { pageSize }),
791
- /**
792
- * Mint a named API token for a user. The returned `token` is the signed
793
- * credential and is shown ONLY here — store it immediately. API tokens
794
- * do not expire and survive password changes / logout; revoke to kill.
795
- * @param {string} userId - The user ID who will own the token
796
- * @param {string} name - A human label, e.g. "Stanza Parser"
797
- * @returns {Promise<{id: string, name: string, token: string}>}
798
- */
799
- create: (userId, name, auditMessage) =>
800
- this._request('POST', `/api/v1/users/${userId}/tokens`, { auditMessage,
801
- body: bodyOf({ name }),
802
- }),
803
- /**
804
- * Revoke a named API token (soft-revoke; idempotent).
805
- * @param {string} userId - The user ID who owns the token
806
- * @param {string} tokenId - The token ID to revoke
807
- */
808
- revoke: (userId, tokenId, auditMessage) =>
809
- this._request('DELETE', `/api/v1/users/${userId}/tokens/${tokenId}`, { auditMessage }),
810
- };
811
-
812
- this.invites = {
813
- /**
814
- * List invites you minted, oldest first. With `projectId`, lists that
815
- * project's invites instead (including co-maintainers'), which requires
816
- * maintainer or admin on it. Never includes invite codes — the code is
817
- * returned once, by create(), and is not recoverable afterward.
818
- * Transparently follows pagination cursors and returns the full flat array.
819
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
820
- * @param {object} [opts]
821
- * @param {string} [opts.projectId] - List this project's invites instead of your own
822
- */
823
- list: ({ projectId } = {}) =>
824
- listAll(this, '/api/v1/invites', { query: { 'project-id': projectId } }),
825
- /**
826
- * Fetch a single page of invites.
827
- * @param {object} [opts]
828
- * @param {string} [opts.projectId] - List this project's invites instead of your own
829
- * @param {number} [opts.limit] - Page size (1..1000; server default 100)
830
- * @param {string} [opts.cursor] - Opaque cursor from a previous page
831
- * @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
832
- */
833
- listPage: ({ projectId, limit, cursor } = {}) =>
834
- listPage(this, '/api/v1/invites', { limit, cursor, query: { 'project-id': projectId } }),
835
- /**
836
- * Async-iterate invites page by page; yields each page's entries array.
837
- * @param {object} [opts]
838
- * @param {string} [opts.projectId] - List this project's invites instead of your own
839
- * @param {number} [opts.pageSize] - Per-request page size
840
- * 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.
841
- * @returns {AsyncGenerator<Array>}
842
- */
843
- iterPages: ({ projectId, pageSize } = {}) =>
844
- iterPages(this, '/api/v1/invites', { pageSize, query: { 'project-id': projectId } }),
845
- /**
846
- * Mint an invite. The returned `code` is shown ONLY here — it is never
847
- * stored and cannot be recovered, so build and hand off the link now.
848
- * Use PlaidClient.inviteUrl() to turn it into one.
849
- *
850
- * Admins may mint anything. A project maintainer may mint role grants on
851
- * projects they maintain, and nothing else: no admin grant, no grantless
852
- * invite, no password resets.
853
- * @param {object} opts
854
- * @param {string} [opts.projectId] - Project the redeemer joins (with projectRole)
855
- * @param {string} [opts.projectRole] - "reader" | "writer" | "maintainer"
856
- * @param {boolean} [opts.grantAdmin] - Make the new account a global admin (admin only)
857
- * @param {string} [opts.targetUserId] - Make this a password reset for that user (admin only)
858
- * @param {number} [opts.maxUses] - How many accounts this link may create (default 1)
859
- * @param {number} [opts.ttlDays] - Days until it expires (default 14, max 365)
860
- * @param {string} [opts.note] - Human label shown in your invite list
861
- * @returns {Promise<{id: string, code: string, kind: string, status: string}>}
862
- */
863
- create: ({ projectId, projectRole, grantAdmin, targetUserId, maxUses, ttlDays, note } = {}, auditMessage) =>
864
- this._request('POST', '/api/v1/invites', { auditMessage,
865
- body: bodyOf({
866
- 'project-id': projectId, 'project-role': projectRole,
867
- 'grant-admin': grantAdmin, 'target-user-id': targetUserId,
868
- 'max-uses': maxUses, 'ttl-days': ttlDays, note,
869
- }),
870
- }),
871
- /**
872
- * Revoke an invite, killing the link immediately. Idempotent. Allowed for
873
- * the creator, an admin, or a maintainer of the invite's project.
874
- * @param {string} id - The invite ID
875
- */
876
- revoke: (id, auditMessage) =>
877
- this._request('DELETE', `/api/v1/invites/${id}`, { auditMessage }),
878
- };
879
-
880
- this.tokenLayers = {
881
- /**
882
- * Shift a token layer's display order.
883
- * @param {string} tokenLayerId - The token layer ID
884
- * @param {string} direction - The direction ("up" or "down")
885
- */
886
- shift: (tokenLayerId, direction, auditMessage) =>
887
- this._request('POST', `/api/v1/token-layers/${tokenLayerId}/shift`, { auditMessage,
888
- body: bodyOf({ direction }),
889
- }),
890
- /**
891
- * Create a new token layer.
892
- * @param {string} textLayerId - The text layer ID
893
- * @param {string} name - The name
894
- * @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.
895
- * @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).
896
- */
897
- create: (textLayerId, name, overlapMode, parentTokenLayerId, auditMessage) =>
898
- this._request('POST', '/api/v1/token-layers', { auditMessage,
899
- body: bodyOf({ 'text-layer-id': textLayerId, name, 'overlap-mode': overlapMode, 'parent-token-layer-id': parentTokenLayerId }),
900
- }),
901
- /**
902
- * Set a configuration value for a layer in an editor namespace.
903
- * @param {string} tokenLayerId - The token layer ID
904
- * @param {string} namespace - The config namespace
905
- * @param {string} configKey - The config key
906
- * @param {any} configValue - Configuration value to set
907
- */
908
- setConfig: (tokenLayerId, namespace, configKey, configValue, auditMessage) =>
909
- this._request('PUT', `/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`, { auditMessage,
910
- rawBody: configValue, skipResponseTransform: true,
911
- }),
912
- /**
913
- * Remove a configuration value for a layer.
914
- * @param {string} tokenLayerId - The token layer ID
915
- * @param {string} namespace - The config namespace
916
- * @param {string} configKey - The config key
917
- */
918
- deleteConfig: (tokenLayerId, namespace, configKey, auditMessage) =>
919
- this._request('DELETE', `/api/v1/token-layers/${tokenLayerId}/config/${namespace}/${configKey}`, { auditMessage,
920
- skipResponseTransform: true,
921
- }),
922
- /**
923
- * Get a token layer by ID.
924
- * @param {string} tokenLayerId - The token layer ID
925
- * @param {string} [asOf] - Temporal query timestamp
926
- */
927
- get: (tokenLayerId, asOf) =>
928
- this._request('GET', `/api/v1/token-layers/${tokenLayerId}`, {
929
- queryParams: { 'as-of': asOf },
930
- }),
931
- /**
932
- * Delete a token layer.
933
- * @param {string} tokenLayerId - The token layer ID
934
- */
935
- delete: (tokenLayerId, auditMessage) =>
936
- this._request('DELETE', `/api/v1/token-layers/${tokenLayerId}`, { auditMessage }),
937
- /**
938
- * Update a token layer's name.
939
- * @param {string} tokenLayerId - The token layer ID
940
- * @param {string} name - The name
941
- */
942
- update: (tokenLayerId, name, auditMessage) =>
943
- this._request('PATCH', `/api/v1/token-layers/${tokenLayerId}`, { auditMessage,
944
- body: bodyOf({ name }),
945
- }),
946
- };
947
-
948
- this.documents = {
949
- /**
950
- * Check the lock status of a document.
951
- * @param {string} documentId - The document ID
952
- * @param {string} [asOf] - Temporal query timestamp
953
- */
954
- checkLock: (documentId, asOf) =>
955
- this._request('GET', `/api/v1/documents/${documentId}/lock`, {
956
- queryParams: { 'as-of': asOf },
957
- }),
958
- /**
959
- * Acquire or refresh a document lock
960
- * @param {string} documentId - The document ID
961
- */
962
- acquireLock: (documentId, auditMessage) =>
963
- this._request('POST', `/api/v1/documents/${documentId}/lock`, { auditMessage }),
964
- /**
965
- * Release a document lock
966
- * @param {string} documentId - The document ID
967
- */
968
- releaseLock: (documentId, auditMessage) =>
969
- this._request('DELETE', `/api/v1/documents/${documentId}/lock`, { auditMessage }),
970
- /**
971
- * Get media file for a document
972
- * @param {string} documentId - The document ID
973
- * @param {string} [asOf] - Temporal query timestamp
974
- */
975
- getMedia: (documentId, asOf) =>
976
- this._request('GET', `/api/v1/documents/${documentId}/media`, {
977
- queryParams: { 'as-of': asOf },
978
- noBatch: true,
979
- binaryResponse: true,
980
- }),
981
- /**
982
- * Upload a media file for a document. Uses Apache Tika for content validation.
983
- * @param {string} documentId - The document ID
984
- * @param {File} file - The file to upload
985
- */
986
- uploadMedia: (documentId, file, auditMessage) => {
987
- const fd = new FormData();
988
- fd.append('file', file);
989
- return this._request('PUT', `/api/v1/documents/${documentId}/media`, { auditMessage,
990
- body: fd, formData: true, noBatch: true,
991
- });
992
- },
993
- /**
994
- * Delete media file for a document
995
- * @param {string} documentId - The document ID
996
- */
997
- deleteMedia: (documentId, auditMessage) =>
998
- this._request('DELETE', `/api/v1/documents/${documentId}/media`, { auditMessage,
999
- noBatch: true,
1000
- }),
1001
- /**
1002
- * Replace all metadata for a document.
1003
- * @param {string} documentId - The document ID
1004
- * @param {any} body - The request body
1005
- */
1006
- setMetadata: (documentId, body, auditMessage) =>
1007
- this._request('PUT', `/api/v1/documents/${documentId}/metadata`, { auditMessage,
1008
- rawBody: body, skipResponseTransform: true,
1009
- }),
1010
- /**
1011
- * Remove all metadata from a document.
1012
- * @param {string} documentId - The document ID
1013
- */
1014
- deleteMetadata: (documentId, auditMessage) =>
1015
- this._request('DELETE', `/api/v1/documents/${documentId}/metadata`, { auditMessage,
1016
- skipResponseTransform: true,
1017
- }),
1018
- /**
1019
- * 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.
1020
- * @param {string} documentId - The document ID
1021
- * @param {any} body - The metadata patch
1022
- */
1023
- patchMetadata: (documentId, body, auditMessage) =>
1024
- this._request('PATCH', `/api/v1/documents/${documentId}/metadata`, { auditMessage,
1025
- rawBody: body, skipResponseTransform: true,
1026
- }),
1027
- /**
1028
- * Get audit log for a document. Transparently follows pagination cursors
1029
- * and returns the full flat array.
1030
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
1031
- * @param {string} documentId - The document ID
1032
- * @param {string} [startTime] - Start of time range
1033
- * @param {string} [endTime] - End of time range
1034
- * @param {string} [asOf] - Temporal query timestamp
1035
- * @param {string[]|string} [opTypes] - Only return operations of these
1036
- * types, spelled as in an entry's `op/type` (e.g.
1037
- * `['span-layer/create', 'span-layer/delete']`). An entry appears when
1038
- * one of its operations matches, carrying only the ones that did.
1039
- */
1040
- audit: (documentId, startTime, endTime, asOf, opTypes) =>
1041
- listAll(this, `/api/v1/documents/${documentId}/audit`, {
1042
- query: {
1043
- 'start-time': startTime,
1044
- 'end-time': endTime,
1045
- 'as-of': asOf,
1046
- 'op-types': opTypesParam(opTypes),
1047
- },
1048
- }),
1049
- /**
1050
- * Get a document. Set `includeBody` to true to include all data.
1051
- *
1052
- * `layers` narrows a body read to the layers you name (ids of any kind:
1053
- * text, token, span, or relation). A layer comes back when it is named
1054
- * or is an ancestor of a named layer, and carries its own
1055
- * texts/tokens/spans/relations/vocabs only when it is itself named — so
1056
- * name the text layer too if you also want the text body. An id that is
1057
- * not a layer of this document's project is an error, not a quietly
1058
- * smaller response. Requires `includeBody`.
1059
- * @param {string} documentId - The document ID
1060
- * @param {boolean} [includeBody] - Include document body data
1061
- * @param {string} [asOf] - Temporal query timestamp
1062
- * @param {string[]|string} [layers] - Layer ids to restrict a body read to
1063
- */
1064
- get: (documentId, includeBody, asOf, layers) =>
1065
- this._request('GET', `/api/v1/documents/${documentId}`, {
1066
- queryParams: {
1067
- 'include-body': includeBody,
1068
- 'as-of': asOf,
1069
- layers: layersParam(layers),
1070
- },
1071
- }),
1072
- /**
1073
- * Delete a document and all data contained.
1074
- * @param {string} documentId - The document ID
1075
- */
1076
- delete: (documentId, auditMessage) =>
1077
- this._request('DELETE', `/api/v1/documents/${documentId}`, { auditMessage }),
1078
- /**
1079
- * Update a document's name.
1080
- * @param {string} documentId - The document ID
1081
- * @param {string} name - The name
1082
- */
1083
- update: (documentId, name, auditMessage) =>
1084
- this._request('PATCH', `/api/v1/documents/${documentId}`, { auditMessage,
1085
- body: bodyOf({ name }),
1086
- }),
1087
- /**
1088
- * Create a new document in a project.
1089
- * @param {string} projectId - The project ID
1090
- * @param {string} name - The name
1091
- * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
1092
- */
1093
- create: (projectId, name, metadata, auditMessage) =>
1094
- this._request('POST', '/api/v1/documents', { auditMessage,
1095
- body: bodyOf({ 'project-id': projectId, name, metadata }),
1096
- }),
1097
- };
1098
-
1099
- this.projects = {
1100
- /**
1101
- * Set a user's access level to read and write for this project.
1102
- * @param {string} id - The resource ID
1103
- * @param {string} userId - The user ID
1104
- */
1105
- addWriter: (id, userId, auditMessage) =>
1106
- this._request('POST', `/api/v1/projects/${id}/writers/${userId}`, { auditMessage }),
1107
- /**
1108
- * Remove a user's writer privileges for this project.
1109
- * @param {string} id - The resource ID
1110
- * @param {string} userId - The user ID
1111
- */
1112
- removeWriter: (id, userId, auditMessage) =>
1113
- this._request('DELETE', `/api/v1/projects/${id}/writers/${userId}`, { auditMessage }),
1114
- /**
1115
- * Set a user's access level to read-only for this project.
1116
- * @param {string} id - The resource ID
1117
- * @param {string} userId - The user ID
1118
- */
1119
- addReader: (id, userId, auditMessage) =>
1120
- this._request('POST', `/api/v1/projects/${id}/readers/${userId}`, { auditMessage }),
1121
- /**
1122
- * Remove a user's reader privileges for this project.
1123
- * @param {string} id - The resource ID
1124
- * @param {string} userId - The user ID
1125
- */
1126
- removeReader: (id, userId, auditMessage) =>
1127
- this._request('DELETE', `/api/v1/projects/${id}/readers/${userId}`, { auditMessage }),
1128
- /**
1129
- * Set a configuration value for a project in an editor namespace.
1130
- * @param {string} id - The resource ID
1131
- * @param {string} namespace - The config namespace
1132
- * @param {string} configKey - The config key
1133
- * @param {any} configValue - Configuration value to set
1134
- */
1135
- setConfig: (id, namespace, configKey, configValue, auditMessage) =>
1136
- this._request('PUT', `/api/v1/projects/${id}/config/${namespace}/${configKey}`, { auditMessage,
1137
- rawBody: configValue, skipResponseTransform: true,
1138
- }),
1139
- /**
1140
- * Remove a configuration value for a project.
1141
- * @param {string} id - The resource ID
1142
- * @param {string} namespace - The config namespace
1143
- * @param {string} configKey - The config key
1144
- */
1145
- deleteConfig: (id, namespace, configKey, auditMessage) =>
1146
- this._request('DELETE', `/api/v1/projects/${id}/config/${namespace}/${configKey}`, { auditMessage,
1147
- skipResponseTransform: true,
1148
- }),
1149
- /**
1150
- * Assign a user as a maintainer for this project.
1151
- * @param {string} id - The resource ID
1152
- * @param {string} userId - The user ID
1153
- */
1154
- addMaintainer: (id, userId, auditMessage) =>
1155
- this._request('POST', `/api/v1/projects/${id}/maintainers/${userId}`, { auditMessage }),
1156
- /**
1157
- * Remove a user's maintainer privileges for this project.
1158
- * @param {string} id - The resource ID
1159
- * @param {string} userId - The user ID
1160
- */
1161
- removeMaintainer: (id, userId, auditMessage) =>
1162
- this._request('DELETE', `/api/v1/projects/${id}/maintainers/${userId}`, { auditMessage }),
1163
- /**
1164
- * Get audit log for a project. Transparently follows pagination cursors
1165
- * and returns the full flat array.
1166
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
1167
- * @param {string} projectId - The project ID
1168
- * @param {string} [startTime] - Start of time range
1169
- * @param {string} [endTime] - End of time range
1170
- * @param {string} [asOf] - Temporal query timestamp
1171
- * @param {string[]|string} [opTypes] - Only return operations of these
1172
- * types, spelled as in an entry's `op/type` (e.g.
1173
- * `['span-layer/create', 'span-layer/delete']`). An entry appears when
1174
- * one of its operations matches, carrying only the ones that did.
1175
- */
1176
- audit: (projectId, startTime, endTime, asOf, opTypes) =>
1177
- listAll(this, `/api/v1/projects/${projectId}/audit`, {
1178
- query: {
1179
- 'start-time': startTime,
1180
- 'end-time': endTime,
1181
- 'as-of': asOf,
1182
- 'op-types': opTypesParam(opTypes),
1183
- },
1184
- }),
1185
- /**
1186
- * Link a vocabulary to a project.
1187
- * @param {string} id - The resource ID
1188
- * @param {string} vocabId - The vocab layer ID
1189
- */
1190
- linkVocab: (id, vocabId, auditMessage) =>
1191
- this._request('POST', `/api/v1/projects/${id}/vocabs/${vocabId}`, { auditMessage }),
1192
- /**
1193
- * Unlink a vocabulary from a project.
1194
- * @param {string} id - The resource ID
1195
- * @param {string} vocabId - The vocab layer ID
1196
- */
1197
- unlinkVocab: (id, vocabId, auditMessage) =>
1198
- this._request('DELETE', `/api/v1/projects/${id}/vocabs/${vocabId}`, { auditMessage }),
1199
- /**
1200
- * Get a project by ID. To fetch the project's documents, use
1201
- * listDocuments(id) — the include-documents flag has been removed.
1202
- * @param {string} id - The resource ID
1203
- * @param {string} [asOf] - Temporal query timestamp
1204
- */
1205
- get: (id, asOf) =>
1206
- this._request('GET', `/api/v1/projects/${id}`, {
1207
- queryParams: { 'as-of': asOf },
1208
- }),
1209
- /**
1210
- * List all documents in a project. Transparently follows pagination
1211
- * cursors and returns the full flat array.
1212
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
1213
- *
1214
- * Note: this endpoint does not support temporal (`as-of`) queries; the
1215
- * server rejects `?as-of=` on the documents-list route with a 400.
1216
- * @param {string} id - The project ID
1217
- */
1218
- listDocuments: (id) =>
1219
- listAll(this, `/api/v1/projects/${id}/documents`),
1220
- /**
1221
- * Fetch a single page of a project's documents.
1222
- *
1223
- * Note: this endpoint does not support temporal (`as-of`) queries; the
1224
- * server rejects `?as-of=` on the documents-list route with a 400.
1225
- * @param {string} id - The project ID
1226
- * @param {object} [opts]
1227
- * @param {number} [opts.limit] - Page size (1..1000; server default 100)
1228
- * @param {string} [opts.cursor] - Opaque cursor from a previous page
1229
- * @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
1230
- */
1231
- listDocumentsPage: (id, { limit, cursor } = {}) =>
1232
- listPage(this, `/api/v1/projects/${id}/documents`, { limit, cursor }),
1233
- /**
1234
- * Async-iterate a project's documents page by page; yields each page's
1235
- * entries array.
1236
- *
1237
- * Note: this endpoint does not support temporal (`as-of`) queries; the
1238
- * server rejects `?as-of=` on the documents-list route with a 400.
1239
- * @param {string} id - The project ID
1240
- * @param {object} [opts]
1241
- * @param {number} [opts.pageSize] - Per-request page size
1242
- * 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.
1243
- * @returns {AsyncGenerator<Array>}
1244
- */
1245
- iterDocuments: (id, { pageSize } = {}) =>
1246
- iterPages(this, `/api/v1/projects/${id}/documents`, { pageSize }),
1247
- /**
1248
- * Delete a project and everything in it. This is irrecoverable.
1249
- *
1250
- * For a large project this can take a long time, so the client's
1251
- * per-request timeout is DISABLED by default. Pass `{ timeout }` to
1252
- * impose a finite bound.
1253
- * @param {string} id - The resource ID
1254
- * @param {string} [auditMessage] - Custom audit-log message
1255
- * @param {object} [options]
1256
- * @param {number} [options.timeout=0] - Per-request timeout in ms (0/null disables)
1257
- */
1258
- delete: (id, auditMessage, { timeout = 0 } = {}) =>
1259
- this._request('DELETE', `/api/v1/projects/${id}`, { auditMessage, timeout }),
1260
- /**
1261
- * Update a project's name.
1262
- * @param {string} id - The resource ID
1263
- * @param {string} name - The name
1264
- */
1265
- update: (id, name, auditMessage) =>
1266
- this._request('PATCH', `/api/v1/projects/${id}`, { auditMessage,
1267
- body: bodyOf({ name }),
1268
- }),
1269
- /**
1270
- * List all projects accessible to user. Transparently follows pagination
1271
- * cursors and returns the full flat array.
1272
- * Cannot be used inside a batch (auto-paginates across requests); throws if called while batching — use listPage() for a single page in a batch.
1273
- * @param {string} [asOf] - Temporal query timestamp
1274
- */
1275
- list: (asOf) =>
1276
- listAll(this, '/api/v1/projects', { query: { 'as-of': asOf } }),
1277
- /**
1278
- * Fetch a single page of projects.
1279
- * @param {object} [opts]
1280
- * @param {number} [opts.limit] - Page size (1..1000; server default 100)
1281
- * @param {string} [opts.cursor] - Opaque cursor from a previous page
1282
- * @param {string} [opts.asOf] - Temporal query timestamp
1283
- * @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
1284
- */
1285
- listPage: ({ limit, cursor, asOf } = {}) =>
1286
- listPage(this, '/api/v1/projects', { limit, cursor, query: { 'as-of': asOf } }),
1287
- /**
1288
- * Async-iterate projects page by page; yields each page's entries array.
1289
- * @param {object} [opts]
1290
- * @param {number} [opts.pageSize] - Per-request page size
1291
- * @param {string} [opts.asOf] - Temporal query timestamp
1292
- * 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.
1293
- * @returns {AsyncGenerator<Array>}
1294
- */
1295
- iterPages: ({ pageSize, asOf } = {}) =>
1296
- iterPages(this, '/api/v1/projects', { pageSize, query: { 'as-of': asOf } }),
1297
- /**
1298
- * Create a new project. Note: this also registers the user as a maintainer.
1299
- * @param {string} name - The name
1300
- */
1301
- create: (name, auditMessage) =>
1302
- this._request('POST', '/api/v1/projects', { auditMessage,
1303
- body: bodyOf({ name }),
1304
- }),
1305
- };
1306
-
1307
- this.textLayers = {
1308
- /**
1309
- * Set a configuration value for a layer in an editor namespace.
1310
- * @param {string} textLayerId - The text layer ID
1311
- * @param {string} namespace - The config namespace
1312
- * @param {string} configKey - The config key
1313
- * @param {any} configValue - Configuration value to set
1314
- */
1315
- setConfig: (textLayerId, namespace, configKey, configValue, auditMessage) =>
1316
- this._request('PUT', `/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`, { auditMessage,
1317
- rawBody: configValue, skipResponseTransform: true,
1318
- }),
1319
- /**
1320
- * Remove a configuration value for a layer.
1321
- * @param {string} textLayerId - The text layer ID
1322
- * @param {string} namespace - The config namespace
1323
- * @param {string} configKey - The config key
1324
- */
1325
- deleteConfig: (textLayerId, namespace, configKey, auditMessage) =>
1326
- this._request('DELETE', `/api/v1/text-layers/${textLayerId}/config/${namespace}/${configKey}`, { auditMessage,
1327
- skipResponseTransform: true,
1328
- }),
1329
- /**
1330
- * Get a text layer by ID.
1331
- * @param {string} textLayerId - The text layer ID
1332
- * @param {string} [asOf] - Temporal query timestamp
1333
- */
1334
- get: (textLayerId, asOf) =>
1335
- this._request('GET', `/api/v1/text-layers/${textLayerId}`, {
1336
- queryParams: { 'as-of': asOf },
1337
- }),
1338
- /**
1339
- * Delete a text layer.
1340
- * @param {string} textLayerId - The text layer ID
1341
- */
1342
- delete: (textLayerId, auditMessage) =>
1343
- this._request('DELETE', `/api/v1/text-layers/${textLayerId}`, { auditMessage }),
1344
- /**
1345
- * Update a text layer's name.
1346
- * @param {string} textLayerId - The text layer ID
1347
- * @param {string} name - The name
1348
- */
1349
- update: (textLayerId, name, auditMessage) =>
1350
- this._request('PATCH', `/api/v1/text-layers/${textLayerId}`, { auditMessage,
1351
- body: bodyOf({ name }),
1352
- }),
1353
- /**
1354
- * Shift a text layer's display order within the project.
1355
- * @param {string} textLayerId - The text layer ID
1356
- * @param {string} direction - The direction ("up" or "down")
1357
- */
1358
- shift: (textLayerId, direction, auditMessage) =>
1359
- this._request('POST', `/api/v1/text-layers/${textLayerId}/shift`, { auditMessage,
1360
- body: bodyOf({ direction }),
1361
- }),
1362
- /**
1363
- * Create a new text layer for a project.
1364
- * @param {string} projectId - The project ID
1365
- * @param {string} name - The name
1366
- */
1367
- create: (projectId, name, auditMessage) =>
1368
- this._request('POST', '/api/v1/text-layers', { auditMessage,
1369
- body: bodyOf({ 'project-id': projectId, name }),
1370
- }),
1371
- };
1372
-
1373
- this.vocabItems = {
1374
- /**
1375
- * Replace all metadata for a vocab item.
1376
- * @param {string} id - The resource ID
1377
- * @param {any} body - The request body
1378
- */
1379
- setMetadata: (id, body, auditMessage) =>
1380
- this._request('PUT', `/api/v1/vocab-items/${id}/metadata`, { auditMessage,
1381
- rawBody: body, skipResponseTransform: true,
1382
- }),
1383
- /**
1384
- * Remove all metadata from a vocab item.
1385
- * @param {string} id - The resource ID
1386
- */
1387
- deleteMetadata: (id, auditMessage) =>
1388
- this._request('DELETE', `/api/v1/vocab-items/${id}/metadata`, { auditMessage,
1389
- skipResponseTransform: true,
1390
- }),
1391
- /**
1392
- * 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.
1393
- * @param {string} id - The resource ID
1394
- * @param {any} body - The metadata patch
1395
- */
1396
- patchMetadata: (id, body, auditMessage) =>
1397
- this._request('PATCH', `/api/v1/vocab-items/${id}/metadata`, { auditMessage,
1398
- rawBody: body, skipResponseTransform: true,
1399
- }),
1400
- /**
1401
- * Create a new vocab item
1402
- * @param {string} vocabLayerId - The vocab layer ID
1403
- * @param {string} form - The vocab item form
1404
- * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
1405
- */
1406
- create: (vocabLayerId, form, metadata, auditMessage) =>
1407
- this._request('POST', '/api/v1/vocab-items', { auditMessage,
1408
- body: bodyOf({ 'vocab-layer-id': vocabLayerId, form, metadata }),
1409
- }),
1410
- /**
1411
- * Create multiple vocab items in a single operation. Entries may target
1412
- * different vocab layers; the user must have write access to each.
1413
- * @param {Array<{vocabLayerId: string, form: string, metadata?: any}>} body - The vocab items to create
1414
- */
1415
- bulkCreate: (body, auditMessage) =>
1416
- this._request('POST', '/api/v1/vocab-items/bulk', { auditMessage, body }),
1417
- /**
1418
- * Delete multiple vocab items in a single operation. Each item's
1419
- * descendant vocab links are deleted too. Provide an array of IDs.
1420
- * @param {string[]} body - The vocab item IDs to delete
1421
- */
1422
- bulkDelete: (body, auditMessage) =>
1423
- this._request('DELETE', '/api/v1/vocab-items/bulk', { auditMessage, body }),
1424
- /**
1425
- * Get a vocab item by ID
1426
- * @param {string} id - The resource ID
1427
- * @param {string} [asOf] - Temporal query timestamp
1428
- */
1429
- get: (id, asOf) =>
1430
- this._request('GET', `/api/v1/vocab-items/${id}`, {
1431
- queryParams: { 'as-of': asOf },
1432
- }),
1433
- /**
1434
- * Delete a vocab item
1435
- * @param {string} id - The resource ID
1436
- */
1437
- delete: (id, auditMessage) =>
1438
- this._request('DELETE', `/api/v1/vocab-items/${id}`, { auditMessage }),
1439
- /**
1440
- * Update a vocab item's form
1441
- * @param {string} id - The resource ID
1442
- * @param {string} form - The vocab item form
1443
- */
1444
- update: (id, form, auditMessage) =>
1445
- this._request('PATCH', `/api/v1/vocab-items/${id}`, { auditMessage,
1446
- body: bodyOf({ form }),
1447
- }),
1448
- };
1449
-
1450
- this.relationLayers = {
1451
- /**
1452
- * Shift a relation layer's display order.
1453
- * @param {string} relationLayerId - The relation layer ID
1454
- * @param {string} direction - The direction ("up" or "down")
1455
- */
1456
- shift: (relationLayerId, direction, auditMessage) =>
1457
- this._request('POST', `/api/v1/relation-layers/${relationLayerId}/shift`, { auditMessage,
1458
- body: bodyOf({ direction }),
1459
- }),
1460
- /**
1461
- * Create a new relation layer.
1462
- * @param {string} spanLayerId - The span layer ID
1463
- * @param {string} name - The name
1464
- */
1465
- create: (spanLayerId, name, auditMessage) =>
1466
- this._request('POST', '/api/v1/relation-layers', { auditMessage,
1467
- body: bodyOf({ 'span-layer-id': spanLayerId, name }),
1468
- }),
1469
- /**
1470
- * Set a configuration value for a layer in an editor namespace.
1471
- * @param {string} relationLayerId - The relation layer ID
1472
- * @param {string} namespace - The config namespace
1473
- * @param {string} configKey - The config key
1474
- * @param {any} configValue - Configuration value to set
1475
- */
1476
- setConfig: (relationLayerId, namespace, configKey, configValue, auditMessage) =>
1477
- this._request('PUT', `/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`, { auditMessage,
1478
- rawBody: configValue, skipResponseTransform: true,
1479
- }),
1480
- /**
1481
- * Remove a configuration value for a layer.
1482
- * @param {string} relationLayerId - The relation layer ID
1483
- * @param {string} namespace - The config namespace
1484
- * @param {string} configKey - The config key
1485
- */
1486
- deleteConfig: (relationLayerId, namespace, configKey, auditMessage) =>
1487
- this._request('DELETE', `/api/v1/relation-layers/${relationLayerId}/config/${namespace}/${configKey}`, { auditMessage,
1488
- skipResponseTransform: true,
1489
- }),
1490
- /**
1491
- * Get a relation layer by ID.
1492
- * @param {string} relationLayerId - The relation layer ID
1493
- * @param {string} [asOf] - Temporal query timestamp
1494
- */
1495
- get: (relationLayerId, asOf) =>
1496
- this._request('GET', `/api/v1/relation-layers/${relationLayerId}`, {
1497
- queryParams: { 'as-of': asOf },
1498
- }),
1499
- /**
1500
- * Delete a relation layer.
1501
- * @param {string} relationLayerId - The relation layer ID
1502
- */
1503
- delete: (relationLayerId, auditMessage) =>
1504
- this._request('DELETE', `/api/v1/relation-layers/${relationLayerId}`, { auditMessage }),
1505
- /**
1506
- * Update a relation layer's name.
1507
- * @param {string} relationLayerId - The relation layer ID
1508
- * @param {string} name - The name
1509
- */
1510
- update: (relationLayerId, name, auditMessage) =>
1511
- this._request('PATCH', `/api/v1/relation-layers/${relationLayerId}`, { auditMessage,
1512
- body: bodyOf({ name }),
1513
- }),
1514
- };
1515
-
1516
- this.tokens = {
1517
- /**
1518
- * Create a new token in a token layer. Tokens define text substrings
1519
- * using begin and end offsets. Tokens may be zero-width and may overlap.
1520
- * For tokens sharing the same begin, precedence controls the linear
1521
- * ordering.
1522
- *
1523
- * Offsets are 0-based indices in Unicode CODE POINTS (not UTF-16 code
1524
- * units): a supplementary-plane character (emoji, SMP script) is one
1525
- * position. JS strings are UTF-16, so do NOT use `str.length` /
1526
- * `str.substring` to compute offsets — count code points instead
1527
- * (e.g. `[...str].length`, or iterate with `codePointAt`).
1528
- * @param {string} tokenLayerId - The token layer ID
1529
- * @param {string} text - The text ID
1530
- * @param {number} begin - Start offset, inclusive (Unicode code points)
1531
- * @param {number} end - End offset, exclusive (Unicode code points)
1532
- * @param {number} [precedence] - Ordering precedence
1533
- * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
1534
- */
1535
- create: (tokenLayerId, text, begin, end, precedence, metadata, auditMessage) =>
1536
- this._request('POST', '/api/v1/tokens', { auditMessage,
1537
- body: bodyOf({ 'token-layer-id': tokenLayerId, text, begin, end, precedence, metadata }),
1538
- }),
1539
- /**
1540
- * Get a token.
1541
- * @param {string} tokenId - The token ID
1542
- * @param {string} [asOf] - Temporal query timestamp
1543
- */
1544
- get: (tokenId, asOf) =>
1545
- this._request('GET', `/api/v1/tokens/${tokenId}`, {
1546
- queryParams: { 'as-of': asOf },
1547
- }),
1548
- /**
1549
- * Delete a token and remove it from any spans. If this causes a span to
1550
- * have no remaining tokens, the span will also be deleted.
1551
- * @param {string} tokenId - The token ID
1552
- */
1553
- delete: (tokenId, auditMessage) =>
1554
- this._request('DELETE', `/api/v1/tokens/${tokenId}`, { auditMessage }),
1555
- /**
1556
- * Update a token.
1557
- * @param {string} tokenId - The token ID
1558
- * @param {number} [begin] - New start offset, inclusive (Unicode code points)
1559
- * @param {number} [end] - New end offset, exclusive (Unicode code points)
1560
- * @param {?number} [precedence] - Ordering precedence. Omit (undefined)
1561
- * to leave unchanged; pass a number to set; pass null explicitly to
1562
- * CLEAR it (revert to no explicit ordering). bodyOf keeps null but
1563
- * drops undefined, so the three cases map correctly to the server.
1564
- */
1565
- update: (tokenId, begin, end, precedence, auditMessage) =>
1566
- this._request('PATCH', `/api/v1/tokens/${tokenId}`, { auditMessage,
1567
- body: bodyOf({ begin, end, precedence }),
1568
- }),
1569
- /**
1570
- * Create multiple tokens in a single operation.
1571
- * @param {Array} body - The request body
1572
- * @returns {Promise<{ids: string[]}>} The created entity IDs, in input order. (bulkDelete resolves to no value.)
1573
- */
1574
- bulkCreate: (body, auditMessage) =>
1575
- this._request('POST', '/api/v1/tokens/bulk', { auditMessage, body }),
1576
- /**
1577
- * Delete multiple tokens in a single operation. Provide an array of IDs.
1578
- * @param {Array} body - The request body
1579
- */
1580
- bulkDelete: (body, auditMessage) =>
1581
- this._request('DELETE', '/api/v1/tokens/bulk', { auditMessage, body }),
1582
- /**
1583
- * Split a token at a Unicode code-point offset. The original token becomes the
1584
- * left half (keeps its ID, spans, vocab-links); the new right token's ID is returned.
1585
- * @param {string} tokenId - The token ID
1586
- * @param {number} position - Code-point offset to split at (strictly between begin and end)
1587
- */
1588
- split: (tokenId, position, auditMessage) =>
1589
- this._request('POST', `/api/v1/tokens/${tokenId}/split`, { auditMessage,
1590
- body: bodyOf({ position }),
1591
- }),
1592
- /**
1593
- * Merge two tokens. The left token (smaller begin) survives with the combined
1594
- * extent; the right is deleted and its spans/vocab-links are reparented to the left.
1595
- * On partitioning layers the tokens must be adjacent; on non-overlapping layers the
1596
- * merged extent must not engulf a third token.
1597
- * @param {string} tokenId - The anchor token ID
1598
- * @param {string} otherTokenId - The other token to merge in
1599
- */
1600
- merge: (tokenId, otherTokenId, auditMessage) =>
1601
- this._request('POST', `/api/v1/tokens/${tokenId}/merge`, { auditMessage,
1602
- body: bodyOf({ 'other-token-id': otherTokenId }),
1603
- }),
1604
- /**
1605
- * Shift a token's boundary. On partitioning layers the adjacent token is
1606
- * auto-adjusted to preserve the partition; on non-overlapping layers a shift that
1607
- * would create an overlap is rejected.
1608
- * @param {string} tokenId - The token ID
1609
- * @param {number} [begin] - New start offset, inclusive (Unicode code points)
1610
- * @param {number} [end] - New end offset, exclusive (Unicode code points)
1611
- */
1612
- shift: (tokenId, begin, end, auditMessage) =>
1613
- this._request('POST', `/api/v1/tokens/${tokenId}/shift`, { auditMessage,
1614
- body: bodyOf({ begin, end }),
1615
- }),
1616
- /**
1617
- * Replace all metadata for a token.
1618
- * @param {string} tokenId - The token ID
1619
- * @param {any} body - The request body
1620
- */
1621
- setMetadata: (tokenId, body, auditMessage) =>
1622
- this._request('PUT', `/api/v1/tokens/${tokenId}/metadata`, { auditMessage,
1623
- rawBody: body, skipResponseTransform: true,
1624
- }),
1625
- /**
1626
- * Remove all metadata from a token.
1627
- * @param {string} tokenId - The token ID
1628
- */
1629
- deleteMetadata: (tokenId, auditMessage) =>
1630
- this._request('DELETE', `/api/v1/tokens/${tokenId}/metadata`, { auditMessage,
1631
- skipResponseTransform: true,
1632
- }),
1633
- /**
1634
- * 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.
1635
- * @param {string} tokenId - The token ID
1636
- * @param {any} body - The metadata patch
1637
- */
1638
- patchMetadata: (tokenId, body, auditMessage) =>
1639
- this._request('PATCH', `/api/v1/tokens/${tokenId}/metadata`, { auditMessage,
1640
- rawBody: body, skipResponseTransform: true,
1641
- }),
1642
- };
1643
-
1644
- this.messages = {
1645
- /**
1646
- * Open a Server-Sent Events stream for a project.
1647
- * @param {string} projectId - The UUID of the project to listen to
1648
- * @param {function} onEvent - Callback function that receives (eventType, data). If it returns true, listening will stop.
1649
- * @param {string} [path] - Stream path under baseUrl (defaults to the project /listen bus; service channels pass their own).
1650
- * @returns {Object} SSE connection object with .close() and .getStats() methods
1651
- */
1652
- listen: (projectId, onEvent, path) =>
1653
- createSSEConnection(this, projectId, onEvent, path),
1654
-
1655
- /**
1656
- * Send a message to project listeners.
1657
- *
1658
- * `data` may be any JSON value and is sent VERBATIM (`rawBody`): a
1659
- * message payload is opaque application data, like `metadata` and
1660
- * `config`, so its keys must not be re-cased on the way out. Without
1661
- * this a key such as `case-marker` would reach listeners as
1662
- * `caseMarker` in JS and `case_marker` in Python. `listen` restores it
1663
- * verbatim on the way in.
1664
- * @param {string} projectId - The UUID of the project to send to
1665
- * @param {any} data - The message data to send
1666
- * @returns {Promise<any>} Response from the send operation
1667
- */
1668
- sendMessage: (projectId, data, auditMessage) =>
1669
- this._request('POST', `/api/v1/projects/${projectId}/message`, { auditMessage,
1670
- rawBody: { body: data },
1671
- }),
1672
-
1673
- /**
1674
- * Discover the services seen on a project (synchronous GET). Currently
1675
- * connected services carry `online: true`; previously-seen offline ones
1676
- * carry `online: false` plus a `lastSeenAt` stamp.
1677
- * @param {string} projectId - The UUID of the project to query
1678
- * @returns {Promise<Array>} Array of discovered service information
1679
- */
1680
- discoverServices: (projectId) =>
1681
- discoverServices(this, projectId),
1682
-
1683
- /**
1684
- * Forget a previously-seen (offline) service. Maintainer-only; 409 if
1685
- * the service is currently connected.
1686
- * @param {string} projectId - The UUID of the project
1687
- * @param {string} serviceId - The ID of the service to forget
1688
- * @returns {Promise<void>}
1689
- */
1690
- discardService: (projectId, serviceId) =>
1691
- discardService(this, projectId, serviceId),
1692
-
1693
- /**
1694
- * Register as a service and handle incoming work requests.
1695
- * @param {string} projectId - The UUID of the project to serve
1696
- * @param {Object} serviceInfo - Service information {serviceId, serviceName, description}
1697
- * @param {function} onServiceRequest - Callback (data, responseHelper)
1698
- * @param {Object} [extras] - Optional additional service metadata
1699
- * @returns {Object} Service registration object with .stop() method
1700
- */
1701
- serve: (projectId, serviceInfo, onServiceRequest, extras) =>
1702
- serve(this, projectId, serviceInfo, onServiceRequest, extras),
1703
-
1704
- /**
1705
- * Request a service to perform work and await its result.
1706
- * @param {string} projectId - The UUID of the project
1707
- * @param {string} serviceId - The ID of the service to request
1708
- * @param {any} data - The request data
1709
- * @param {number} [timeout] - Timeout in milliseconds (default: 10000)
1710
- * @param {function} [onProgress] - Called with each progress payload {percent, message}
1711
- * @returns {Promise<any>} Service response
1712
- */
1713
- requestService: (projectId, serviceId, data, timeout, onProgress) =>
1714
- requestService(this, projectId, serviceId, data, timeout, onProgress),
1715
- };
1716
-
1717
- /**
1718
- * Run a query over every project you can read.
1719
- *
1720
- * `body` is the query AST. Its keys follow the usual client convention
1721
- * (camelCase, e.g. `scope.projectIds`) and are converted to the wire
1722
- * format automatically; clause heads and variables are plain strings you
1723
- * write literally (e.g. `'span'`, `'?s1'`, `'vocab-link'`). Example:
1724
- *
1725
- * await client.query({
1726
- * find: ['?s1', '?s2'],
1727
- * where: [
1728
- * ['span', '?s1', { layer: posLayerId, value: 'NOUN' }],
1729
- * ['span', '?s2', { layer: posLayerId, value: 'VERB' }],
1730
- * ['covers', '?s1', '?t1'], ['covers', '?s2', '?t2'],
1731
- * ['precedes', '?t1', '?t2'],
1732
- * ],
1733
- * return: 'entities', // 'ids' (default) | 'entities' | 'count'
1734
- * limit: 100,
1735
- * });
1736
- *
1737
- * A `layer` is referenced by its id (its UUID) only — not by name or
1738
- * path. To match a layer by name, bind it with a `*-layer` clause (e.g.
1739
- * `['span-layer', '?sl', { name: 'pos' }]`) and use the variable.
1740
- *
1741
- * Optional keys: `scope` (restrict to projects by id, `{projectIds}`), `orderBy`
1742
- * (sort rows), and `bindings` (substitute `?name` placeholders with literals).
1743
- * `return` may also be an aggregate spec `{group, aggregates}`. See the query
1744
- * language reference.
1745
- *
1746
- * @param {Object} body - The query AST ({find, where, scope?, limit?, orderBy?,
1747
- * return?, bindings?}).
1748
- * @returns {Promise<Object>} For 'ids'/'entities': {columns, results, count, truncated}.
1749
- * For 'count': {return: 'count', count}. Entity cells are full entity objects
1750
- * (same shape as the GET endpoints).
1751
- */
1752
- this.query = (body, auditMessage) =>
1753
- this._request('POST', '/api/v1/query', { auditMessage, body });
1754
-
1755
- // Logical-operation groups (audit-log grouping). There is no create: a
1756
- // group row is made lazily by the first write carrying `?group-id=`
1757
- // (see beginOperation). Not an audited write, so no auditMessage arg.
1758
- this.operationGroups = {
1759
- /**
1760
- * Get a logical-operation group (its label + creator).
1761
- * @param {string} id - The group id
1762
- */
1763
- get: (id) =>
1764
- this._request('GET', `/api/v1/operation-groups/${id}`),
1765
- /**
1766
- * Relabel a logical-operation group after the fact. Owner or admin only.
1767
- * @param {string} id - The group id
1768
- * @param {string|null} message - The new label
1769
- */
1770
- update: (id, message) =>
1771
- this._request('PATCH', `/api/v1/operation-groups/${id}`, { body: { message } }),
1772
- };
1773
- }
1774
-
1775
- // --- Core methods ---
1776
-
1777
- async _request(method, path, options = {}) {
1778
- return makeRequest(this, method, path, options);
1779
- }
1780
-
1781
- /**
1782
- * Enter strict mode for a specific document, requiring document version
1783
- * headers so that conflicting concurrent writes are rejected.
1784
- * @param {string} documentId - The ID of the document to track versions for
1785
- */
1786
- enterStrictMode(documentId) {
1787
- this.strictModeDocumentId = documentId;
1788
- }
1789
-
1790
- /** Exit strict mode and stop tracking document versions for writes. */
1791
- exitStrictMode() {
1792
- this.strictModeDocumentId = null;
1793
- }
1794
-
1795
- /**
1796
- * Begin a LOGICAL OPERATION: a user-meaningful action ("Merge morphemes",
1797
- * "Re-transcribe") implemented as many low-level writes, possibly across
1798
- * several batches and even a service round-trip. Until `endOperation()`,
1799
- * every write is stamped with a client-minted `?group-id=` (and the message)
1800
- * so the audit log shows the whole run as ONE expandable entry labeled
1801
- * `message`, with each write's own description underneath.
1802
- *
1803
- * Grouping is orthogonal to batches: a batch is a transaction boundary, an
1804
- * operation is an intent boundary. An operation is NOT atomic — if write 3
1805
- * of 5 fails, writes 1–2 stay committed (and logged under the group). Use a
1806
- * batch inside the operation for any step that must be all-or-nothing.
1807
- *
1808
- * Nesting flattens: a `beginOperation` while one is open is a no-op that
1809
- * joins the outer operation (the outer label wins), and the matching
1810
- * `endOperation` is likewise a no-op. The label is recorded on the FIRST
1811
- * write, so an operation that is never ended (crash, closed tab) is still
1812
- * labeled in the log.
1813
- *
1814
- * @param {string} message - Human label for the operation.
1815
- * @param {object} [opts] - Optional `{ id }`: adopt an existing group id instead of minting one (a service joining the requester's operation; `requestService` propagates an open operation to the service automatically).
1816
- * @returns {string} The operation's group id.
1817
- */
1818
- beginOperation(message, { id } = {}) {
1819
- if (this.operationGroup) {
1820
- this.operationGroup.depth += 1;
1821
- return this.operationGroup.id;
1822
- }
1823
- this.operationGroup = {
1824
- id: id || crypto.randomUUID(),
1825
- message: message == null ? null : String(message),
1826
- depth: 1,
1827
- written: false,
1828
- refined: undefined,
1829
- };
1830
- return this.operationGroup.id;
1831
- }
1832
-
1833
- /**
1834
- * End the current logical operation. With no argument this is purely local
1835
- * (no request). Pass a refined `message` to relabel the group now that the
1836
- * outcome is known (e.g. `endOperation('Merged 3 morphemes')`) — that sends
1837
- * one PATCH, skipped if the operation never wrote anything. A refine from a
1838
- * nested (flattened) `endOperation` is ignored; the outer label wins.
1839
- * @param {string} [message] - Optional refined label.
1840
- * @returns {Promise<void>}
1841
- */
1842
- async endOperation(message) {
1843
- const group = this.operationGroup;
1844
- if (!group) return;
1845
- if (group.depth > 1) {
1846
- group.depth -= 1;
1847
- return;
1848
- }
1849
- this.operationGroup = null;
1850
- const refined = message !== undefined ? message : group.refined;
1851
- if (refined !== undefined && group.written) {
1852
- try {
1853
- await this.operationGroups.update(group.id, refined);
1854
- } catch (e) {
1855
- // 404: the group never materialized server-side (every tagged write
1856
- // failed or a batch was aborted). Nothing to relabel — not an error.
1857
- if (e.status !== 404) throw e;
1858
- }
1859
- }
1860
- }
1861
-
1862
- /**
1863
- * Run `fn` as one logical operation (see beginOperation), ending it when
1864
- * `fn` settles — including on throw. `fn` receives a `setMessage(msg)`
1865
- * callback to refine the label once the outcome is known.
1866
- *
1867
- * await client.withOperation('Merge morphemes', async (setMessage) => {
1868
- * await client.batched(() => { ... });
1869
- * setMessage(`Merged ${n} morphemes`);
1870
- * });
1871
- *
1872
- * @param {string} message - Human label for the operation.
1873
- * @param {function} fn - The work to run; receives `setMessage(msg)` to refine the label once the outcome is known.
1874
- * @returns {Promise<any>} Whatever `fn` resolves to.
1875
- */
1876
- async withOperation(message, fn) {
1877
- this.beginOperation(message);
1878
- const group = this.operationGroup;
1879
- const setMessage = (msg) => { if (group.depth === 1) group.refined = msg; };
1880
- try {
1881
- return await fn(setMessage);
1882
- } finally {
1883
- await this.endOperation();
1884
- }
1885
- }
1886
-
1887
- /** Begin a batch of operations. Subsequent API calls will be queued. */
1888
- beginBatch() {
1889
- this.isBatching = true;
1890
- this.batchOperations = [];
1891
- // Strict mode stamps the expected document-version on the FIRST write of
1892
- // the batch only (see _request) — reset the marker per batch.
1893
- this.batchVersionStamped = false;
1894
- }
1895
-
1896
- /**
1897
- * Submit all queued batch operations as a single batch request, executed
1898
- * atomically. If any operation fails, all changes are rolled back.
1899
- * @returns {Promise<Array>} Array of results corresponding to each operation
1900
- */
1901
- async submitBatch() {
1902
- if (!this.isBatching) {
1903
- throw new Error('No active batch. Call beginBatch() first.');
1904
- }
1905
-
1906
- if (this.batchOperations.length === 0) {
1907
- this.isBatching = false;
1908
- return [];
1909
- }
1910
-
1911
- try {
1912
- let url = `${this.baseUrl}/api/v1/batch`;
1913
- const body = this.batchOperations.map(op => ({
1914
- path: op.path,
1915
- method: op.method.toUpperCase(),
1916
- ...(op.body && { body: op.body }),
1917
- }));
1918
-
1919
- const fetchOptions = {
1920
- method: 'POST',
1921
- headers: {
1922
- 'Authorization': `Bearer ${this.token}`,
1923
- 'Content-Type': 'application/json',
1924
- },
1925
- body: JSON.stringify(body),
1926
- };
1927
- const signal = timeoutSignal(this.timeout);
1928
- if (signal) fetchOptions.signal = signal;
1929
-
1930
- try {
1931
- const response = await fetch(url, fetchOptions);
1932
- if (!response.ok) {
1933
- throw makeHttpError(response, await parseErrorBody(response), url, 'POST');
1934
- }
1935
-
1936
- const results = await response.json();
1937
-
1938
- // Extract document versions from each batch response
1939
- for (const result of results) {
1940
- if (result.headers && result.headers['X-Document-Versions']) {
1941
- try {
1942
- const versionsMap = JSON.parse(result.headers['X-Document-Versions']);
1943
- if (typeof versionsMap === 'object' && versionsMap !== null) {
1944
- // Clone once per response, then merge — not once per entry.
1945
- this.documentVersions = { ...this.documentVersions, ...versionsMap };
1946
- }
1947
- } catch (e) {
1948
- console.warn('Failed to parse document versions header from batch response:', e);
1949
- }
1950
- }
1951
- }
1952
-
1953
- return results.map(result => transformResponse(result));
1954
- } catch (error) {
1955
- if (error.status) throw error;
1956
- throw makeNetworkError(error, url, 'POST');
1957
- }
1958
- } finally {
1959
- this.isBatching = false;
1960
- this.batchOperations = [];
1961
- }
1962
- }
1963
-
1964
- /** Abort the current batch without executing any operations. */
1965
- abortBatch() {
1966
- this.isBatching = false;
1967
- this.batchOperations = [];
1968
- }
1969
-
1970
- /**
1971
- * Check if currently in batch mode.
1972
- * @returns {boolean}
1973
- */
1974
- isBatchMode() {
1975
- return this.isBatching;
1976
- }
1977
-
1978
- /**
1979
- * Run `fn` with a batch open, then submit all queued ops as ONE atomic
1980
- * request — or abort the batch if `fn` throws, so a half-open batch can never
1981
- * silently swallow later non-batch calls. `fn` makes the (queued) client
1982
- * calls; it must NOT call submitBatch itself. Resolves to the batch results
1983
- * array (`[]` if `fn` queued nothing).
1984
- *
1985
- * const [sentRes, wordRes] = await client.batched(async () => {
1986
- * client.tokens.bulkCreate(sentenceOps);
1987
- * client.tokens.bulkCreate(wordOps);
1988
- * });
1989
- *
1990
- * Server-side a batch runs sequentially in one transaction (a child op sees
1991
- * parents created earlier in the same `fn`; any op's failure rolls the whole
1992
- * batch back). Not nestable. Named `batched()` because `client.batch` is the
1993
- * low-level batch resource.
1994
- * @param {() => (void | Promise<void>)} fn
1995
- * @returns {Promise<Array>}
1996
- */
1997
- async batched(fn) {
1998
- this.beginBatch();
1999
- try {
2000
- await fn();
2001
- } catch (e) {
2002
- // fn failed: drop the queued ops so the client leaves batch mode and
2003
- // later plain calls don't queue into a never-submitted batch.
2004
- if (this.isBatchMode()) this.abortBatch();
2005
- throw e;
2006
- }
2007
- return this.submitBatch();
2008
- }
2009
-
2010
- /**
2011
- * Authenticate and return a new client instance with token. This is the
2012
- * single auth entry point — there is no `client.login` resource.
2013
- * @param {string} baseUrl - The base URL for the API
2014
- * @param {string} userId - User ID for authentication
2015
- * @param {string} password - Password for authentication
2016
- * @param {object} [options] - Client options forwarded to the constructor (e.g. { timeout })
2017
- * @returns {Promise<PlaidClient>} - Authenticated client instance
2018
- */
2019
- /**
2020
- * Build the link to hand someone for an invite code. The server never sees
2021
- * an app URL, so the app that minted the invite is the one that names it.
2022
- * Both SPAs use HashRouter, so the code rides in the fragment — which also
2023
- * keeps it out of server access logs.
2024
- * @param {string} appUrl - Where the SPA lives, e.g. "https://plaid.example.org/igt/"
2025
- * @param {string} code - The code returned by invites.create()
2026
- * @returns {string}
2027
- */
2028
- static inviteUrl(appUrl, code) {
2029
- return `${appUrl.replace(/#.*$/, '').replace(/\/$/, '')}/#/invite/${encodeURIComponent(code)}`;
2030
- }
2031
-
2032
- /**
2033
- * Describe an invite code, with NO authentication — this is what a signup
2034
- * page calls before the redeemer has an account. Returns the kind of link
2035
- * ("signup" or "password-reset"), its status ("active", "used", "expired",
2036
- * "revoked"), and the project it grants access to, if any.
2037
- *
2038
- * Throws a 404-shaped error if the code is unknown. A known-but-dead code
2039
- * resolves normally with a non-"active" status, so the page can say why.
2040
- * @param {string} baseUrl - The API base URL
2041
- * @param {string} code - The invite code
2042
- * @param {object} [options]
2043
- * @returns {Promise<{kind: string, status: string, expiresAt: string, projectName?: string, projectRole?: string, username?: string, grantAdmin: boolean}>}
2044
- */
2045
- static async lookupInvite(baseUrl, code, options = {}) {
2046
- return anonymousPost(baseUrl, '/api/v1/invite-codes/lookup', { code }, options);
2047
- }
2048
-
2049
- /**
2050
- * Redeem an invite code, with NO authentication.
2051
- *
2052
- * For a signup invite, pass `username` and `password` to create the account;
2053
- * the invite's grants are applied in the same transaction. For a password
2054
- * reset link, pass `password` only. Resolves to a logged-in client, exactly
2055
- * like login() — the redeemer just chose these credentials, so there is no
2056
- * reason to send them to a login form to retype them.
2057
- * @param {string} baseUrl - The API base URL
2058
- * @param {string} code - The invite code
2059
- * @param {object} credentials
2060
- * @param {string} [credentials.username] - Desired username (signup only)
2061
- * @param {string} credentials.password - Desired password (min 8 characters)
2062
- * @param {object} [options]
2063
- * @returns {Promise<{client: PlaidClient, userId: string, kind: string}>}
2064
- */
2065
- static async redeemInvite(baseUrl, code, { username, password } = {}, options = {}) {
2066
- const data = await anonymousPost(
2067
- baseUrl, '/api/v1/invite-codes/redeem',
2068
- bodyOf({ code, username, password }), options,
2069
- );
2070
- return {
2071
- client: new PlaidClient(baseUrl.replace(/\/$/, ''), data.token || '', options),
2072
- userId: data.userId,
2073
- kind: data.kind,
2074
- };
2075
- }
2076
-
2077
- static async login(baseUrl, userId, password, options = {}) {
2078
- baseUrl = baseUrl.replace(/\/$/, '');
2079
- const url = `${baseUrl}/api/v1/login`;
2080
- try {
2081
- const fetchOptions = {
2082
- method: 'POST',
2083
- headers: { 'Content-Type': 'application/json' },
2084
- body: JSON.stringify({ 'user-id': userId, password }),
2085
- };
2086
- const signal = timeoutSignal(options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT_MS);
2087
- if (signal) fetchOptions.signal = signal;
9
+ /**
10
+ * @typedef {import("./types.js").TextEditOp} TextEditOp
11
+ * @typedef {import("./types.js").TextGap} TextGap
12
+ * @typedef {import("./types.js").MetadataOp} MetadataOp
13
+ * @typedef {import("./types.js").LayerConstraint} LayerConstraint
14
+ * @typedef {import("./types.js").ConstraintWriteOptions} ConstraintWriteOptions
15
+ * @typedef {import("./types.js").ConstraintViolation} ConstraintViolation
16
+ * @typedef {import("./types.js").Requester} Requester
17
+ * @typedef {import("./types.js").BatchRef} BatchRef
18
+ * @typedef {import("./types.js").ServiceParam} ServiceParam
19
+ * @typedef {import("./types.js").ServiceInfo} ServiceInfo
20
+ * @typedef {import("./types.js").DiscoveredService} DiscoveredService
21
+ * @typedef {import("./types.js").ResponseHelper} ResponseHelper
22
+ * @typedef {import("./types.js").ServiceRegistration} ServiceRegistration
23
+ * @typedef {import("./types.js").ServiceStatusEvent} ServiceStatusEvent
24
+ * @typedef {import("./types.js").OperationGroupsBundle} OperationGroupsBundle
25
+ * @typedef {import("./types.js").PlaidClientOptions} PlaidClientOptions
26
+ * @typedef {import("./types.js").OperationKind} OperationKind
27
+ * @typedef {import("./types.js").KeySeed} KeySeed
28
+ * @typedef {import("./types.js").PlaidBatch} PlaidBatch
29
+ * @typedef {import("./types.js").AuditPageOptions} AuditPageOptions
30
+ * @typedef {import("./types.js").VocabItemRestoreSummary} VocabItemRestoreSummary
31
+ * @typedef {import("./types.js").UserDataEntry} UserDataEntry
32
+ * @typedef {import("./types.js").AdminUserDataEntry} AdminUserDataEntry
33
+ * @typedef {import("./types.js").ActivityTallyRow} ActivityTallyRow
34
+ * @typedef {import("./types.js").ConstraintRepair} ConstraintRepair
35
+ * @typedef {import("./types.js").ValueSetRule} ValueSetRule
36
+ * @typedef {import("./types.js").RefusedValues} RefusedValues
37
+ * @typedef {import("./types.js").CommentableType} CommentableType
38
+ * @typedef {import("./types.js").Comment} Comment
39
+ * @typedef {import("./types.js").Guideline} Guideline
40
+ * @typedef {import("./types.js").ClientEventType} ClientEventType
41
+ * @typedef {import("./types.js").ClientEvent} ClientEvent
42
+ * @typedef {import("./types.js").Invite} Invite
43
+ * @typedef {import("./types.js").ServerLimits} ServerLimits
44
+ * @typedef {import("./types.js").ServerHealth} ServerHealth
45
+ * @typedef {import("./types.js").RateLimitBucket} RateLimitBucket
46
+ * @typedef {import("./types.js").RateLimitSnapshot} RateLimitSnapshot
47
+ * @typedef {import("./types.js").BackupStatus} BackupStatus
48
+ * @typedef {import("./types.js").ServerReport} ServerReport
49
+ * @typedef {import("./types.js").LiveLog} LiveLog
50
+ * @typedef {import("./types.js").SSEConnection} SSEConnection
51
+ * @typedef {import("./types.js").SSEStats} SSEStats
52
+ * @typedef {import("./types.js").AtomicValue} AtomicValue
53
+ * @typedef {import("./types.js").Created} Created
54
+ * @typedef {import("./types.js").Project} Project
55
+ * @typedef {import("./types.js").ProjectTextLayer} ProjectTextLayer
56
+ * @typedef {import("./types.js").ProjectTokenLayer} ProjectTokenLayer
57
+ * @typedef {import("./types.js").ProjectSpanLayer} ProjectSpanLayer
58
+ * @typedef {import("./types.js").ProjectRelationLayer} ProjectRelationLayer
59
+ * @typedef {import("./types.js").ProjectVocab} ProjectVocab
60
+ * @typedef {import("./types.js").PlaidDocument} PlaidDocument
61
+ * @typedef {import("./types.js").DocumentTextLayer} DocumentTextLayer
62
+ * @typedef {import("./types.js").DocumentTokenLayer} DocumentTokenLayer
63
+ * @typedef {import("./types.js").DocumentSpanLayer} DocumentSpanLayer
64
+ * @typedef {import("./types.js").PlaidText} PlaidText
65
+ * @typedef {import("./types.js").TextReshape} TextReshape
66
+ * @typedef {import("./types.js").Token} Token
67
+ * @typedef {import("./types.js").Span} Span
68
+ * @typedef {import("./types.js").Relation} Relation
69
+ * @typedef {import("./types.js").VocabItem} VocabItem
70
+ * @typedef {import("./types.js").VocabLink} VocabLink
71
+ * @typedef {import("./types.js").VocabLayer} VocabLayer
72
+ * @typedef {import("./types.js").TextLayer} TextLayer
73
+ * @typedef {import("./types.js").TokenLayer} TokenLayer
74
+ * @typedef {import("./types.js").SpanLayer} SpanLayer
75
+ * @typedef {import("./types.js").RelationLayer} RelationLayer
76
+ * @typedef {import("./types.js").MetadataRecords} MetadataRecords
77
+ * @typedef {import("./types.js").LayerRecords} LayerRecords
78
+ * @typedef {import("./types.js").User} User
79
+ * @typedef {import("./types.js").ProjectListEntry} ProjectListEntry
80
+ * @typedef {import("./types.js").DocumentListEntry} DocumentListEntry
81
+ * @typedef {import("./types.js").ApiToken} ApiToken
82
+ * @typedef {import("./types.js").AuditUser} AuditUser
83
+ * @typedef {import("./types.js").AuditTarget} AuditTarget
84
+ * @typedef {import("./types.js").AuditOp} AuditOp
85
+ * @typedef {import("./types.js").AuditEntry} AuditEntry
86
+ * @typedef {import("./types.js").LockGrant} LockGrant
87
+ * @typedef {import("./types.js").DocumentRestoreSummary} DocumentRestoreSummary
88
+ * @typedef {import("./types.js").RestoreCounts} RestoreCounts
89
+ * @typedef {import("./types.js").OperationGroup} OperationGroup
90
+ * @typedef {import("./types.js").QueryResult} QueryResult
91
+ */
2088
92
 
2089
- const response = await fetch(url, fetchOptions);
2090
- if (!response.ok) {
2091
- throw makeHttpError(response, await parseErrorBody(response), url, 'POST');
2092
- }
93
+ /**
94
+ * @template [T=any]
95
+ * @typedef {import("./types.js").Page<T>} Page
96
+ */
2093
97
 
2094
- const data = await response.json();
2095
- const token = data.token || '';
2096
- return new PlaidClient(baseUrl, token, options);
2097
- } catch (error) {
2098
- if (error.status) throw error;
2099
- throw makeNetworkError(error, url, 'POST');
2100
- }
2101
- }
2102
- }
98
+ import { PlaidClient } from "./client.js";
2103
99
 
2104
100
  export default PlaidClient;
2105
101
  export { PlaidClient };
102
+ export { BACKUP_TIMEOUT_MS } from "./apiServer.js";
103
+ export { valueSetAllows, violationCountOf, violationsOf } from "./constraints.js";
2106
104
 
2107
105
  // Unicode code-point helpers for text offsets (token begin/end are code-point
2108
106
  // indices). See ./codepoint.js.
2109
- export { cpLength, cpSlice, cpSlicer, utf16ToCp, cpToUtf16, cpIndexOf } from './codepoint.js';
2110
- export { PLAID_NAMESPACE, ROLE_KEY, ROLES, readRole, findByRole } from './roles.js';
107
+ export {
108
+ cpLength,
109
+ cpSlice,
110
+ cpSlicer,
111
+ utf16ToCp,
112
+ cpIndexOf,
113
+ composeText,
114
+ } from "./codepoint.js";
115
+ export { composeTextEdits, gapsToOps, applyTextOps } from "./textEdits.js";
116
+ export {
117
+ PLAID_NAMESPACE,
118
+ ROLE_KEY,
119
+ PRESERVE_ON_SPLIT_KEY,
120
+ SPLIT_ON_SPACE_KEY,
121
+ IGNORED_TOKENS_KEY,
122
+ TARTAN_KEY,
123
+ RESEARCH_KEY,
124
+ readResearchTelemetry,
125
+ ROLES,
126
+ readRole,
127
+ readIgnoredTokens,
128
+ findByRole,
129
+ } from "./roles.js";
2111
130
  // Service self-description helpers: filter discovered services by task, read a
2112
131
  // service's parameter schema/summary, and build/coerce form values. See
2113
132
  // ./serviceSchema.js.
@@ -2119,20 +138,79 @@ export {
2119
138
  getServiceSummary,
2120
139
  buildDefaultValues,
2121
140
  coerceParamValues,
2122
- } from './serviceSchema.js';
2123
- // Provenance: the cross-app convention for machine-provided vs human-labeled
2124
- // information (flat prov/provSource/provConfirmed metadata; absence = human),
2125
- // plus the machine-writer contract. See ./provenance.js and the manual,
2126
- // "Provenance".
141
+ } from "./serviceSchema.js";
142
+ // Cooperative cancellation for a service AUTHORED in JS: `progress()` is a
143
+ // checkpoint that throws ServiceCancelled once the requester stops the
144
+ // request, and `critical()` holds that off around writes. See ./services.js
145
+ // and the manual, "Stopping a request".
146
+ export { ServiceCancelled, createCancelScope } from "./services.js";
147
+ // The two messages a write that got no answer is reported with: one the
148
+ // server may have saved, one never sent. See ./services.js.
149
+ export { UNKNOWN_OUTCOME, UNREACHABLE } from "./services.js";
150
+ // Who asked for a service run, named in its History label and in what it
151
+ // stores. See ./requester.js.
152
+ export { REQUESTED_BY, makeRequester, requesterOf } from "./requester.js";
153
+ // Document locks: `client.documents.locked()` holds one for a block of work and
154
+ // renews it while the block runs. DocumentLockLost is what a write throws once
155
+ // a renewal has failed. See ./documentLock.js.
156
+ export { DocumentLock, DocumentLockLost } from "./documentLock.js";
157
+ // Provenance: the cross-app convention for who made an annotation (flat
158
+ // prov/provSource/provConfirmed metadata; absence = a verifier; 'inferred' =
159
+ // a machine; 'contributed' = a person whose work is reviewed), plus the
160
+ // machine-writer contract. See ./provenance.js and the manual, "Provenance".
2127
161
  export {
2128
162
  PROV,
2129
163
  PROV_STATES,
2130
- PROV_CONFIRMED,
164
+ PROVENANCE_KEYS,
2131
165
  stampInferred,
2132
166
  confirmedInferred,
167
+ stampContributed,
2133
168
  provState,
169
+ provOrigin,
2134
170
  isMachine,
2135
171
  isProtected,
2136
- verifyOnEdit,
172
+ needsReview,
173
+ contributeOnEdit,
2137
174
  serviceSource,
2138
- } from './provenance.js';
175
+ REVIEW_KEY,
176
+ readReview,
177
+ reviewsAnyone,
178
+ projectRole,
179
+ isReviewed,
180
+ withReviewedUser,
181
+ CONFIDENCE_KEY,
182
+ DEFAULT_CONFIDENCE_THRESHOLD,
183
+ readConfidenceThreshold,
184
+ machineProb,
185
+ isDoubtful,
186
+ VALUE_PROBS_KEY,
187
+ FORM_PROBS_KEY,
188
+ readValueProbs,
189
+ writerPolicy,
190
+ } from "./provenance.js";
191
+ // Lexicon entry forms are stored without affix or clitic markers, the marker
192
+ // drawn from the entry's morph type. Every entry-form writer stores what
193
+ // entryForm returns. See ./entryForms.js.
194
+ export {
195
+ MORPH_TYPES,
196
+ entryForm,
197
+ readAffixMarkers,
198
+ decorateWithAffixMarkers,
199
+ } from "./entryForms.js";
200
+ // Metadata ops: the body of a metadata PATCH and of a bulk update entry's
201
+ // `metadata`. See ./metadataOps.js.
202
+ export {
203
+ metadataOps,
204
+ applyMetadataOps,
205
+ mergeMetadata,
206
+ isReservedMetadataKey,
207
+ } from "./metadataOps.js";
208
+ // The ids a create or bulk create answered with, off the call or its batch
209
+ // result. See ./created.js.
210
+ export { createdId, createdIds } from "./created.js";
211
+ // UUIDv7 ids for what a client creates. See ./ids.js and the manual,
212
+ // "Retrying a write". uuidv4 works where crypto.randomUUID does not (a page
213
+ // served over plain HTTP from any host but localhost).
214
+ export { uuidv4, uuidv7, normalizeUserId } from "./ids.js";
215
+ export { wasReplayed } from "./replayed.js";
216
+ export { MAX_BATCH_OPS } from "./batch.js";