@larc-iu/plaid-client 0.2.0-alpha.23 → 0.2.0-alpha.26
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/README.md +4 -0
- package/package.json +11 -5
- package/src/batchRef.d.ts +30 -0
- package/src/codepoint.d.ts +94 -0
- package/src/codepoint.js +131 -1
- package/src/constraints.d.ts +77 -0
- package/src/constraints.js +26 -1
- package/src/created.d.ts +2 -0
- package/src/documentLock.d.ts +158 -0
- package/src/documentLock.js +27 -3
- package/src/events.d.ts +60 -0
- package/src/events.js +7 -3
- package/src/http.d.ts +212 -0
- package/src/http.js +7 -0
- package/src/ids.d.ts +26 -0
- package/src/ids.js +22 -0
- package/src/index.d.ts +3814 -0
- package/src/index.js +939 -699
- package/src/metadataOps.d.ts +15 -0
- package/src/metadataOps.js +13 -8
- package/src/pagination.d.ts +49 -0
- package/src/pagination.js +2 -19
- package/src/provenance.d.ts +200 -0
- package/src/provenance.js +34 -6
- package/src/replayed.d.ts +3 -0
- package/src/replayed.js +5 -1
- package/src/requester.d.ts +60 -0
- package/src/requester.js +21 -3
- package/src/roles.d.ts +65 -0
- package/src/roles.js +3 -2
- package/src/serviceSchema.d.ts +331 -0
- package/src/serviceSchema.js +75 -9
- package/src/services.d.ts +302 -0
- package/src/services.js +67 -10
- package/src/sse.d.ts +30 -0
- package/src/sse.js +11 -0
- package/src/textEdits.d.ts +59 -0
- package/src/textEdits.js +26 -1
- package/src/transforms.d.ts +25 -0
- package/index.d.ts +0 -2026
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One line about a failure that is safe to show the person who asked.
|
|
3
|
+
*
|
|
4
|
+
* Strips absolute URLs (so an internal host never reaches a requester's screen)
|
|
5
|
+
* and any `secrets` given (a model provider's API key can come back inside its
|
|
6
|
+
* own error text). The whole error still goes to the operator's console: this
|
|
7
|
+
* is the requester's half only. The Python twin is `requester_message`.
|
|
8
|
+
*
|
|
9
|
+
* Only a connection that never opened is "could not be reached". A write whose
|
|
10
|
+
* answer never came (a reset, a timeout) may have been saved, and says so
|
|
11
|
+
* (`UNKNOWN_OUTCOME`).
|
|
12
|
+
*
|
|
13
|
+
* @param {any} error
|
|
14
|
+
* @param {string[]} [secrets]
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function requesterMessage(error: any, secrets?: string[]): string;
|
|
18
|
+
/**
|
|
19
|
+
* What the requester is told when a request fails, named by the service.
|
|
20
|
+
*
|
|
21
|
+
* Every path that reports a failed request goes through here, so a raw message
|
|
22
|
+
* cannot reach a requester from one of them. (Python's twin additionally lets a
|
|
23
|
+
* `ValueError` through unprefixed, as an authored refusal; JS has no
|
|
24
|
+
* distinguished refusal type, so every failure is named by the service.)
|
|
25
|
+
*
|
|
26
|
+
* @param {any} error
|
|
27
|
+
* @param {string} [serviceName]
|
|
28
|
+
* @param {string[]} [secrets]
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function serviceErrorMessage(error: any, serviceName?: string, secrets?: string[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Discover the services seen on a project — a synchronous GET. Returns every
|
|
34
|
+
* service ever registered on the project: currently connected ones carry
|
|
35
|
+
* `online: true`; previously-seen offline ones carry `online: false` plus a
|
|
36
|
+
* `lastSeenAt` stamp. Callers that need a service they can actually submit
|
|
37
|
+
* work to should filter on `online`. A connected service also names who runs
|
|
38
|
+
* it, as the caller sees it: `runnerName` (the runner's display name),
|
|
39
|
+
* `runByYou`, and `servesYou`, whether it would take the caller's requests on
|
|
40
|
+
* this project. A delegating service serves members other than its runner
|
|
41
|
+
* only where the runner is a maintainer or an admin. Goes over the wire even
|
|
42
|
+
* while a batch is open on the client.
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} client - PlaidClient instance
|
|
45
|
+
* @param {string} projectId - Project UUID
|
|
46
|
+
* @returns {Promise<Array>} [{serviceId, serviceName, description, extras, online, lastSeenAt, runnerName, runByYou, servesYou}]
|
|
47
|
+
*/
|
|
48
|
+
export function discoverServices(client: any, projectId: string): Promise<any[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Forget a previously-seen (offline) service: removes its row from the
|
|
51
|
+
* project's persistent registry. Maintainer-only; 409 if the service is
|
|
52
|
+
* currently connected (it would just re-register).
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} client - PlaidClient instance
|
|
55
|
+
* @param {string} projectId - Project UUID
|
|
56
|
+
* @param {string} serviceId - Service ID to forget
|
|
57
|
+
* @returns {Promise<void>}
|
|
58
|
+
*/
|
|
59
|
+
export function discardService(client: any, projectId: string, serviceId: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* POST a progress, result or error event for an in-flight request; the server
|
|
62
|
+
* relays it to the waiting requester. The Python twin is `_report_event`.
|
|
63
|
+
*
|
|
64
|
+
* outOfBand: these are signals to whoever is waiting, not writes to the
|
|
65
|
+
* project (see the note at the top of http.js). A service may report them on
|
|
66
|
+
* the batch it is filling, where queueing them would hold every one back until
|
|
67
|
+
* submit, deliver none at all if the batch aborts, and take slots in the
|
|
68
|
+
* batch's results. The requester would hear nothing and wait out its idle
|
|
69
|
+
* timeout on work that had already finished.
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} client - PlaidClient instance
|
|
72
|
+
* @param {string} projectId - Project UUID
|
|
73
|
+
* @param {string} requestId - The request id
|
|
74
|
+
* @param {Object} body - The event payload
|
|
75
|
+
* @returns {Promise<any>}
|
|
76
|
+
*/
|
|
77
|
+
export function reportRequestEvent(client: any, projectId: string, requestId: string, body: any): Promise<any>;
|
|
78
|
+
/**
|
|
79
|
+
* The cancellation half of a responseHelper, on its own.
|
|
80
|
+
* @typedef {object} CancelScope
|
|
81
|
+
* @property {boolean} cancelled True once the requester has asked this request
|
|
82
|
+
* to stop.
|
|
83
|
+
* @property {() => void} raiseIfCancelled Stop here if the requester has asked
|
|
84
|
+
* the request to stop.
|
|
85
|
+
* @property {<T>(fn: () => T | Promise<T>) => Promise<T>} critical Run `fn` as
|
|
86
|
+
* a stretch that must finish once begun, usually the writes.
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* What a `serve` handler gets for each request.
|
|
90
|
+
* @typedef {CancelScope & {
|
|
91
|
+
* requestId: string,
|
|
92
|
+
* requesterId: string | null,
|
|
93
|
+
* progress(percent: number, message: string, extra?: Record<string, any>): void,
|
|
94
|
+
* complete(data: any): void,
|
|
95
|
+
* stopped(data?: any): void,
|
|
96
|
+
* error(error: string | Error): void,
|
|
97
|
+
* }} ResponseHelper `progress` is also a cancellation checkpoint: it throws
|
|
98
|
+
* `ServiceCancelled` once a stop has been asked for. `stopped` ends the
|
|
99
|
+
* request because it was asked to stop, and its result carries `stopped: true`.
|
|
100
|
+
*/
|
|
101
|
+
/**
|
|
102
|
+
* A connection-state transition of a service registration. "stopped": the
|
|
103
|
+
* server refused the channel for a reason retrying cannot fix (a revoked
|
|
104
|
+
* token, no write access, no such project), which ends the registration.
|
|
105
|
+
* @typedef {"registered" | "reconnected" | "disconnected" | "stopped"} ServiceStatusEvent
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* What `serve` returns.
|
|
109
|
+
* @typedef {object} ServiceRegistration
|
|
110
|
+
* @property {() => void} stop
|
|
111
|
+
* @property {() => boolean} isRunning Still serving, including while retrying
|
|
112
|
+
* a channel through a server restart.
|
|
113
|
+
* @property {() => boolean} isConnected Channel open right now, so the server
|
|
114
|
+
* sees this service as online.
|
|
115
|
+
* @property {import("./serviceSchema.js").ServiceInfo & {extras: any}} serviceInfo
|
|
116
|
+
*/
|
|
117
|
+
/**
|
|
118
|
+
* Register a service and handle incoming work requests.
|
|
119
|
+
*
|
|
120
|
+
* Opens the service's dedicated request channel — which registers it for
|
|
121
|
+
* discovery (presence = open channel) — and handles work on it. For each
|
|
122
|
+
* request, runs `onServiceRequest(data, responseHelper)` where `responseHelper`
|
|
123
|
+
* has `progress(percent, msg)` / `complete(data)` / `error(err)`.
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} client - PlaidClient instance
|
|
126
|
+
* @param {string} projectId - Project UUID
|
|
127
|
+
* @param {import("./serviceSchema.js").ServiceInfo} serviceInfo - {serviceId, serviceName, description}
|
|
128
|
+
* @param {(data: any, responseHelper: ResponseHelper) => void} onServiceRequest - Handler callback (data, responseHelper)
|
|
129
|
+
* @param {Object} [extras] - Optional additional metadata
|
|
130
|
+
* @param {((event: ServiceStatusEvent, projectId: string, detail?: string) => void) | null} [onStatus] - Optional callback (event, projectId, detail) for
|
|
131
|
+
* connection-state transitions: 'registered', 'reconnected', 'disconnected',
|
|
132
|
+
* and 'stopped' when the server refuses the channel for a reason retrying
|
|
133
|
+
* cannot fix (see PERMANENT_STATUSES), which ends the registration.
|
|
134
|
+
* Called once per transition, not once per retry.
|
|
135
|
+
* @returns {ServiceRegistration}
|
|
136
|
+
*/
|
|
137
|
+
export function serve(client: any, projectId: string, serviceInfo: import("./serviceSchema.js").ServiceInfo, onServiceRequest: (data: any, responseHelper: ResponseHelper) => void, extras?: any, onStatus?: ((event: ServiceStatusEvent, projectId: string, detail?: string) => void) | null): ServiceRegistration;
|
|
138
|
+
/**
|
|
139
|
+
* Submit work to a service and await its result.
|
|
140
|
+
*
|
|
141
|
+
* Streams the service's progress + result back over a single server-mediated
|
|
142
|
+
* response (no broadcast). Rejects if no service is connected (503), if the
|
|
143
|
+
* service reports an error, or after `timeout` of SILENCE.
|
|
144
|
+
*
|
|
145
|
+
* The request outlives this call: after a timeout, an abort, or a dropped
|
|
146
|
+
* connection the service goes on, and `attachServiceRequest` collects the
|
|
147
|
+
* result given the request id, which `opts.onAccepted` receives as soon as
|
|
148
|
+
* the server has taken the request. Pass `opts.requestId` (a UUID you mint)
|
|
149
|
+
* to know the id before submitting; submitting an id that names a request
|
|
150
|
+
* you already made rejoins it instead of starting another.
|
|
151
|
+
*
|
|
152
|
+
* Errors that leave the request alive carry `pending: true`: a timeout, a
|
|
153
|
+
* stop, a dropped connection, a proxy's 502 or 504. An error without it is
|
|
154
|
+
* the end of the request. With `opts.requestId`, a request whose answer went
|
|
155
|
+
* missing (a dropped connection, a 502 or 504) is rejoined by that id a few
|
|
156
|
+
* times before the error is given.
|
|
157
|
+
*
|
|
158
|
+
* @param {Object} client - PlaidClient instance
|
|
159
|
+
* @param {string} projectId - Project UUID
|
|
160
|
+
* @param {string} serviceId - Service ID to request
|
|
161
|
+
* @param {any} data - Request payload
|
|
162
|
+
* @param {number} [timeout=10000] - How long the service may say NOTHING, in ms.
|
|
163
|
+
* Every event it sends starts the clock again, so this does not cap a long run.
|
|
164
|
+
* 0, a negative number or null: no limit (wait until `signal` stops it)
|
|
165
|
+
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
166
|
+
* @param {AbortSignal} [signal] - Abort to stop waiting; rejects with an AbortError
|
|
167
|
+
* @param {Object} [opts]
|
|
168
|
+
* @param {string} [opts.requestId] - A client-minted request id (UUID)
|
|
169
|
+
* @param {function} [opts.onAccepted] - Called with the request id once the server has it
|
|
170
|
+
* @param {string[]} [opts.projectIds] - Other projects the request is about, beside
|
|
171
|
+
* `projectId`. A delegating service's token is scoped to `projectId` and to those of
|
|
172
|
+
* these the requester can read, and reaches nothing else
|
|
173
|
+
* @param {boolean} [opts.noOperation] - Carry no open operation: the service's writes
|
|
174
|
+
* are a group of their own, not part of whatever operation this client has open
|
|
175
|
+
* @returns {Promise<any>} The service's result
|
|
176
|
+
*/
|
|
177
|
+
export function requestService(client: any, projectId: string, serviceId: string, data: any, timeout?: number, onProgress?: Function, signal?: AbortSignal, opts?: {
|
|
178
|
+
requestId?: string;
|
|
179
|
+
onAccepted?: Function;
|
|
180
|
+
projectIds?: string[];
|
|
181
|
+
noOperation?: boolean;
|
|
182
|
+
}): Promise<any>;
|
|
183
|
+
/**
|
|
184
|
+
* Rejoin a service request made earlier (by this user) and await its result:
|
|
185
|
+
* the latest progress is replayed, then the result comes, or at once if the
|
|
186
|
+
* request already finished. Rejects with an error whose `status` is 404 when
|
|
187
|
+
* the request is unknown or expired (the server keeps a finished request's
|
|
188
|
+
* result for a while, not forever). As with `requestService`, `timeout` is
|
|
189
|
+
* how long the service may be silent, and an error carrying `pending: true`
|
|
190
|
+
* means the request is still there to rejoin again.
|
|
191
|
+
*
|
|
192
|
+
* @param {Object} client - PlaidClient instance
|
|
193
|
+
* @param {string} projectId - Project UUID
|
|
194
|
+
* @param {string} requestId - The request id (from `onAccepted` or your own)
|
|
195
|
+
* @param {number} [timeout=10000] - How long the service may say NOTHING, in ms
|
|
196
|
+
* (0, a negative number or null: no limit)
|
|
197
|
+
* @param {function} [onProgress] - Called with each progress payload {percent, message}
|
|
198
|
+
* @param {AbortSignal} [signal] - Abort to stop waiting; rejects with an AbortError
|
|
199
|
+
* @returns {Promise<any>} The service's result
|
|
200
|
+
*/
|
|
201
|
+
export function attachServiceRequest(client: any, projectId: string, requestId: string, timeout?: number, onProgress?: Function, signal?: AbortSignal): Promise<any>;
|
|
202
|
+
/**
|
|
203
|
+
* The cancellation half of a responseHelper: a flag, a depth counter, and one
|
|
204
|
+
* rule about when a checkpoint may raise. Split from the transport so it can
|
|
205
|
+
* be reasoned about on its own. Mirrors `CancelScope` in the Python client.
|
|
206
|
+
* @param {() => boolean} isCancelled
|
|
207
|
+
* @returns {CancelScope}
|
|
208
|
+
*/
|
|
209
|
+
export function createCancelScope(isCancelled: () => boolean): CancelScope;
|
|
210
|
+
/**
|
|
211
|
+
* Ask the service to stop a request made earlier (by this user). The request
|
|
212
|
+
* still ends with whatever the service then reports, on the stream of whoever
|
|
213
|
+
* is awaiting it. Rejects with 404 if unknown or expired, 409 once finished.
|
|
214
|
+
*
|
|
215
|
+
* outOfBand: a DELETE, but not a write. It signals a running service and
|
|
216
|
+
* changes no project data (see the note at the top of http.js).
|
|
217
|
+
*
|
|
218
|
+
* @param {Object} client - PlaidClient instance
|
|
219
|
+
* @param {string} projectId - Project UUID
|
|
220
|
+
* @param {string} requestId - The request id
|
|
221
|
+
* @returns {Promise<void>}
|
|
222
|
+
*/
|
|
223
|
+
export function cancelServiceRequest(client: any, projectId: string, requestId: string): Promise<void>;
|
|
224
|
+
/**
|
|
225
|
+
* What a requester is told when an error carries no message of its own.
|
|
226
|
+
* Naming the JS class instead would say nothing they can act on.
|
|
227
|
+
*/
|
|
228
|
+
export const UNKNOWN_FAILURE: "The service could not finish this request.";
|
|
229
|
+
/** A write sent to the server whose answer never came back. */
|
|
230
|
+
export const UNKNOWN_OUTCOME: "The Plaid server did not answer. This change may or may not have been saved.";
|
|
231
|
+
/** The server was never reached, so nothing was sent. */
|
|
232
|
+
export const UNREACHABLE: "The Plaid server could not be reached.";
|
|
233
|
+
/**
|
|
234
|
+
* Thrown inside a handler when the requester has asked it to stop.
|
|
235
|
+
*
|
|
236
|
+
* Cooperative cancellation: nothing interrupts a handler, so the request ends
|
|
237
|
+
* at the next point the handler looks. `responseHelper.progress` is that
|
|
238
|
+
* point, which is why a long service that already reports progress needs no
|
|
239
|
+
* changes at all. `serve` catches this and ends the request as STOPPED rather
|
|
240
|
+
* than failed.
|
|
241
|
+
*/
|
|
242
|
+
export class ServiceCancelled extends Error {
|
|
243
|
+
constructor(message?: string);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The cancellation half of a responseHelper, on its own.
|
|
247
|
+
*/
|
|
248
|
+
export type CancelScope = {
|
|
249
|
+
/**
|
|
250
|
+
* True once the requester has asked this request
|
|
251
|
+
* to stop.
|
|
252
|
+
*/
|
|
253
|
+
cancelled: boolean;
|
|
254
|
+
/**
|
|
255
|
+
* Stop here if the requester has asked
|
|
256
|
+
* the request to stop.
|
|
257
|
+
*/
|
|
258
|
+
raiseIfCancelled: () => void;
|
|
259
|
+
/**
|
|
260
|
+
* Run `fn` as
|
|
261
|
+
* a stretch that must finish once begun, usually the writes.
|
|
262
|
+
*/
|
|
263
|
+
critical: <T>(fn: () => T | Promise<T>) => Promise<T>;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* `progress` is also a cancellation checkpoint: it throws
|
|
267
|
+
* `ServiceCancelled` once a stop has been asked for. `stopped` ends the
|
|
268
|
+
* request because it was asked to stop, and its result carries `stopped: true`.
|
|
269
|
+
*/
|
|
270
|
+
export type ResponseHelper = CancelScope & {
|
|
271
|
+
requestId: string;
|
|
272
|
+
requesterId: string | null;
|
|
273
|
+
progress(percent: number, message: string, extra?: Record<string, any>): void;
|
|
274
|
+
complete(data: any): void;
|
|
275
|
+
stopped(data?: any): void;
|
|
276
|
+
error(error: string | Error): void;
|
|
277
|
+
};
|
|
278
|
+
/**
|
|
279
|
+
* A connection-state transition of a service registration. "stopped": the
|
|
280
|
+
* server refused the channel for a reason retrying cannot fix (a revoked
|
|
281
|
+
* token, no write access, no such project), which ends the registration.
|
|
282
|
+
*/
|
|
283
|
+
export type ServiceStatusEvent = "registered" | "reconnected" | "disconnected" | "stopped";
|
|
284
|
+
/**
|
|
285
|
+
* What `serve` returns.
|
|
286
|
+
*/
|
|
287
|
+
export type ServiceRegistration = {
|
|
288
|
+
stop: () => void;
|
|
289
|
+
/**
|
|
290
|
+
* Still serving, including while retrying
|
|
291
|
+
* a channel through a server restart.
|
|
292
|
+
*/
|
|
293
|
+
isRunning: () => boolean;
|
|
294
|
+
/**
|
|
295
|
+
* Channel open right now, so the server
|
|
296
|
+
* sees this service as online.
|
|
297
|
+
*/
|
|
298
|
+
isConnected: () => boolean;
|
|
299
|
+
serviceInfo: import("./serviceSchema.js").ServiceInfo & {
|
|
300
|
+
extras: any;
|
|
301
|
+
};
|
|
302
|
+
};
|
package/src/services.js
CHANGED
|
@@ -132,12 +132,16 @@ export function serviceErrorMessage(error, serviceName = '', secrets = []) {
|
|
|
132
132
|
* service ever registered on the project: currently connected ones carry
|
|
133
133
|
* `online: true`; previously-seen offline ones carry `online: false` plus a
|
|
134
134
|
* `lastSeenAt` stamp. Callers that need a service they can actually submit
|
|
135
|
-
* work to should filter on `online`.
|
|
136
|
-
*
|
|
135
|
+
* work to should filter on `online`. A connected service also names who runs
|
|
136
|
+
* it, as the caller sees it: `runnerName` (the runner's display name),
|
|
137
|
+
* `runByYou`, and `servesYou`, whether it would take the caller's requests on
|
|
138
|
+
* this project. A delegating service serves members other than its runner
|
|
139
|
+
* only where the runner is a maintainer or an admin. Goes over the wire even
|
|
140
|
+
* while a batch is open on the client.
|
|
137
141
|
*
|
|
138
142
|
* @param {Object} client - PlaidClient instance
|
|
139
143
|
* @param {string} projectId - Project UUID
|
|
140
|
-
* @returns {Promise<Array>} [{serviceId, serviceName, description, extras, online, lastSeenAt}]
|
|
144
|
+
* @returns {Promise<Array>} [{serviceId, serviceName, description, extras, online, lastSeenAt, runnerName, runByYou, servesYou}]
|
|
141
145
|
*/
|
|
142
146
|
export function discoverServices(client, projectId) {
|
|
143
147
|
return client._request('GET', `/api/v1/projects/${projectId}/services`);
|
|
@@ -193,6 +197,49 @@ export function reportRequestEvent(client, projectId, requestId, body) {
|
|
|
193
197
|
*/
|
|
194
198
|
const PERMANENT_STATUSES = new Set([400, 401, 403, 404, 405, 422]);
|
|
195
199
|
|
|
200
|
+
/**
|
|
201
|
+
* The cancellation half of a responseHelper, on its own.
|
|
202
|
+
* @typedef {object} CancelScope
|
|
203
|
+
* @property {boolean} cancelled True once the requester has asked this request
|
|
204
|
+
* to stop.
|
|
205
|
+
* @property {() => void} raiseIfCancelled Stop here if the requester has asked
|
|
206
|
+
* the request to stop.
|
|
207
|
+
* @property {<T>(fn: () => T | Promise<T>) => Promise<T>} critical Run `fn` as
|
|
208
|
+
* a stretch that must finish once begun, usually the writes.
|
|
209
|
+
*/
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* What a `serve` handler gets for each request.
|
|
213
|
+
* @typedef {CancelScope & {
|
|
214
|
+
* requestId: string,
|
|
215
|
+
* requesterId: string | null,
|
|
216
|
+
* progress(percent: number, message: string, extra?: Record<string, any>): void,
|
|
217
|
+
* complete(data: any): void,
|
|
218
|
+
* stopped(data?: any): void,
|
|
219
|
+
* error(error: string | Error): void,
|
|
220
|
+
* }} ResponseHelper `progress` is also a cancellation checkpoint: it throws
|
|
221
|
+
* `ServiceCancelled` once a stop has been asked for. `stopped` ends the
|
|
222
|
+
* request because it was asked to stop, and its result carries `stopped: true`.
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A connection-state transition of a service registration. "stopped": the
|
|
227
|
+
* server refused the channel for a reason retrying cannot fix (a revoked
|
|
228
|
+
* token, no write access, no such project), which ends the registration.
|
|
229
|
+
* @typedef {"registered" | "reconnected" | "disconnected" | "stopped"} ServiceStatusEvent
|
|
230
|
+
*/
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* What `serve` returns.
|
|
234
|
+
* @typedef {object} ServiceRegistration
|
|
235
|
+
* @property {() => void} stop
|
|
236
|
+
* @property {() => boolean} isRunning Still serving, including while retrying
|
|
237
|
+
* a channel through a server restart.
|
|
238
|
+
* @property {() => boolean} isConnected Channel open right now, so the server
|
|
239
|
+
* sees this service as online.
|
|
240
|
+
* @property {import("./serviceSchema.js").ServiceInfo & {extras: any}} serviceInfo
|
|
241
|
+
*/
|
|
242
|
+
|
|
196
243
|
/**
|
|
197
244
|
* Register a service and handle incoming work requests.
|
|
198
245
|
*
|
|
@@ -203,16 +250,15 @@ const PERMANENT_STATUSES = new Set([400, 401, 403, 404, 405, 422]);
|
|
|
203
250
|
*
|
|
204
251
|
* @param {Object} client - PlaidClient instance
|
|
205
252
|
* @param {string} projectId - Project UUID
|
|
206
|
-
* @param {
|
|
207
|
-
* @param {
|
|
208
|
-
* @param {Object} extras - Optional additional metadata
|
|
209
|
-
* @param {
|
|
253
|
+
* @param {import("./serviceSchema.js").ServiceInfo} serviceInfo - {serviceId, serviceName, description}
|
|
254
|
+
* @param {(data: any, responseHelper: ResponseHelper) => void} onServiceRequest - Handler callback (data, responseHelper)
|
|
255
|
+
* @param {Object} [extras] - Optional additional metadata
|
|
256
|
+
* @param {((event: ServiceStatusEvent, projectId: string, detail?: string) => void) | null} [onStatus] - Optional callback (event, projectId, detail) for
|
|
210
257
|
* connection-state transitions: 'registered', 'reconnected', 'disconnected',
|
|
211
258
|
* and 'stopped' when the server refuses the channel for a reason retrying
|
|
212
259
|
* cannot fix (see PERMANENT_STATUSES), which ends the registration.
|
|
213
260
|
* Called once per transition, not once per retry.
|
|
214
|
-
* @returns {
|
|
215
|
-
* .serviceInfo
|
|
261
|
+
* @returns {ServiceRegistration}
|
|
216
262
|
*/
|
|
217
263
|
export function serve(client, projectId, serviceInfo, onServiceRequest, extras = {}, onStatus = null) {
|
|
218
264
|
const { serviceId, serviceName, description = '' } = serviceInfo;
|
|
@@ -573,6 +619,8 @@ export class ServiceCancelled extends Error {
|
|
|
573
619
|
* The cancellation half of a responseHelper: a flag, a depth counter, and one
|
|
574
620
|
* rule about when a checkpoint may raise. Split from the transport so it can
|
|
575
621
|
* be reasoned about on its own. Mirrors `CancelScope` in the Python client.
|
|
622
|
+
* @param {() => boolean} isCancelled
|
|
623
|
+
* @returns {CancelScope}
|
|
576
624
|
*/
|
|
577
625
|
export function createCancelScope(isCancelled) {
|
|
578
626
|
// Depth of nested `critical()` blocks; while non-zero, a checkpoint will
|
|
@@ -767,8 +815,17 @@ function streamServiceRequest(client, { url, method, body, onStatus, what }, tim
|
|
|
767
815
|
return;
|
|
768
816
|
}
|
|
769
817
|
if (!response.ok) {
|
|
770
|
-
|
|
818
|
+
// A refusal says why in its body (`{error}`), as on every other route.
|
|
819
|
+
let said = null;
|
|
820
|
+
try {
|
|
821
|
+
said = await response.json();
|
|
822
|
+
} catch {
|
|
823
|
+
said = null;
|
|
824
|
+
}
|
|
825
|
+
const why = typeof said?.error === 'string' ? `: ${said.error}` : '';
|
|
826
|
+
const err = new Error(`${what} failed: HTTP ${response.status} ${response.statusText}${why}`);
|
|
771
827
|
err.status = response.status;
|
|
828
|
+
if (said && typeof said === 'object') err.responseData = said;
|
|
772
829
|
// A proxy's 502 or 504 is no answer from the server: the request may
|
|
773
830
|
// have been taken, and the service may be running it.
|
|
774
831
|
finish(reject, response.status === 502 || response.status === 504 ? lostAnswer(err) : err);
|
package/src/sse.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project a parsed SSE `data:` object onto what a listener callback sees.
|
|
3
|
+
*
|
|
4
|
+
* The envelope is API surface and gets the usual `transformResponse` recasing.
|
|
5
|
+
* A `message` event's `data` is NOT: it is opaque application data (whatever
|
|
6
|
+
* `sendMessage` was handed), so its keys must survive verbatim, exactly as
|
|
7
|
+
* `metadata` and `config` do elsewhere. Without this, a key written
|
|
8
|
+
* `case-marker` reaches a JavaScript listener as `caseMarker` and a Python
|
|
9
|
+
* listener as `case_marker`, so the same broadcast reads differently depending
|
|
10
|
+
* on the reader's language.
|
|
11
|
+
*/
|
|
12
|
+
export function eventPayload(eventType: any, parsed: any): any;
|
|
13
|
+
/**
|
|
14
|
+
* Create an SSE connection to the listen endpoint using fetch-based streaming.
|
|
15
|
+
* Automatically handles heartbeat confirmations and event parsing.
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} client - PlaidClient instance
|
|
18
|
+
* @param {string} projectId - Project UUID
|
|
19
|
+
* @param {function} onEvent - Callback (eventType, data). Return true to stop.
|
|
20
|
+
* @param {string} [path] - Stream path under baseUrl. Defaults to the project
|
|
21
|
+
* /listen bus; service request channels pass their own. Only /listen emits
|
|
22
|
+
* `heartbeat` events needing a POST confirmation — other streams keep
|
|
23
|
+
* themselves alive with ignored SSE comments.
|
|
24
|
+
* @returns {Object} SSE connection with .close(), .getStats(), .readyState,
|
|
25
|
+
* and .error: once CLOSED, why (null when this side closed it). A refused
|
|
26
|
+
* stream's error carries the HTTP `status`, and a stream the server ended
|
|
27
|
+
* carries an error named `StreamClosed`.
|
|
28
|
+
*/
|
|
29
|
+
export function createSSEConnection(client: any, projectId: string, onEvent: Function, path?: string): any;
|
|
30
|
+
export const SSE_IDLE_TIMEOUT_MS: 60000;
|
package/src/sse.js
CHANGED
|
@@ -36,6 +36,12 @@ export function eventPayload(eventType, parsed) {
|
|
|
36
36
|
return payload;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Tell the client a stream of its own dropped or opened (a stand-in client in
|
|
40
|
+
// a test may not listen).
|
|
41
|
+
function tell(client, hook) {
|
|
42
|
+
try { client[hook]?.(); } catch (_) { /* the stream must not die of it */ }
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
/**
|
|
40
46
|
* Create an SSE connection to the listen endpoint using fetch-based streaming.
|
|
41
47
|
* Automatically handles heartbeat confirmations and event parsing.
|
|
@@ -147,6 +153,8 @@ export function createSSEConnection(client, projectId, onEvent, path) {
|
|
|
147
153
|
isConnected = true;
|
|
148
154
|
sseConnection.readyState = 1; // OPEN
|
|
149
155
|
lastWarning.delete(url);
|
|
156
|
+
// After a drop the server may have restarted with other limits.
|
|
157
|
+
tell(client, '_noteStreamOpened');
|
|
150
158
|
|
|
151
159
|
const reader = response.body.getReader();
|
|
152
160
|
const decoder = new TextDecoder();
|
|
@@ -219,10 +227,13 @@ export function createSSEConnection(client, projectId, onEvent, path) {
|
|
|
219
227
|
}
|
|
220
228
|
}
|
|
221
229
|
} finally {
|
|
230
|
+
// Ended without a close() of this side's own: a drop.
|
|
231
|
+
const dropped = !isClosed;
|
|
222
232
|
clearIdleTimer();
|
|
223
233
|
isConnected = false;
|
|
224
234
|
isClosed = true;
|
|
225
235
|
sseConnection.readyState = 2; // CLOSED
|
|
236
|
+
if (dropped) tell(client, '_noteStreamDropped');
|
|
226
237
|
}
|
|
227
238
|
})();
|
|
228
239
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code points of `body`, as a frozen array shared with other callers.
|
|
3
|
+
* @param {string} body
|
|
4
|
+
* @returns {readonly string[]}
|
|
5
|
+
*/
|
|
6
|
+
export function bodyCodePoints(body: string): readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* The net change of `ops` applied in turn to `body`, as gaps in the old
|
|
9
|
+
* body's code points: sorted, and never touching (at least one old code point
|
|
10
|
+
* is kept between two gaps). Old text deleted and typed back is typed text,
|
|
11
|
+
* and a gap whose value is the old text it removes is dropped. Throws on a
|
|
12
|
+
* malformed or out-of-bounds op.
|
|
13
|
+
* @param {string} body
|
|
14
|
+
* @param {TextEditOp[]} ops
|
|
15
|
+
* @returns {TextGap[]}
|
|
16
|
+
*/
|
|
17
|
+
export function composeTextEdits(body: string, ops: TextEditOp[]): TextGap[];
|
|
18
|
+
/**
|
|
19
|
+
* Gaps (old-body coordinates, sorted, not overlapping) as running ops: an
|
|
20
|
+
* insert where a gap removes nothing, a delete where it types nothing, and a
|
|
21
|
+
* replace otherwise.
|
|
22
|
+
* @param {TextGap[]} gaps
|
|
23
|
+
* @returns {TextEditOp[]}
|
|
24
|
+
*/
|
|
25
|
+
export function gapsToOps(gaps: TextGap[]): TextEditOp[];
|
|
26
|
+
/**
|
|
27
|
+
* `body` with `ops` applied in turn (running coordinates, code points).
|
|
28
|
+
* Throws on a malformed or out-of-bounds op.
|
|
29
|
+
* @param {string} body
|
|
30
|
+
* @param {TextEditOp[]} ops
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function applyTextOps(body: string, ops: TextEditOp[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* One op as `PATCH /texts/:id` takes it. Each index is in the body the ops
|
|
36
|
+
* before it left.
|
|
37
|
+
*/
|
|
38
|
+
export type TextEditOp = {
|
|
39
|
+
type: "insert";
|
|
40
|
+
index: number;
|
|
41
|
+
value: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "delete";
|
|
44
|
+
index: number;
|
|
45
|
+
value: number;
|
|
46
|
+
} | {
|
|
47
|
+
type: "replace";
|
|
48
|
+
index: number;
|
|
49
|
+
length: number;
|
|
50
|
+
value: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Old-body code points [start, end) give way to `value`.
|
|
54
|
+
*/
|
|
55
|
+
export type TextGap = {
|
|
56
|
+
start: number;
|
|
57
|
+
end: number;
|
|
58
|
+
value: string;
|
|
59
|
+
};
|
package/src/textEdits.js
CHANGED
|
@@ -18,6 +18,19 @@
|
|
|
18
18
|
* `plaid_client.text_edits` is the twin of this module.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* One op as `PATCH /texts/:id` takes it. Each index is in the body the ops
|
|
23
|
+
* before it left.
|
|
24
|
+
* @typedef {{type: "insert", index: number, value: string}
|
|
25
|
+
* | {type: "delete", index: number, value: number}
|
|
26
|
+
* | {type: "replace", index: number, length: number, value: string}} TextEditOp
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Old-body code points [start, end) give way to `value`.
|
|
31
|
+
* @typedef {{start: number, end: number, value: string}} TextGap
|
|
32
|
+
*/
|
|
33
|
+
|
|
21
34
|
const codePoints = (s) => [...s];
|
|
22
35
|
|
|
23
36
|
// The last body split into code points, kept: an edit log composes the same
|
|
@@ -27,7 +40,11 @@ const codePoints = (s) => [...s];
|
|
|
27
40
|
let lastBody = null;
|
|
28
41
|
let lastPoints = Object.freeze([]);
|
|
29
42
|
|
|
30
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The code points of `body`, as a frozen array shared with other callers.
|
|
45
|
+
* @param {string} body
|
|
46
|
+
* @returns {readonly string[]}
|
|
47
|
+
*/
|
|
31
48
|
export function bodyCodePoints(body) {
|
|
32
49
|
if (body !== lastBody) {
|
|
33
50
|
lastPoints = Object.freeze(codePoints(body));
|
|
@@ -112,6 +129,9 @@ function cutAt(segs, at) {
|
|
|
112
129
|
* is kept between two gaps). Old text deleted and typed back is typed text,
|
|
113
130
|
* and a gap whose value is the old text it removes is dropped. Throws on a
|
|
114
131
|
* malformed or out-of-bounds op.
|
|
132
|
+
* @param {string} body
|
|
133
|
+
* @param {TextEditOp[]} ops
|
|
134
|
+
* @returns {TextGap[]}
|
|
115
135
|
*/
|
|
116
136
|
export function composeTextEdits(body, ops) {
|
|
117
137
|
const old = bodyCodePoints(body);
|
|
@@ -171,6 +191,8 @@ function mergeTyped(segs) {
|
|
|
171
191
|
* Gaps (old-body coordinates, sorted, not overlapping) as running ops: an
|
|
172
192
|
* insert where a gap removes nothing, a delete where it types nothing, and a
|
|
173
193
|
* replace otherwise.
|
|
194
|
+
* @param {TextGap[]} gaps
|
|
195
|
+
* @returns {TextEditOp[]}
|
|
174
196
|
*/
|
|
175
197
|
export function gapsToOps(gaps) {
|
|
176
198
|
const ops = [];
|
|
@@ -193,6 +215,9 @@ export function gapsToOps(gaps) {
|
|
|
193
215
|
/**
|
|
194
216
|
* `body` with `ops` applied in turn (running coordinates, code points).
|
|
195
217
|
* Throws on a malformed or out-of-bounds op.
|
|
218
|
+
* @param {string} body
|
|
219
|
+
* @param {TextEditOp[]} ops
|
|
220
|
+
* @returns {string}
|
|
196
221
|
*/
|
|
197
222
|
export function applyTextOps(body, ops) {
|
|
198
223
|
let chars = codePoints(body);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert kebab-case/namespaced key to camelCase.
|
|
3
|
+
* 'layer-id' -> 'layerId'
|
|
4
|
+
* 'relation/layer' -> 'layer' (namespace stripped)
|
|
5
|
+
* Hyphens before a digit are also consumed ('layer-2' -> 'layer2') so no stray
|
|
6
|
+
* hyphen is ever left in the key. (The Python client uses snake_case, where the
|
|
7
|
+
* analogous key is 'layer_2' — the local spelling differs by convention, but
|
|
8
|
+
* neither leaves a separator that doesn't belong to the convention.)
|
|
9
|
+
*/
|
|
10
|
+
export function transformKeyToCamel(key: any): any;
|
|
11
|
+
/**
|
|
12
|
+
* Convert camelCase key to kebab-case.
|
|
13
|
+
* 'layerId' -> 'layer-id'
|
|
14
|
+
*/
|
|
15
|
+
export function transformKeyFromCamel(key: any): any;
|
|
16
|
+
/**
|
|
17
|
+
* Recursively transform request object keys from camelCase to kebab-case.
|
|
18
|
+
* Preserves `metadata` and `config` contents without transformation.
|
|
19
|
+
*/
|
|
20
|
+
export function transformRequest(obj: any): any;
|
|
21
|
+
/**
|
|
22
|
+
* Recursively transform response object keys from kebab-case/namespaced to camelCase.
|
|
23
|
+
* Preserves `metadata` and `config` contents without transformation.
|
|
24
|
+
*/
|
|
25
|
+
export function transformResponse(obj: any): any;
|