@larc-iu/plaid-client 0.2.0-alpha.21 → 0.2.0-alpha.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +15 -4
- package/package.json +1 -1
- package/src/http.js +25 -2
- package/src/index.js +121 -23
- package/src/services.js +13 -2
- package/src/textEdits.js +18 -2
package/index.d.ts
CHANGED
|
@@ -923,6 +923,10 @@ interface AuditPageOptions {
|
|
|
923
923
|
order?: "asc" | "desc";
|
|
924
924
|
limit?: number;
|
|
925
925
|
cursor?: string;
|
|
926
|
+
/** Keep only each entry's oldest N operations in `ops` (1..1000). Every entry has `opCount`. */
|
|
927
|
+
opsLimit?: number;
|
|
928
|
+
/** Read only this entry rather than a page. */
|
|
929
|
+
entryId?: string;
|
|
926
930
|
}
|
|
927
931
|
|
|
928
932
|
/** One user's activity over a scope and window. */
|
|
@@ -1115,7 +1119,14 @@ interface DocumentsBundle {
|
|
|
1115
1119
|
onProgress?: (progress: { loaded: number; total: number | null }) => void;
|
|
1116
1120
|
},
|
|
1117
1121
|
): Promise<any>;
|
|
1118
|
-
deleteMedia(
|
|
1122
|
+
deleteMedia(
|
|
1123
|
+
documentId: string,
|
|
1124
|
+
auditMessage?: string,
|
|
1125
|
+
options?: {
|
|
1126
|
+
/** The recording meant, the `?v=` of the document's `mediaUrl`. A 409 when the stored one is another. */
|
|
1127
|
+
mediaVersion?: string;
|
|
1128
|
+
},
|
|
1129
|
+
): Promise<any>;
|
|
1119
1130
|
setMetadata(
|
|
1120
1131
|
documentId: string,
|
|
1121
1132
|
body: any,
|
|
@@ -1458,7 +1469,7 @@ interface TokensBundle {
|
|
|
1458
1469
|
tokenId: string,
|
|
1459
1470
|
position: number,
|
|
1460
1471
|
auditMessage?: string,
|
|
1461
|
-
options?: { id?: string },
|
|
1472
|
+
options?: { id?: string; keep?: "left" | "right" },
|
|
1462
1473
|
): Promise<any>;
|
|
1463
1474
|
merge(
|
|
1464
1475
|
tokenId: string,
|
|
@@ -1652,7 +1663,7 @@ export declare class PlaidClient {
|
|
|
1652
1663
|
} | null;
|
|
1653
1664
|
beginOperation(
|
|
1654
1665
|
message: string,
|
|
1655
|
-
opts?: {
|
|
1666
|
+
opts?: { groupId?: string; kind?: OperationKind; ref?: string; keys?: KeySeed; minted?: Set<string> },
|
|
1656
1667
|
): string;
|
|
1657
1668
|
/** A seed for the Idempotency-Keys of an operation that may be run again (see beginOperation's `keys`). */
|
|
1658
1669
|
keySeed(): KeySeed;
|
|
@@ -1660,7 +1671,7 @@ export declare class PlaidClient {
|
|
|
1660
1671
|
withOperation<T>(
|
|
1661
1672
|
message: string,
|
|
1662
1673
|
fn: (setMessage: (msg: string) => void) => Promise<T> | T,
|
|
1663
|
-
opts?: { kind?: OperationKind; ref?: string;
|
|
1674
|
+
opts?: { kind?: OperationKind; ref?: string; groupId?: string; keys?: KeySeed; minted?: Set<string> },
|
|
1664
1675
|
): Promise<T>;
|
|
1665
1676
|
operationGroups: OperationGroupsBundle;
|
|
1666
1677
|
|
package/package.json
CHANGED
package/src/http.js
CHANGED
|
@@ -69,6 +69,23 @@ export const DEFAULT_TIMEOUT_MS = 30000;
|
|
|
69
69
|
// nothing and costs a retry stacked on a write that is still running.
|
|
70
70
|
export const DEFAULT_BATCH_TIMEOUT_MS = 180000;
|
|
71
71
|
|
|
72
|
+
// A query may run for core's own query limit (30 s, `*query-timeout-ms*` in
|
|
73
|
+
// plaid.sql.query.exec), and core then answers 408. The client waits a little
|
|
74
|
+
// longer than that, so the caller gets core's 408 for a query too broad and
|
|
75
|
+
// not a timeout of its own that reads like a lost connection.
|
|
76
|
+
export const DEFAULT_QUERY_TIMEOUT_MS = 35000;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The timeout for a query: the client's own, raised to
|
|
80
|
+
* DEFAULT_QUERY_TIMEOUT_MS when it is shorter. A disabled timeout (0, null)
|
|
81
|
+
* stays disabled.
|
|
82
|
+
*/
|
|
83
|
+
export function queryTimeout(client) {
|
|
84
|
+
const t = client.timeout;
|
|
85
|
+
if (!t || t <= 0) return t;
|
|
86
|
+
return Math.max(t, DEFAULT_QUERY_TIMEOUT_MS);
|
|
87
|
+
}
|
|
88
|
+
|
|
72
89
|
/**
|
|
73
90
|
* Note how far the server's clock is from this machine's, from a response's
|
|
74
91
|
* Date header, for `client.serverNow()`. A time the server stamped (an audit
|
|
@@ -738,6 +755,10 @@ export async function queueRequest(batch, method, path, options = {}) {
|
|
|
738
755
|
* skipResponseTransform - Return raw parsed JSON (no transformResponse)
|
|
739
756
|
* noAuth - Skip Authorization header
|
|
740
757
|
* binaryResponse - Return arrayBuffer instead of JSON/text
|
|
758
|
+
* noBusyRetry - If true, a 503 is not retried. For a query: core
|
|
759
|
+
* answers 503 when its queue of large queries stays full
|
|
760
|
+
* until the query's deadline, and asking again at once
|
|
761
|
+
* only joins the same queue.
|
|
741
762
|
* timeout - Per-request timeout in ms overriding client.timeout
|
|
742
763
|
* for this call (0/null disables). Used for known-long
|
|
743
764
|
* ops like project delete.
|
|
@@ -756,6 +777,7 @@ export async function makeRequest(client, method, path, options = {}) {
|
|
|
756
777
|
timeout,
|
|
757
778
|
onUploadProgress,
|
|
758
779
|
learningOmittedVersion,
|
|
780
|
+
noBusyRetry,
|
|
759
781
|
} = options;
|
|
760
782
|
if (method !== "GET" && omittedStrictDocument(client)) {
|
|
761
783
|
await learnOmittedVersion(client);
|
|
@@ -825,12 +847,13 @@ export async function makeRequest(client, method, path, options = {}) {
|
|
|
825
847
|
|
|
826
848
|
try {
|
|
827
849
|
const onRetry = (info) => client._noteRetry?.(info);
|
|
850
|
+
const busy = noBusyRetry ? { onRetry, retries: 0 } : { onRetry };
|
|
828
851
|
const response = keyed
|
|
829
|
-
? await retryUnknown(() => retryWhileBusy(attempt,
|
|
852
|
+
? await retryUnknown(() => retryWhileBusy(attempt, busy), {
|
|
830
853
|
delaysMs: client.retryDelaysMs,
|
|
831
854
|
onRetry,
|
|
832
855
|
})
|
|
833
|
-
: await retryWhileBusy(attempt,
|
|
856
|
+
: await retryWhileBusy(attempt, busy);
|
|
834
857
|
noteServerClock(client, response.headers);
|
|
835
858
|
|
|
836
859
|
if (!response.ok) {
|
package/src/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
REPLAYED_HEADER,
|
|
21
21
|
DEFAULT_TIMEOUT_MS,
|
|
22
22
|
DEFAULT_BATCH_TIMEOUT_MS,
|
|
23
|
+
queryTimeout,
|
|
23
24
|
} from "./http.js";
|
|
24
25
|
import { uuidv7 } from "./ids.js";
|
|
25
26
|
import { constraintsBody } from "./constraints.js";
|
|
@@ -133,6 +134,21 @@ async function anonymousGet(baseUrl, path, options = {}) {
|
|
|
133
134
|
*/
|
|
134
135
|
const MAX_BATCH_OPS = 1000;
|
|
135
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The kinds an operation may say it is (`?group-kind=`), the server's closed
|
|
139
|
+
* list (plaid.sql.operation-group/kinds). An operation of any other kind is
|
|
140
|
+
* refused when it begins, before it writes anything.
|
|
141
|
+
*/
|
|
142
|
+
const OPERATION_KINDS = [
|
|
143
|
+
"assistant-plan",
|
|
144
|
+
"service-run",
|
|
145
|
+
"import",
|
|
146
|
+
"bulk-edit",
|
|
147
|
+
"guess-adoption",
|
|
148
|
+
"repair",
|
|
149
|
+
"review",
|
|
150
|
+
];
|
|
151
|
+
|
|
136
152
|
/**
|
|
137
153
|
* How long `admin.backup()` waits for the server to finish writing a backup:
|
|
138
154
|
* minutes on a large database, past the usual per-request timeout.
|
|
@@ -528,13 +544,17 @@ class PlaidClient {
|
|
|
528
544
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
529
545
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
530
546
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
547
|
+
* @param {number} [opts.opsLimit] - Keep only each entry's oldest N
|
|
548
|
+
* operations in `ops` (1..1000). `opCount` on every entry says how
|
|
549
|
+
* many it has
|
|
550
|
+
* @param {string} [opts.entryId] - Read only this entry, not a page
|
|
531
551
|
* @param {string} [opts.itemId] - Only the changes that wrote this one
|
|
532
552
|
* entry, each with only its operations that did
|
|
533
553
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
534
554
|
*/
|
|
535
555
|
auditPage: (
|
|
536
556
|
id,
|
|
537
|
-
{ startTime, endTime, opTypes, kinds, order, limit, cursor, itemId } = {},
|
|
557
|
+
{ startTime, endTime, opTypes, kinds, order, limit, cursor, opsLimit, entryId, itemId } = {},
|
|
538
558
|
) =>
|
|
539
559
|
listPage(this, `/api/v1/vocab-layers/${id}/audit`, {
|
|
540
560
|
limit,
|
|
@@ -545,6 +565,8 @@ class PlaidClient {
|
|
|
545
565
|
"op-types": opTypesParam(opTypes),
|
|
546
566
|
kinds: kindsParam(kinds),
|
|
547
567
|
order,
|
|
568
|
+
"ops-limit": opsLimit,
|
|
569
|
+
"entry-id": entryId,
|
|
548
570
|
"item-id": itemId,
|
|
549
571
|
},
|
|
550
572
|
}),
|
|
@@ -1173,14 +1195,14 @@ class PlaidClient {
|
|
|
1173
1195
|
* List (or search) users. Transparently follows pagination cursors and
|
|
1174
1196
|
* returns the full flat array. Admin-or-maintainer only.
|
|
1175
1197
|
* @param {object} [opts]
|
|
1176
|
-
* @param {string} [opts.q] - Filter to users whose display name or email contains
|
|
1198
|
+
* @param {string} [opts.q] - Filter to users whose display name contains this text, or whose email starts with it (contains it, when the text has an @), case-insensitive
|
|
1177
1199
|
*/
|
|
1178
1200
|
list: ({ q } = {}) =>
|
|
1179
1201
|
listAll(this, "/api/v1/users", { query: { q } }),
|
|
1180
1202
|
/**
|
|
1181
1203
|
* Fetch a single page of users (optionally filtered by `q`).
|
|
1182
1204
|
* @param {object} [opts]
|
|
1183
|
-
* @param {string} [opts.q] - Filter to users whose display name or email contains
|
|
1205
|
+
* @param {string} [opts.q] - Filter to users whose display name contains this text, or whose email starts with it (contains it, when the text has an @), case-insensitive
|
|
1184
1206
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1185
1207
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1186
1208
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
@@ -1194,7 +1216,7 @@ class PlaidClient {
|
|
|
1194
1216
|
/**
|
|
1195
1217
|
* Async-iterate users page by page; yields each page's entries array.
|
|
1196
1218
|
* @param {object} [opts]
|
|
1197
|
-
* @param {string} [opts.q] - Filter to users whose display name or email contains
|
|
1219
|
+
* @param {string} [opts.q] - Filter to users whose display name contains this text, or whose email starts with it (contains it, when the text has an @), case-insensitive
|
|
1198
1220
|
* @param {number} [opts.pageSize] - Per-request page size
|
|
1199
1221
|
* @returns {AsyncGenerator<Array>}
|
|
1200
1222
|
*/
|
|
@@ -1256,11 +1278,15 @@ class PlaidClient {
|
|
|
1256
1278
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
1257
1279
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1258
1280
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1281
|
+
* @param {number} [opts.opsLimit] - Keep only each entry's oldest N
|
|
1282
|
+
* operations in `ops` (1..1000). `opCount` on every entry says how
|
|
1283
|
+
* many it has
|
|
1284
|
+
* @param {string} [opts.entryId] - Read only this entry, not a page
|
|
1259
1285
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1260
1286
|
*/
|
|
1261
1287
|
auditPage: (
|
|
1262
1288
|
userId,
|
|
1263
|
-
{ startTime, endTime, opTypes, kinds, order, limit, cursor } = {},
|
|
1289
|
+
{ startTime, endTime, opTypes, kinds, order, limit, cursor, opsLimit, entryId } = {},
|
|
1264
1290
|
) =>
|
|
1265
1291
|
listPage(this, `/api/v1/users/${userId}/audit`, {
|
|
1266
1292
|
limit,
|
|
@@ -1271,6 +1297,8 @@ class PlaidClient {
|
|
|
1271
1297
|
"op-types": opTypesParam(opTypes),
|
|
1272
1298
|
kinds: kindsParam(kinds),
|
|
1273
1299
|
order,
|
|
1300
|
+
"ops-limit": opsLimit,
|
|
1301
|
+
"entry-id": entryId,
|
|
1274
1302
|
},
|
|
1275
1303
|
}),
|
|
1276
1304
|
/**
|
|
@@ -1839,9 +1867,13 @@ class PlaidClient {
|
|
|
1839
1867
|
* is what a feed wants. A cursor belongs to the direction that made it.
|
|
1840
1868
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
1841
1869
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
1870
|
+
* @param {number} [opts.opsLimit] - Keep only each entry's oldest N
|
|
1871
|
+
* operations in `ops` (1..1000). `opCount` on every entry says how
|
|
1872
|
+
* many it has
|
|
1873
|
+
* @param {string} [opts.entryId] - Read only this entry, not a page
|
|
1842
1874
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
1843
1875
|
*/
|
|
1844
|
-
listPage: ({ startTime, endTime, opTypes, kinds, order, limit, cursor } = {}) =>
|
|
1876
|
+
listPage: ({ startTime, endTime, opTypes, kinds, order, limit, cursor, opsLimit, entryId } = {}) =>
|
|
1845
1877
|
listPage(this, "/api/v1/audit", {
|
|
1846
1878
|
limit,
|
|
1847
1879
|
cursor,
|
|
@@ -1851,6 +1883,8 @@ class PlaidClient {
|
|
|
1851
1883
|
"op-types": opTypesParam(opTypes),
|
|
1852
1884
|
kinds: kindsParam(kinds),
|
|
1853
1885
|
order,
|
|
1886
|
+
"ops-limit": opsLimit,
|
|
1887
|
+
"entry-id": entryId,
|
|
1854
1888
|
},
|
|
1855
1889
|
}),
|
|
1856
1890
|
/**
|
|
@@ -2112,6 +2146,11 @@ class PlaidClient {
|
|
|
2112
2146
|
}),
|
|
2113
2147
|
/**
|
|
2114
2148
|
* Upload a media file for a document. Uses Apache Tika for content validation.
|
|
2149
|
+
* A document that already has a recording refuses it with a 409
|
|
2150
|
+
* (`media-exists`, and the current `media-url`): delete that one first.
|
|
2151
|
+
* A WAV whose samples are not PCM or float (IMA or MS ADPCM, GSM, A-law
|
|
2152
|
+
* and the rest), which browsers cannot play, is refused with a 415
|
|
2153
|
+
* whose `error` names its coding.
|
|
2115
2154
|
* @param {string} documentId - The document ID
|
|
2116
2155
|
* @param {File} file - The file to upload
|
|
2117
2156
|
* @param {string} [auditMessage] - Custom audit-log message for this write
|
|
@@ -2131,15 +2170,21 @@ class PlaidClient {
|
|
|
2131
2170
|
/**
|
|
2132
2171
|
* Delete media file for a document
|
|
2133
2172
|
* @param {string} documentId - The document ID
|
|
2173
|
+
* @param {string} [auditMessage] - Audit message for this write
|
|
2174
|
+
* @param {{mediaVersion?: string}} [options] - `mediaVersion`: the
|
|
2175
|
+
* recording meant, the `?v=` of the document's `mediaUrl`. The delete
|
|
2176
|
+
* is refused with a 409 (`media-changed`, and the current `media-url`)
|
|
2177
|
+
* when the stored recording is another one.
|
|
2134
2178
|
*/
|
|
2135
2179
|
// The upload above is multipart and cannot be batched, but a DELETE
|
|
2136
2180
|
// carries no blob, so the batch transport takes it. It is a write
|
|
2137
2181
|
// of the document's own data and queues like any other. Note that the
|
|
2138
2182
|
// file removal happens outside the server's transaction, so a batch that
|
|
2139
2183
|
// aborts after this op does not bring the file back.
|
|
2140
|
-
deleteMedia: (documentId, auditMessage) =>
|
|
2184
|
+
deleteMedia: (documentId, auditMessage, { mediaVersion } = {}) =>
|
|
2141
2185
|
this._request("DELETE", `/api/v1/documents/${documentId}/media`, {
|
|
2142
2186
|
auditMessage,
|
|
2187
|
+
queryParams: { "media-version": mediaVersion },
|
|
2143
2188
|
}),
|
|
2144
2189
|
/**
|
|
2145
2190
|
* Replace all metadata for a document.
|
|
@@ -2200,11 +2245,15 @@ class PlaidClient {
|
|
|
2200
2245
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
2201
2246
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
2202
2247
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
2248
|
+
* @param {number} [opts.opsLimit] - Keep only each entry's oldest N
|
|
2249
|
+
* operations in `ops` (1..1000). `opCount` on every entry says how
|
|
2250
|
+
* many it has
|
|
2251
|
+
* @param {string} [opts.entryId] - Read only this entry, not a page
|
|
2203
2252
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2204
2253
|
*/
|
|
2205
2254
|
auditPage: (
|
|
2206
2255
|
documentId,
|
|
2207
|
-
{ startTime, endTime, opTypes, kinds, order, limit, cursor } = {},
|
|
2256
|
+
{ startTime, endTime, opTypes, kinds, order, limit, cursor, opsLimit, entryId } = {},
|
|
2208
2257
|
) =>
|
|
2209
2258
|
listPage(this, `/api/v1/documents/${documentId}/audit`, {
|
|
2210
2259
|
limit,
|
|
@@ -2215,6 +2264,8 @@ class PlaidClient {
|
|
|
2215
2264
|
"op-types": opTypesParam(opTypes),
|
|
2216
2265
|
kinds: kindsParam(kinds),
|
|
2217
2266
|
order,
|
|
2267
|
+
"ops-limit": opsLimit,
|
|
2268
|
+
"entry-id": entryId,
|
|
2218
2269
|
},
|
|
2219
2270
|
}),
|
|
2220
2271
|
/**
|
|
@@ -2430,11 +2481,15 @@ class PlaidClient {
|
|
|
2430
2481
|
* @param {"asc"|"desc"} [opts.order] - "desc" pages newest-first
|
|
2431
2482
|
* @param {number} [opts.limit] - Page size (1..1000; server default 100)
|
|
2432
2483
|
* @param {string} [opts.cursor] - Opaque cursor from a previous page
|
|
2484
|
+
* @param {number} [opts.opsLimit] - Keep only each entry's oldest N
|
|
2485
|
+
* operations in `ops` (1..1000). `opCount` on every entry says how
|
|
2486
|
+
* many it has
|
|
2487
|
+
* @param {string} [opts.entryId] - Read only this entry, not a page
|
|
2433
2488
|
* @returns {Promise<{entries: Array, nextCursor: (string|null)}>}
|
|
2434
2489
|
*/
|
|
2435
2490
|
auditPage: (
|
|
2436
2491
|
projectId,
|
|
2437
|
-
{ startTime, endTime, opTypes, kinds, order, limit, cursor } = {},
|
|
2492
|
+
{ startTime, endTime, opTypes, kinds, order, limit, cursor, opsLimit, entryId } = {},
|
|
2438
2493
|
) =>
|
|
2439
2494
|
listPage(this, `/api/v1/projects/${projectId}/audit`, {
|
|
2440
2495
|
limit,
|
|
@@ -2445,6 +2500,8 @@ class PlaidClient {
|
|
|
2445
2500
|
"op-types": opTypesParam(opTypes),
|
|
2446
2501
|
kinds: kindsParam(kinds),
|
|
2447
2502
|
order,
|
|
2503
|
+
"ops-limit": opsLimit,
|
|
2504
|
+
"entry-id": entryId,
|
|
2448
2505
|
},
|
|
2449
2506
|
}),
|
|
2450
2507
|
/**
|
|
@@ -2762,8 +2819,11 @@ class PlaidClient {
|
|
|
2762
2819
|
* losers are deleted. Links are read when the merge runs, so one made
|
|
2763
2820
|
* after the caller looked moves too. Every loser must be in the
|
|
2764
2821
|
* survivor's vocabulary, and a loser that is already gone is skipped, so
|
|
2765
|
-
* a repeated merge changes nothing.
|
|
2766
|
-
*
|
|
2822
|
+
* a repeated merge changes nothing. Metadata naming a loser by its id,
|
|
2823
|
+
* in the vocabulary's other entries and in the documents of every
|
|
2824
|
+
* project it is linked to, is rewritten to name the survivor. A
|
|
2825
|
+
* reference to a loser in the survivor's own metadata is the caller's to
|
|
2826
|
+
* rewrite, in the same batch.
|
|
2767
2827
|
* Needs maintainer rights on the vocabulary.
|
|
2768
2828
|
* @param {string} survivorId - The entry that stays
|
|
2769
2829
|
* @param {string[]} loserIds - The entries merged into it
|
|
@@ -2966,8 +3026,11 @@ class PlaidClient {
|
|
|
2966
3026
|
* @param {string} tokenId - The token ID
|
|
2967
3027
|
* @param {number} position - Code-point offset to split at (strictly between begin and end)
|
|
2968
3028
|
* @param {string} [auditMessage] - Audit message for this write
|
|
2969
|
-
* @param {{id?: string}} [options] - `id`: the
|
|
2970
|
-
*
|
|
3029
|
+
* @param {{id?: string, keep?: "left"|"right"}} [options] - `id`: the new
|
|
3030
|
+
* token's id, a UUIDv7 this client minted. `keep`: the half the original token
|
|
3031
|
+
* (its id, spans, vocab-links, comments and metadata) stays on, `"left"` by
|
|
3032
|
+
* default. With `"right"` the new token, whose id is answered, is the left
|
|
3033
|
+
* half. A relation layer whose relations must stay inside one token
|
|
2971
3034
|
* of this layer declares a same-ancestor constraint, and the server deletes
|
|
2972
3035
|
* the relations the split leaves crossing in the same transaction.
|
|
2973
3036
|
*/
|
|
@@ -2975,11 +3038,11 @@ class PlaidClient {
|
|
|
2975
3038
|
tokenId,
|
|
2976
3039
|
position,
|
|
2977
3040
|
auditMessage,
|
|
2978
|
-
{ id } = {},
|
|
3041
|
+
{ id, keep } = {},
|
|
2979
3042
|
) =>
|
|
2980
3043
|
this._request("POST", `/api/v1/tokens/${tokenId}/split`, {
|
|
2981
3044
|
auditMessage,
|
|
2982
|
-
body: bodyOf({ id, position }),
|
|
3045
|
+
body: bodyOf({ id, keep, position }),
|
|
2983
3046
|
}),
|
|
2984
3047
|
/**
|
|
2985
3048
|
* Merge two tokens. The left token (smaller begin) survives with the combined
|
|
@@ -3228,9 +3291,16 @@ class PlaidClient {
|
|
|
3228
3291
|
*/
|
|
3229
3292
|
// outOfBand: a query is a read that travels as a POST (see the note at
|
|
3230
3293
|
// the top of http.js). Made on a batch it goes over the wire like any
|
|
3231
|
-
// read, and it never joins a logical operation.
|
|
3294
|
+
// read, and it never joins a logical operation. It waits past core's own
|
|
3295
|
+
// query limit, so a query too broad answers with core's 408, and a 503
|
|
3296
|
+
// from core's full queue of large queries is not retried.
|
|
3232
3297
|
this.query = (body) =>
|
|
3233
|
-
this._request("POST", "/api/v1/query", {
|
|
3298
|
+
this._request("POST", "/api/v1/query", {
|
|
3299
|
+
body,
|
|
3300
|
+
outOfBand: true,
|
|
3301
|
+
timeout: queryTimeout(this),
|
|
3302
|
+
noBusyRetry: true,
|
|
3303
|
+
});
|
|
3234
3304
|
|
|
3235
3305
|
// Logical-operation groups (audit-log grouping). There is no create: a
|
|
3236
3306
|
// group row is made lazily by the first write carrying `?group-id=`
|
|
@@ -3691,6 +3761,7 @@ class PlaidClient {
|
|
|
3691
3761
|
* `kind` says what kind of operation this is, for a program reading the
|
|
3692
3762
|
* log: one of `assistant-plan`, `service-run`, `import`, `bulk-edit`,
|
|
3693
3763
|
* `guess-adoption`, `repair` or `review` (the server refuses any other).
|
|
3764
|
+
* Any other kind throws here, before anything is written.
|
|
3694
3765
|
* `ref` is a short string naming what the operation came from, in the
|
|
3695
3766
|
* shape its kind documents (the core manual, "Kinds of operation"). Both
|
|
3696
3767
|
* are recorded from the first write like the label, and a nested operation
|
|
@@ -3706,10 +3777,15 @@ class PlaidClient {
|
|
|
3706
3777
|
* matching end, and the outer numbering resumes after it.
|
|
3707
3778
|
*
|
|
3708
3779
|
* @param {string} message - Human label for the operation.
|
|
3709
|
-
* @param {object} [opts] - Optional `{
|
|
3780
|
+
* @param {object} [opts] - Optional `{ groupId, kind, ref, keys, minted }`. `groupId` adopts an existing group id instead of minting one (a service joining the requester's operation; `requestService` propagates an open operation to the service automatically). Python's is `group_id`. `kind`, `ref` and `keys` are described above. `minted` is the ids the operation mints for what it creates: a create refused 409 id-taken for one of them was made by an earlier send of the operation, and answers as made.
|
|
3710
3781
|
* @returns {string} The operation's group id.
|
|
3711
3782
|
*/
|
|
3712
|
-
beginOperation(message, {
|
|
3783
|
+
beginOperation(message, { groupId, kind, ref, keys, minted } = {}) {
|
|
3784
|
+
if (kind != null && !OPERATION_KINDS.includes(String(kind))) {
|
|
3785
|
+
throw new Error(
|
|
3786
|
+
`Unknown operation kind "${kind}". It is one of: ${OPERATION_KINDS.join(", ")}.`,
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
3713
3789
|
const frame = {
|
|
3714
3790
|
keys: keys || null,
|
|
3715
3791
|
count: 0,
|
|
@@ -3733,7 +3809,7 @@ class PlaidClient {
|
|
|
3733
3809
|
return open.id;
|
|
3734
3810
|
}
|
|
3735
3811
|
this.operationGroup = {
|
|
3736
|
-
id:
|
|
3812
|
+
id: groupId ? String(groupId) : crypto.randomUUID(),
|
|
3737
3813
|
message: message == null ? null : String(message),
|
|
3738
3814
|
kind: kind == null ? null : String(kind),
|
|
3739
3815
|
ref: ref == null ? null : String(ref),
|
|
@@ -3807,11 +3883,11 @@ class PlaidClient {
|
|
|
3807
3883
|
*
|
|
3808
3884
|
* @param {string} message - Human label for the operation.
|
|
3809
3885
|
* @param {function} fn - The work to run; receives `setMessage(msg)` to refine the label once the outcome is known.
|
|
3810
|
-
* @param {object} [opts] - Optional `{ kind, ref,
|
|
3886
|
+
* @param {object} [opts] - Optional `{ kind, ref, groupId, keys, minted }`, as for beginOperation. `kind` is one of `assistant-plan`, `service-run`, `import`, `bulk-edit`, `guess-adoption`, `repair` or `review`, and any other throws before `fn` runs.
|
|
3811
3887
|
* @returns {Promise<any>} Whatever `fn` resolves to.
|
|
3812
3888
|
*/
|
|
3813
|
-
async withOperation(message, fn, { kind, ref,
|
|
3814
|
-
this.beginOperation(message, { kind, ref,
|
|
3889
|
+
async withOperation(message, fn, { kind, ref, groupId, keys, minted } = {}) {
|
|
3890
|
+
this.beginOperation(message, { kind, ref, groupId, keys, minted });
|
|
3815
3891
|
const group = this.operationGroup;
|
|
3816
3892
|
// The key frame this call opened is ended by this call, whatever else
|
|
3817
3893
|
// began or ended meanwhile, not by whichever end comes at its depth.
|
|
@@ -3957,6 +4033,28 @@ class PlaidClient {
|
|
|
3957
4033
|
}
|
|
3958
4034
|
}
|
|
3959
4035
|
}
|
|
4036
|
+
// The batch's own header holds each document's version after the whole
|
|
4037
|
+
// batch, the layer rules' remedies at its end included, which no single
|
|
4038
|
+
// operation's answer does: a split that deletes a relation crossing
|
|
4039
|
+
// the new boundary moves the version once more.
|
|
4040
|
+
const outerVersions = response.headers?.get?.("X-Document-Versions");
|
|
4041
|
+
if (outerVersions) {
|
|
4042
|
+
try {
|
|
4043
|
+
const versionsMap = JSON.parse(outerVersions);
|
|
4044
|
+
if (typeof versionsMap === "object" && versionsMap !== null) {
|
|
4045
|
+
this.documentVersions = mergeVersions(
|
|
4046
|
+
this.documentVersions,
|
|
4047
|
+
versionsMap,
|
|
4048
|
+
replayed,
|
|
4049
|
+
);
|
|
4050
|
+
}
|
|
4051
|
+
} catch (e) {
|
|
4052
|
+
console.warn(
|
|
4053
|
+
"Failed to parse document versions header from batch response:",
|
|
4054
|
+
e,
|
|
4055
|
+
);
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
3960
4058
|
|
|
3961
4059
|
const out = results.map((result) => ({
|
|
3962
4060
|
...result,
|
package/src/services.js
CHANGED
|
@@ -444,7 +444,8 @@ export function serve(client, projectId, serviceInfo, onServiceRequest, extras =
|
|
|
444
444
|
* @param {string} serviceId - Service ID to request
|
|
445
445
|
* @param {any} data - Request payload
|
|
446
446
|
* @param {number} [timeout=10000] - How long the service may say NOTHING, in ms.
|
|
447
|
-
* Every event it sends starts the clock again, so this does not cap a long run
|
|
447
|
+
* Every event it sends starts the clock again, so this does not cap a long run.
|
|
448
|
+
* 0, a negative number or null: no limit (wait until `signal` stops it)
|
|
448
449
|
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
449
450
|
* @param {AbortSignal} [signal] - Abort to stop waiting; rejects with an AbortError
|
|
450
451
|
* @param {Object} [opts]
|
|
@@ -494,8 +495,15 @@ export function requestService(client, projectId, serviceId, data, timeout = 100
|
|
|
494
495
|
url: `${client.baseUrl}/api/v1/projects/${projectId}/services/${encodeURIComponent(serviceId)}/requests${qs}`,
|
|
495
496
|
method: 'POST',
|
|
496
497
|
body: JSON.stringify(payload === undefined ? null : transformRequest(payload)),
|
|
498
|
+
// `notLive` (with `serviceId`) lets an app say it in the service's own
|
|
499
|
+
// name rather than its id.
|
|
497
500
|
onStatus: (response) =>
|
|
498
|
-
response.status === 503
|
|
501
|
+
response.status === 503
|
|
502
|
+
? Object.assign(new Error(`No live service '${serviceId}' on this project`), {
|
|
503
|
+
notLive: true,
|
|
504
|
+
serviceId,
|
|
505
|
+
})
|
|
506
|
+
: null,
|
|
499
507
|
what: 'Service request',
|
|
500
508
|
},
|
|
501
509
|
timeout,
|
|
@@ -526,6 +534,7 @@ export function requestService(client, projectId, serviceId, data, timeout = 100
|
|
|
526
534
|
* @param {string} projectId - Project UUID
|
|
527
535
|
* @param {string} requestId - The request id (from `onAccepted` or your own)
|
|
528
536
|
* @param {number} [timeout=10000] - How long the service may say NOTHING, in ms
|
|
537
|
+
* (0, a negative number or null: no limit)
|
|
529
538
|
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
530
539
|
* @param {AbortSignal} [signal] - Abort to stop waiting; rejects with an AbortError
|
|
531
540
|
* @returns {Promise<any>} The service's result
|
|
@@ -702,9 +711,11 @@ function streamServiceRequest(client, { url, method, body, onStatus, what }, tim
|
|
|
702
711
|
// deadline on the whole run it killed working transcriptions at the
|
|
703
712
|
// five-minute default and left their writes to land on a document the page
|
|
704
713
|
// had already handed back to the user as editable.
|
|
714
|
+
// 0, a negative number or null is no limit, as for every other timeout.
|
|
705
715
|
let timer;
|
|
706
716
|
const waitAgain = () => {
|
|
707
717
|
clearTimeout(timer);
|
|
718
|
+
if (!(timeout > 0)) return;
|
|
708
719
|
timer = setTimeout(
|
|
709
720
|
() =>
|
|
710
721
|
finish(reject, stillRunning(new Error(`${what} timed out after ${timeout}ms of silence`))),
|
package/src/textEdits.js
CHANGED
|
@@ -20,6 +20,22 @@
|
|
|
20
20
|
|
|
21
21
|
const codePoints = (s) => [...s];
|
|
22
22
|
|
|
23
|
+
// The last body split into code points, kept: an edit log composes the same
|
|
24
|
+
// long body once for each of its kept states, and splitting it each time made
|
|
25
|
+
// a save on a long text block the page (H31-TEXT-1). Frozen, as every caller
|
|
26
|
+
// shares it.
|
|
27
|
+
let lastBody = null;
|
|
28
|
+
let lastPoints = Object.freeze([]);
|
|
29
|
+
|
|
30
|
+
/** The code points of `body`, as a frozen array shared with other callers. */
|
|
31
|
+
export function bodyCodePoints(body) {
|
|
32
|
+
if (body !== lastBody) {
|
|
33
|
+
lastPoints = Object.freeze(codePoints(body));
|
|
34
|
+
lastBody = body;
|
|
35
|
+
}
|
|
36
|
+
return lastPoints;
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
function opName(op) {
|
|
24
40
|
return JSON.stringify(op);
|
|
25
41
|
}
|
|
@@ -98,7 +114,7 @@ function cutAt(segs, at) {
|
|
|
98
114
|
* malformed or out-of-bounds op.
|
|
99
115
|
*/
|
|
100
116
|
export function composeTextEdits(body, ops) {
|
|
101
|
-
const old =
|
|
117
|
+
const old = bodyCodePoints(body);
|
|
102
118
|
let segs = old.length ? [{ old: true, start: 0, end: old.length }] : [];
|
|
103
119
|
let len = old.length;
|
|
104
120
|
for (const op of ops) {
|
|
@@ -128,7 +144,7 @@ export function composeTextEdits(body, ops) {
|
|
|
128
144
|
close(seg.start);
|
|
129
145
|
kept = seg.end;
|
|
130
146
|
} else {
|
|
131
|
-
|
|
147
|
+
for (const c of seg.chars) typed.push(c);
|
|
132
148
|
}
|
|
133
149
|
}
|
|
134
150
|
close(old.length);
|