@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
@@ -0,0 +1,551 @@
1
+ /**
2
+ * The document bundles: documents (with their locks, media and history) and
3
+ * texts, each a factory of the client (client.js installs them).
4
+ */
5
+
6
+ import { withDocumentLock } from "./documentLock.js";
7
+ import { auditMethods, bodyOf, csvParam, metadataMethods } from "./families.js";
8
+
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
+ */
92
+
93
+ /**
94
+ * @template [T=any]
95
+ * @typedef {import("./types.js").Page<T>} Page
96
+ */
97
+
98
+ /** @param {import("./client.js").PlaidClient} client */
99
+ export function documentsApi(client) {
100
+ return {
101
+ /**
102
+ * Get a document. Set `includeBody` to true to include all data.
103
+ *
104
+ * A document with a recording has `mediaUrl`, and `mediaDuration`, the
105
+ * recording's length in seconds, when the server could read it from the
106
+ * file at upload (WAV, MP4, M4A, MOV, FLAC, MP3).
107
+ *
108
+ * `layers` narrows a body read to the layers you name (ids of any kind:
109
+ * text, token, span, or relation). A layer comes back when it is named
110
+ * or is an ancestor of a named layer, and carries its own
111
+ * texts/tokens/spans/relations/vocabs only when it is itself named — so
112
+ * name the text layer too if you also want the text body. An id that is
113
+ * not a layer of this document's project is an error, not a quietly
114
+ * smaller response. Requires `includeBody`.
115
+ * @param {string} documentId - The document ID
116
+ * @param {boolean} [includeBody] - Include document body data
117
+ * @param {string} [asOf] - Temporal query timestamp
118
+ * @param {string[]|string} [layers] - Layer ids to restrict a body read to
119
+ * @returns {Promise<PlaidDocument>}
120
+ */
121
+ get: (documentId, includeBody, asOf, layers) =>
122
+ client._request("GET", `/api/v1/documents/${documentId}`, {
123
+ queryParams: {
124
+ "include-body": includeBody,
125
+ "as-of": asOf,
126
+ layers: csvParam(layers),
127
+ },
128
+ }),
129
+ /**
130
+ * Get the media file for a document, as a Blob whose `type` is the
131
+ * served media type. Media is not versioned, so there is no as-of form:
132
+ * the route refuses the parameter. A busy core (503) is asked again, as
133
+ * for any read. To play a recording, stream it from `mediaLink` instead.
134
+ * @param {string} documentId - The document ID
135
+ * @param {object} [options]
136
+ * @param {number | null} [options.timeout] - This read's timeout in ms, in place of the client's. A
137
+ * recording can take longer than the default 30 s to come down: null or 0 waits as long as it takes.
138
+ * @param {(progress: {loaded: number, total: number | null}) => void} [options.onProgress] - Called with
139
+ * `{ loaded, total }` (bytes, total null when unknown) as the file comes down.
140
+ * @returns {Promise<Blob>}
141
+ */
142
+ getMedia: (documentId, { timeout, onProgress } = {}) =>
143
+ client._request("GET", `/api/v1/documents/${documentId}/media`, {
144
+ blobResponse: true,
145
+ onDownloadProgress: onProgress,
146
+ timeout,
147
+ }),
148
+ /**
149
+ * Create a new document in a project.
150
+ * @param {string} projectId - The project ID
151
+ * @param {string} name - The name
152
+ * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
153
+ * @param {string} [auditMessage] - Audit message for this write
154
+ * @param {object} [opts]
155
+ * @param {string} [opts.id] - The new entity's id, minted by the caller (see uuidv7)
156
+ * @param {boolean} [opts.freeName] - A name another document of the project holds, ignoring case, becomes
157
+ * the first free "name (2)", "name (3)" and on. Without it such a name is refused with 422
158
+ * and `name-taken`.
159
+ * @returns {Promise<{id: string, name: string}>} The new id and the name stored
160
+ */
161
+ create: (projectId, name, metadata, auditMessage, { id, freeName } = {}) =>
162
+ client._request("POST", "/api/v1/documents", {
163
+ auditMessage,
164
+ body: bodyOf({
165
+ id,
166
+ "project-id": projectId,
167
+ name,
168
+ metadata,
169
+ "free-name": freeName || undefined,
170
+ }),
171
+ }),
172
+ /**
173
+ * Update a document's name. A name another document of the project holds,
174
+ * ignoring case,
175
+ * is refused with 422 and `name-taken`.
176
+ * @param {string} documentId - The document ID
177
+ * @param {string} name - The name
178
+ * @param {string} [auditMessage] - Audit message for this write
179
+ * @returns {Promise<PlaidDocument>}
180
+ */
181
+ update: (documentId, name, auditMessage) =>
182
+ client._request("PATCH", `/api/v1/documents/${documentId}`, {
183
+ auditMessage,
184
+ body: bodyOf({ name }),
185
+ }),
186
+ /**
187
+ * Delete a document and all data contained.
188
+ * @param {string} documentId - The document ID
189
+ * @param {string} [auditMessage] - Audit message for this write
190
+ * @returns {Promise<void>}
191
+ */
192
+ delete: (documentId, auditMessage) =>
193
+ client._request("DELETE", `/api/v1/documents/${documentId}`, {
194
+ auditMessage,
195
+ }),
196
+ /**
197
+ * Check the lock status of a document.
198
+ * @param {string} documentId - The document ID
199
+ * @returns {Promise<{userId: string, expiresAt: number} | void>}
200
+ */
201
+ checkLock: (documentId) =>
202
+ client._request("GET", `/api/v1/documents/${documentId}/lock`),
203
+ /**
204
+ * Acquire a document lock as a new holder.
205
+ *
206
+ * The answer's `lockId` names the holder: `renewLock` and `releaseLock`
207
+ * take it. While the lock is held, a second acquire is refused with
208
+ * HTTP 423, whoever makes it, this user included.
209
+ *
210
+ * `newLockId` names the holder from the client's side (a fresh UUID). An
211
+ * acquire whose answer never arrived can then be sent again, which
212
+ * answers 200 while that holder has the lock, or released. Without it
213
+ * the server mints the id, and a lost answer leaves a lock nobody can
214
+ * release until it expires.
215
+ *
216
+ * outOfBand: the lock is a signal, not project data (see the note at
217
+ * the top of http.js). Queued on a batch it would be taken only at
218
+ * submit, after every write it was meant to guard, and until then answer
219
+ * success to a caller that does not hold it and cannot see the 423
220
+ * saying somebody else does.
221
+ * @param {string} documentId - The document ID
222
+ * @param {string} [auditMessage]
223
+ * @param {string} [newLockId] - A holder id this client minted
224
+ * @returns {Promise<LockGrant>}
225
+ */
226
+ acquireLock: (documentId, auditMessage, newLockId) =>
227
+ client._request("POST", `/api/v1/documents/${documentId}/lock`, {
228
+ ...(newLockId !== undefined && newLockId !== null
229
+ ? { queryParams: { "new-lock-id": newLockId } }
230
+ : {}),
231
+ auditMessage,
232
+ outOfBand: true,
233
+ }),
234
+ /**
235
+ * Renew the lock `lockId` holds while it is live. HTTP 423 if another
236
+ * holder has it, and also once it has expired or been dropped, even when
237
+ * nobody holds the document now: a renewal never takes a free document.
238
+ * outOfBand, for the same reason as acquireLock.
239
+ * @param {string} documentId - The document ID
240
+ * @param {string} lockId - The `lockId` acquireLock answered with
241
+ * @param {string} [auditMessage] - Audit message for this write
242
+ * @returns {Promise<LockGrant>}
243
+ */
244
+ renewLock: (documentId, lockId, auditMessage) =>
245
+ client._request("POST", `/api/v1/documents/${documentId}/lock`, {
246
+ queryParams: { "lock-id": lockId },
247
+ auditMessage,
248
+ outOfBand: true,
249
+ }),
250
+ /**
251
+ * Release the lock `lockId` holds. Idempotent: a lock that holder no
252
+ * longer has is left alone. outOfBand, for the same reason as
253
+ * acquireLock: queued, the lock would be held until the batch submits,
254
+ * and not released at all if it aborts.
255
+ * @param {string} documentId - The document ID
256
+ * @param {string} lockId - The `lockId` acquireLock answered with
257
+ * @param {string} [auditMessage] - Audit message for this write
258
+ * @returns {Promise<void>}
259
+ */
260
+ releaseLock: (documentId, lockId, auditMessage) =>
261
+ client._request("DELETE", `/api/v1/documents/${documentId}/lock`, {
262
+ queryParams: { "lock-id": lockId },
263
+ auditMessage,
264
+ outOfBand: true,
265
+ }),
266
+ /**
267
+ * Hold this document's server-enforced lock for the length of `fn`,
268
+ * releasing it on the way out (including on error). Resolves to what
269
+ * `fn` returned.
270
+ *
271
+ * await client.documents.locked(docId, async () => {
272
+ * // delete + recreate tokens
273
+ * });
274
+ *
275
+ * Wrap any multi-step, server-side mutation of a document that must not
276
+ * interleave with a human editor or another service, e.g. a parser or
277
+ * tokenizer that deletes and recreates a document's tokens, spans and
278
+ * relations. A single atomic call does not need it. While the lock is
279
+ * held, writes to the document by ANOTHER user are refused with HTTP
280
+ * 423; the holder's own writes pass and renew it. If anyone already
281
+ * holds it, another block of this same user included, this rejects with
282
+ * a readable 423 and `fn` does not run.
283
+ *
284
+ * The lock is renewed for as long as `fn` runs, so work that computes
285
+ * for minutes before it writes holds the lock the whole time rather than
286
+ * only for its first minute. If a renewal fails the lock is gone: every
287
+ * later write from this client throws `DocumentLockLost`, and a block
288
+ * that got to the end anyway ends with that error rather than reporting
289
+ * success. `fn` receives a lock handle and may read `lock.lost` (or call
290
+ * `lock.raiseIfLost()`) to give up sooner.
291
+ *
292
+ * The lock is per HOLDER: each block is its own, named by
293
+ * `lock.lockId`, and only that id renews or releases it. NOT
294
+ * re-entrant: a nested `locked()` block on one document is a second
295
+ * holder and gets the 423. Lock at exactly one level per call path.
296
+ * The block mints its holder id and sends it with the acquire, so an
297
+ * acquire whose answer was lost is sent again (up to three tries) and,
298
+ * if none is answered, released rather than left to expire. A
299
+ * lost lock is recorded on the CLIENT, like strict mode, so it stops
300
+ * every write the client makes (on any batch of it too) and not only the
301
+ * ones this block makes.
302
+ *
303
+ * A core restart drops every lock, and until the next renewal finds it
304
+ * gone (up to half the lock window) this block's writes still pass, since
305
+ * writes carry no lock id. When a write must not land past that point,
306
+ * hold the document in strict mode too (`enterStrictMode`), so such a
307
+ * write is refused with a 409.
308
+ * @template T
309
+ * @param {string} documentId - The document ID
310
+ * @param {(lock: import("./documentLock.js").DocumentLock) => T | Promise<T>} fn - The work to run while holding it
311
+ * @param {{keepAlive?: boolean}} [options] - `{ keepAlive }`: renew on a timer (default true)
312
+ * @returns {Promise<T>}
313
+ */
314
+ locked: (documentId, fn, options) =>
315
+ withDocumentLock(client, documentId, fn, options),
316
+ /**
317
+ * Get a link that plays the document's recording without an
318
+ * Authorization header, for an audio or video element's `src`, which
319
+ * cannot send one. `url` is the document's media URL with a
320
+ * `media-token` added, resolved against the client's base URL. The token
321
+ * opens only this recording, for this user, and the server refuses it on
322
+ * every other route. It expires at `expiresAt` (an ISO-8601 instant, six
323
+ * hours on by default), and sooner when the user signs out, changes
324
+ * password or loses access to the project: ask for a new link then. A
325
+ * document with no recording is a 404. Writes nothing, so it takes no
326
+ * Idempotency-Key and goes over the wire when made on a batch.
327
+ * @param {string} documentId - The document ID
328
+ * @returns {Promise<{url: string, expiresAt: string}>}
329
+ */
330
+ mediaLink: async (documentId) => {
331
+ const link = await client._request(
332
+ "POST",
333
+ `/api/v1/documents/${documentId}/media/link`,
334
+ { outOfBand: true },
335
+ );
336
+ return { ...link, url: `${client.baseUrl}${link.url}` };
337
+ },
338
+ /**
339
+ * Upload a media file for a document. Uses Apache Tika for content validation.
340
+ * A document that already has a recording refuses it with a 409
341
+ * (`media-exists`, and the current `media-url`): delete that one first.
342
+ * A WAV whose samples are not PCM, 32-bit float, A-law or mu-law (IMA or
343
+ * MS ADPCM, GSM, 64-bit float and the rest), which browsers cannot play,
344
+ * is refused with a 415 whose `error` names its coding.
345
+ * @param {string} documentId - The document ID
346
+ * @param {File} file - The file to upload
347
+ * @param {string} [auditMessage] - Custom audit-log message for this write
348
+ * @param {{onProgress?: (progress: {loaded: number, total: number | null}) => void}} [options] - `{ onProgress }`: called with `{ loaded, total }` (bytes) as the file goes up. In a browser the upload then travels by XMLHttpRequest, and the request timeout only fires when the upload stalls, not on total time.
349
+ * @returns {Promise<{message: string, extension: string, contentType: string}>}
350
+ */
351
+ uploadMedia: (documentId, file, auditMessage, { onProgress } = {}) => {
352
+ const fd = new FormData();
353
+ fd.append("file", file);
354
+ return client._request("PUT", `/api/v1/documents/${documentId}/media`, {
355
+ auditMessage,
356
+ body: fd,
357
+ formData: true,
358
+ noBatch: true,
359
+ onUploadProgress: onProgress,
360
+ });
361
+ },
362
+ /**
363
+ * Delete media file for a document
364
+ * @param {string} documentId - The document ID
365
+ * @param {string} [auditMessage] - Audit message for this write
366
+ * @param {{mediaVersion?: string}} [options] - `mediaVersion`: the
367
+ * recording meant, the `?v=` of the document's `mediaUrl`. The delete
368
+ * is refused with a 409 (`media-changed`, and the current `media-url`)
369
+ * when the stored recording is another one.
370
+ * @returns {Promise<void>}
371
+ */
372
+ // The upload above is multipart and cannot be batched, but a DELETE
373
+ // carries no blob, so the batch transport takes it. It is a write
374
+ // of the document's own data and queues like any other. The server
375
+ // removes the file only after the write commits, so a batch that aborts
376
+ // after this op leaves the recording in place.
377
+ deleteMedia: (documentId, auditMessage, { mediaVersion } = {}) =>
378
+ client._request("DELETE", `/api/v1/documents/${documentId}/media`, {
379
+ auditMessage,
380
+ queryParams: { "media-version": mediaVersion },
381
+ }),
382
+ /**
383
+ * Restore a document to its state at an earlier time, as one
384
+ * operation: what was deleted since then comes back under its original
385
+ * id, what was added since is removed, and what changed is set back,
386
+ * across every layer. A layer deleted since then, or a vocabulary entry
387
+ * that no longer exists, is skipped and reported under `skipped`. A name
388
+ * another document holds now stays with it, and the document keeps its
389
+ * current name, reported under `nameKept` (`{name, taken}`).
390
+ * Resolves to a summary of the changes. Maintainers only.
391
+ * @param {string} documentId - The document ID
392
+ * @param {string} asOf - The moment to go back to (ISO-8601 instant),
393
+ * typically a history entry's `endTime`
394
+ * @param {{dryRun?: boolean}} [options] - `{ dryRun }`: with `dryRun` true nothing
395
+ * is written and the summary says what would change
396
+ * @param {string} [auditMessage] - Custom audit message for this operation
397
+ * @returns {Promise<DocumentRestoreSummary>}
398
+ */
399
+ restore: (documentId, asOf, { dryRun } = {}, auditMessage) =>
400
+ client._request("POST", `/api/v1/documents/${documentId}/restore`, {
401
+ queryParams: {
402
+ "as-of": asOf,
403
+ "dry-run": dryRun ? "true" : undefined,
404
+ },
405
+ auditMessage,
406
+ }),
407
+ /**
408
+ * Copy a document and everything in it into a new document of the same
409
+ * project, as one operation. The copy shares the source's layers and
410
+ * the vocabulary entries its links name, and holds the source's texts,
411
+ * tokens, spans, relations and vocab links under fresh ids, with their
412
+ * metadata. Comments do not travel. The media file does, unless
413
+ * `includeMedia` is false. Resolves to `{ id, name }`, plus `mediaError` when
414
+ * the source had media the copy could not take with it. A name another
415
+ * document of the project holds, ignoring case, is refused with 422 and `name-taken`,
416
+ * unless `freeName`, which takes the first free "name (2)", "name (3)"
417
+ * and on.
418
+ * @param {string} documentId - The document to copy
419
+ * @param {string} name - The new document's name
420
+ * @param {{includeMedia?: boolean, id?: string, freeName?: boolean}} [options] - `{ includeMedia }`: false leaves the media
421
+ * file behind
422
+ * @param {string} [auditMessage] - Custom audit message for this operation
423
+ * @returns {Promise<{id: string, name: string, mediaError?: string}>}
424
+ */
425
+ copy: (documentId, name, { includeMedia, id, freeName } = {}, auditMessage) =>
426
+ client._request("POST", `/api/v1/documents/${documentId}/copy`, {
427
+ auditMessage,
428
+ body: bodyOf({
429
+ id,
430
+ name,
431
+ "include-media": includeMedia,
432
+ "free-name": freeName || undefined,
433
+ }),
434
+ }),
435
+ ...metadataMethods(client, "documents"),
436
+ ...auditMethods(client, "documents"),
437
+ };
438
+ }
439
+
440
+ /** @param {import("./client.js").PlaidClient} client */
441
+ export function textsApi(client) {
442
+ return {
443
+ /**
444
+ * Get a text.
445
+ * @param {string} textId - The text ID
446
+ * @returns {Promise<PlaidText>}
447
+ */
448
+ get: (textId) =>
449
+ client._request("GET", `/api/v1/texts/${textId}`),
450
+ /**
451
+ * Create a new text in a document's text layer. A text is a container for
452
+ * one long string in `body` for a given layer.
453
+ * @param {string} textLayerId - The text layer ID
454
+ * @param {string} documentId - The document ID
455
+ * @param {string} body - The text
456
+ * @param {any} [metadata] - Metadata map. Omit to leave unset; pass null to send JSON null.
457
+ * @param {string} [auditMessage]
458
+ * @param {{id?: string, tokenEdges?: number[], tokenizeLayer?: string}} [options] - `id`: the
459
+ * new text's id, a UUIDv7 this client minted. `tokenEdges`: the code-point offsets where
460
+ * the tokens made next on this text begin and end. The body is stored composed, except a
461
+ * character one of them falls inside, which stays as sent (see composeText).
462
+ * `tokenizeLayer`: a word layer of the text, which gets the body's words, cut by the
463
+ * server ("Tokenize new text").
464
+ * @returns {Promise<Created>}
465
+ */
466
+ create: (
467
+ textLayerId,
468
+ documentId,
469
+ body,
470
+ metadata,
471
+ auditMessage,
472
+ { id, tokenEdges, tokenizeLayer } = {},
473
+ ) =>
474
+ client._request("POST", "/api/v1/texts", {
475
+ auditMessage,
476
+ body: bodyOf({
477
+ id,
478
+ "text-layer-id": textLayerId,
479
+ "document-id": documentId,
480
+ body,
481
+ "token-edges": tokenEdges,
482
+ "tokenize-layer": tokenizeLayer,
483
+ metadata,
484
+ }),
485
+ }),
486
+ /**
487
+ * Update a text's body. A diff is computed and token indices are updated
488
+ * so that tokens remain intact. Alternatively, `body` can be a list of
489
+ * edit directives (code-point indices, applied in order):
490
+ * {type: 'delete', index, value: count}
491
+ * {type: 'insert', index, value: string}
492
+ * {type: 'replace', index, length, value: string} — unlike
493
+ * delete+insert, a token covering the whole range is resized to keep
494
+ * it (respell a word in place without losing its annotations).
495
+ * A list is applied exactly as sent. With `base` (the `digest` of the
496
+ * body the update was made on, as every read of a text gives it) the
497
+ * update applies only to that body, and is refused with 409 and
498
+ * `text-changed` otherwise. Strict mode then does not stamp it, unless
499
+ * `versioned` is true (a batch whose later writes are stamped needs its
500
+ * first write stamped too).
501
+ * @param {string} textId - The text ID
502
+ * @param {any} body - The new text
503
+ * @param {string} [auditMessage]
504
+ * `tokenizeLayer`: a word layer of the text, whose words the server makes for the text the
505
+ * update types, after it places the old tokens ("Tokenize new text"). They come in the
506
+ * answer's `reshape`.
507
+ * @param {{base?: string, versioned?: boolean, tokenizeLayer?: string}} [options]
508
+ * @returns {Promise<PlaidText & {reshape: TextReshape}>}
509
+ */
510
+ update: (textId, body, auditMessage, { base, versioned, tokenizeLayer } = {}) =>
511
+ client._request("PATCH", `/api/v1/texts/${textId}`, {
512
+ auditMessage,
513
+ body: bodyOf({ body, base, "tokenize-layer": tokenizeLayer }),
514
+ versioned: versioned ?? (base === undefined || base === null),
515
+ }),
516
+ /**
517
+ * Delete a text and all dependent data.
518
+ * @param {string} textId - The text ID
519
+ * @param {string} [auditMessage] - Audit message for this write
520
+ * @returns {Promise<void>}
521
+ */
522
+ delete: (textId, auditMessage) =>
523
+ client._request("DELETE", `/api/v1/texts/${textId}`, { auditMessage }),
524
+ /**
525
+ * Change a text's body by the edits made at the caret: a list of edit
526
+ * directives as `update` takes them (code-point indices, applied in
527
+ * order, each index in the body the ones before it left). Only their
528
+ * net change counts. An insert or a delete stays where it was made, and
529
+ * a stretch deleted and typed over is a delete and an insert at the same
530
+ * place, so a word all of whose letters it replaces is deleted. The
531
+ * answer is the text with its new `digest` and `reshape` (the tokens
532
+ * moved, the spans and vocab links trimmed, the rows deleted). With
533
+ * `base`, the digest of the body the edits were made on, the edit
534
+ * applies only to that body and is refused with 409 and `text-changed`
535
+ * otherwise, and strict mode does not stamp it unless `versioned` is
536
+ * true. `tokenizeLayer` is as `update` takes it. See composeTextEdits and gapsToOps.
537
+ * @param {string} textId - The text ID
538
+ * @param {Array<object>} edits - The edit directives
539
+ * @param {string} [auditMessage]
540
+ * @param {{base?: string, versioned?: boolean, tokenizeLayer?: string}} [options]
541
+ * @returns {Promise<PlaidText & {reshape: TextReshape}>}
542
+ */
543
+ edit: (textId, edits, auditMessage, { base, versioned, tokenizeLayer } = {}) =>
544
+ client._request("PATCH", `/api/v1/texts/${textId}`, {
545
+ auditMessage,
546
+ body: bodyOf({ edits, base, "tokenize-layer": tokenizeLayer }),
547
+ versioned: versioned ?? (base === undefined || base === null),
548
+ }),
549
+ ...metadataMethods(client, "texts"),
550
+ };
551
+ }