@procedures/client 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +20 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/server/transport.d.ts +6 -0
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +10 -1
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/websocket/client/transport.d.ts +7 -3
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +9 -3
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +10 -0
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +22 -2
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/browser.d.ts +4 -4
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/client/errors/registry.d.ts.map +1 -1
- package/dist/client/errors/registry.js +1 -0
- package/dist/client/errors/registry.js.map +1 -1
- package/dist/client/middleware/retry.d.ts +4 -0
- package/dist/client/middleware/retry.d.ts.map +1 -1
- package/dist/client/middleware/retry.js +86 -78
- package/dist/client/middleware/retry.js.map +1 -1
- package/dist/events/bus.d.ts +32 -14
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +119 -65
- package/dist/events/bus.js.map +1 -1
- package/dist/events/index.d.ts +3 -2
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js.map +1 -1
- package/dist/events/publisher.d.ts.map +1 -1
- package/dist/events/publisher.js +20 -6
- package/dist/events/publisher.js.map +1 -1
- package/dist/events/remote.d.ts +80 -18
- package/dist/events/remote.d.ts.map +1 -1
- package/dist/events/remote.js +448 -58
- package/dist/events/remote.js.map +1 -1
- package/dist/events/types.d.ts +13 -2
- package/dist/events/types.d.ts.map +1 -1
- package/dist/procedures/caller.d.ts +33 -0
- package/dist/procedures/caller.d.ts.map +1 -0
- package/dist/procedures/caller.js +27 -0
- package/dist/procedures/caller.js.map +1 -0
- package/dist/procedures/collection/index.d.ts +2 -2
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +1 -1
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +21 -1
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/schemas.d.ts +9 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +28 -0
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +6 -0
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/index.d.ts +3 -1
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +2 -0
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +3 -0
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/types.d.ts +50 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +1 -0
- package/dist/protocol.js.map +1 -1
- package/dist/server/procedure-server.d.ts +37 -3
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +86 -4
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/timers.d.ts +5 -0
- package/dist/timers.d.ts.map +1 -1
- package/dist/timers.js +3 -1
- package/dist/timers.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/http/server/transport.ts +11 -1
- package/src/adapters/websocket/client/transport.ts +9 -3
- package/src/adapters/websocket/server/transport.ts +23 -2
- package/src/browser.ts +6 -0
- package/src/client/errors/registry.ts +1 -0
- package/src/client/middleware/retry.ts +89 -78
- package/src/events/bus.ts +141 -73
- package/src/events/index.ts +3 -0
- package/src/events/publisher.ts +23 -6
- package/src/events/remote.ts +575 -73
- package/src/events/types.ts +15 -2
- package/src/procedures/caller.ts +45 -0
- package/src/procedures/collection/index.ts +3 -0
- package/src/procedures/collection/procedures.ts +22 -0
- package/src/procedures/collection/schemas.ts +37 -0
- package/src/procedures/collection/storage.ts +6 -0
- package/src/procedures/index.ts +4 -0
- package/src/procedures/invoke.ts +2 -0
- package/src/procedures/types.ts +53 -0
- package/src/protocol.ts +3 -1
- package/src/server/procedure-server.ts +103 -6
- package/src/timers.ts +8 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/client",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"description": "Procedures as data for TypeScript: define typed procedures once and call them in process, over HTTP, WebSocket, the CLI or MCP. Streaming, middleware, retries, caching and a JSON program format.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"rpc",
|
|
@@ -566,7 +566,7 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
566
566
|
|
|
567
567
|
server.listen(this.options.port, this.options.host, () => {
|
|
568
568
|
console.log(
|
|
569
|
-
`[${this.name}] Server listening on http://${this.options.host}:${this.options.port}`
|
|
569
|
+
`[${this.name}] Server listening on http://${this.options.host}:${this.port() ?? this.options.port}`
|
|
570
570
|
);
|
|
571
571
|
resolve();
|
|
572
572
|
});
|
|
@@ -610,4 +610,14 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
610
610
|
isRunning(): boolean {
|
|
611
611
|
return !this.stopped && this.httpServer !== null && this.httpServer.listening;
|
|
612
612
|
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* The port that the server of the transport listens on. With the option `port` 0, the system
|
|
616
|
+
* gives a free port, and this function returns that port. Before `start()` and after `stop()`,
|
|
617
|
+
* the function returns undefined.
|
|
618
|
+
*/
|
|
619
|
+
port(): number | undefined {
|
|
620
|
+
const address = this.httpServer?.listening ? this.httpServer.address() : null;
|
|
621
|
+
return typeof address === "object" && address !== null ? address.port : undefined;
|
|
622
|
+
}
|
|
613
623
|
}
|
|
@@ -874,11 +874,12 @@ export class WebSocketTransport implements Transport {
|
|
|
874
874
|
/**
|
|
875
875
|
* This function subscribes to topics of the bus of the server (`documentation/EVENTS.md`). The
|
|
876
876
|
* handler gets each event in order. After a drop, the transport sends the subscription again on
|
|
877
|
-
* the new connection. The events between the drop and the new confirmation are lost.
|
|
877
|
+
* the new connection. The events between the drop and the new confirmation are lost. The
|
|
878
|
+
* subscribers of the same topics with the same options share one call.
|
|
878
879
|
*
|
|
879
880
|
* @param topics - One topic, or a list of 1 to 64 topics
|
|
880
881
|
* @param handler - The handler of each event. A promise that it gives holds the next event.
|
|
881
|
-
* @param options - `onSubscribed`, `onError`, a signal, the metadata of the call
|
|
882
|
+
* @param options - `onSubscribed`, `onError`, a signal, the metadata of the call, the resubscribe rules
|
|
882
883
|
* @returns The unsubscribe function, with the `ready` promise of the first confirmation
|
|
883
884
|
*
|
|
884
885
|
* @example
|
|
@@ -904,12 +905,17 @@ export class WebSocketTransport implements Transport {
|
|
|
904
905
|
return remoteEvents<T>(this, topics, options, this.subscriptionHooks());
|
|
905
906
|
}
|
|
906
907
|
|
|
907
|
-
/**
|
|
908
|
+
/**
|
|
909
|
+
* The parts of the subscriptions of this transport: its connection, its backoff, its
|
|
910
|
+
* `maxAttempts`, its clock, its close and `onEvent`.
|
|
911
|
+
*/
|
|
908
912
|
private subscriptionHooks(): SubscriptionHooks {
|
|
909
913
|
return {
|
|
910
914
|
lifetime: this.subscriptions.signal,
|
|
911
915
|
ready: (signal) => this.whenConnected(signal),
|
|
912
916
|
backoff: (attempt) => reconnectDelay(attempt, this.options.reconnect),
|
|
917
|
+
maxAttempts: this.options.reconnect.maxAttempts,
|
|
918
|
+
clock: this.clock,
|
|
913
919
|
onEvent: (event) => {
|
|
914
920
|
if (!this.options.onEvent) return;
|
|
915
921
|
try {
|
|
@@ -90,6 +90,12 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
90
90
|
*/
|
|
91
91
|
private readonly channels = new Map<string, { incoming: IncomingCalls; outgoing: OutgoingCalls }>();
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* The transport of the remote events of each connection: one object for each connection, so the
|
|
95
|
+
* subscriptions of one connection to the same topics share one call (EVENTS.md).
|
|
96
|
+
*/
|
|
97
|
+
private readonly eventTransports = new Map<string, Transport>();
|
|
98
|
+
|
|
93
99
|
/** The browser checks of each connection (see `server/browser-guard.ts`) */
|
|
94
100
|
private readonly guard: BrowserGuardOptions;
|
|
95
101
|
|
|
@@ -303,6 +309,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
303
309
|
channel?.incoming.abortAll();
|
|
304
310
|
channel?.outgoing.failAll("WebSocket connection closed");
|
|
305
311
|
this.channels.delete(connectionId);
|
|
312
|
+
this.eventTransports.delete(connectionId);
|
|
306
313
|
const conn = this.trackedConnections.get(connectionId);
|
|
307
314
|
if (conn) {
|
|
308
315
|
this.trackedConnections.delete(connectionId);
|
|
@@ -552,7 +559,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
552
559
|
handler: RemoteEventHandler<T>,
|
|
553
560
|
options: SubscribeOptions = {},
|
|
554
561
|
): Subscription {
|
|
555
|
-
return subscribeRemote<T>(this.
|
|
562
|
+
return subscribeRemote<T>(this.eventTransportFor(connectionId), topics, handler, options, { resubscribe: false });
|
|
556
563
|
}
|
|
557
564
|
|
|
558
565
|
/**
|
|
@@ -564,7 +571,21 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
564
571
|
* @param options - A signal, `onSubscribed`, the metadata of the call
|
|
565
572
|
*/
|
|
566
573
|
events<T = unknown>(connectionId: string, topics: string | readonly string[], options: RemoteEventsOptions = {}): AsyncIterable<RemoteEvent<T>> {
|
|
567
|
-
return remoteEvents<T>(this.
|
|
574
|
+
return remoteEvents<T>(this.eventTransportFor(connectionId), topics, options, { resubscribe: false });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* The transport of the remote events of a connection: the same object for each subscription of
|
|
579
|
+
* a tracked connection. The close of the connection removes it.
|
|
580
|
+
*/
|
|
581
|
+
private eventTransportFor(connectionId: string): Transport {
|
|
582
|
+
if (!this.trackedConnections.has(connectionId)) return this.transportFor(connectionId);
|
|
583
|
+
let transport = this.eventTransports.get(connectionId);
|
|
584
|
+
if (!transport) {
|
|
585
|
+
transport = this.transportFor(connectionId);
|
|
586
|
+
this.eventTransports.set(connectionId, transport);
|
|
587
|
+
}
|
|
588
|
+
return transport;
|
|
568
589
|
}
|
|
569
590
|
|
|
570
591
|
/**
|
package/src/browser.ts
CHANGED
|
@@ -128,6 +128,7 @@ export {
|
|
|
128
128
|
registerModules,
|
|
129
129
|
createRegistry,
|
|
130
130
|
registerClientProcedures,
|
|
131
|
+
callerOf,
|
|
131
132
|
// Procedure reference system (procedure-as-data)
|
|
132
133
|
PROCEDURE_SYMBOL,
|
|
133
134
|
PROCEDURE_JSON_KEY,
|
|
@@ -213,6 +214,7 @@ export {
|
|
|
213
214
|
|
|
214
215
|
export type {
|
|
215
216
|
Effects,
|
|
217
|
+
FsRootRules,
|
|
216
218
|
PermittedEffects,
|
|
217
219
|
Register,
|
|
218
220
|
ClearanceOf,
|
|
@@ -235,6 +237,8 @@ export type {
|
|
|
235
237
|
StorageMetadata,
|
|
236
238
|
ProcedureModule,
|
|
237
239
|
ModuleInput,
|
|
240
|
+
Caller,
|
|
241
|
+
CallerOptions,
|
|
238
242
|
RegistrationOptions,
|
|
239
243
|
ProcedureRegistryLike,
|
|
240
244
|
ProcedureDefinition,
|
|
@@ -411,6 +415,7 @@ export type {
|
|
|
411
415
|
TypedEventBus,
|
|
412
416
|
EventHandler,
|
|
413
417
|
EventBusOptions,
|
|
418
|
+
EventListenOptions,
|
|
414
419
|
EventWaitOptions,
|
|
415
420
|
ChannelMap,
|
|
416
421
|
} from "./events/index.js";
|
|
@@ -424,6 +429,7 @@ export type {
|
|
|
424
429
|
RemoteEvent,
|
|
425
430
|
RemoteEventHandler,
|
|
426
431
|
RemoteEventsOptions,
|
|
432
|
+
ResubscribeBackoff,
|
|
427
433
|
SubscribeOptions,
|
|
428
434
|
Subscription,
|
|
429
435
|
} from "./events/index.js";
|
|
@@ -84,6 +84,7 @@ const PROTOCOL_CATEGORIES: Readonly<Record<ProtocolErrorCode, ErrorCategory>> =
|
|
|
84
84
|
CONNECTION_CLOSED: ErrorCategory.NETWORK,
|
|
85
85
|
CONNECTION_NOT_FOUND: ErrorCategory.CLIENT,
|
|
86
86
|
TOPIC_NOT_FOUND: ErrorCategory.CLIENT,
|
|
87
|
+
TOPIC_CLEARED: ErrorCategory.CANCELLED,
|
|
87
88
|
};
|
|
88
89
|
|
|
89
90
|
/**
|
|
@@ -108,6 +108,10 @@ export interface RetryOptions {
|
|
|
108
108
|
/**
|
|
109
109
|
* Hook called after each retry completes (success or failure).
|
|
110
110
|
*
|
|
111
|
+
* The middleware calls it when the call ends: after the last item, or when the reader stops
|
|
112
|
+
* reading. A `Client` with `throwOnError` stops at the error item. Before, the hook then did
|
|
113
|
+
* not run, so it did not hear of a failure. A thrown error and an abort do not call it.
|
|
114
|
+
*
|
|
111
115
|
* @param success - Whether the retry succeeded
|
|
112
116
|
* @param attempt - Attempt number that just completed
|
|
113
117
|
*/
|
|
@@ -249,99 +253,106 @@ export function createRetryMiddleware(options: RetryOptions = {}): TypedClientMi
|
|
|
249
253
|
const idempotencyKey = idempotencyKeyOf(context.message.metadata) ?? newIdempotencyKey();
|
|
250
254
|
context.message.metadata[IDEMPOTENCY_KEY_METADATA] = idempotencyKey;
|
|
251
255
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
// The outcome of the call for onAfterRetry. The hook runs in `finally`: a reader that stops
|
|
257
|
+
// at an error item (a Client with throwOnError) closes this generator at its yield. Before,
|
|
258
|
+
// the hook came after the yield, so it did not hear of a failure.
|
|
259
|
+
let outcome: { success: boolean; attempt: number } | undefined;
|
|
260
|
+
try {
|
|
261
|
+
let attempt = 0;
|
|
262
|
+
for (;;) {
|
|
263
|
+
// Update retry metadata with current state
|
|
264
|
+
context.message.metadata.retry = {
|
|
265
|
+
...(typeof given === "object" && given !== null ? (given as object) : {}),
|
|
266
|
+
attempt,
|
|
267
|
+
maxAttempts: maxRetries + 1,
|
|
268
|
+
idempotent,
|
|
269
|
+
};
|
|
270
|
+
// Each attempt gets its own request id. The id correlates the frames of one attempt
|
|
271
|
+
// on the wire: a cancelled attempt can still answer under its id (deep dive TRN-8/9).
|
|
272
|
+
context.message.id = attempt === 0 ? baseId : `${baseId}.retry${attempt}`;
|
|
264
273
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
274
|
+
// Check if cancelled before attempt
|
|
275
|
+
if (signal?.aborted) {
|
|
276
|
+
yield abortedItem<TRes>(context.message.id);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
270
279
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
// The items pass through as they arrive (BUGS-2026-07 M2: before, the middleware
|
|
281
|
+
// collected the whole stream first). A retry is possible only while no item has
|
|
282
|
+
// reached the reader: the first item is a retryable error, or the attempt throws
|
|
283
|
+
// before its first item. After that, a retry would repeat items.
|
|
284
|
+
let started = false;
|
|
285
|
+
let lastItem: ResponseItem<TRes> | undefined;
|
|
286
|
+
let retryItem: ResponseItem<TRes> | undefined;
|
|
287
|
+
try {
|
|
288
|
+
for await (const item of next(context)) {
|
|
289
|
+
if (!started && attempt < maxRetries && shouldRetryItem(item, attempt)) {
|
|
290
|
+
retryItem = item;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
started = true;
|
|
294
|
+
lastItem = item;
|
|
295
|
+
outcome = { success: item.status.type === "success", attempt };
|
|
296
|
+
yield item;
|
|
283
297
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
298
|
+
} catch (error) {
|
|
299
|
+
// A thrown error does not call onAfterRetry
|
|
300
|
+
outcome = undefined;
|
|
301
|
+
const isAborted = signal?.aborted || (error instanceof Error && error.name === "AbortError");
|
|
302
|
+
if (started || isAborted || attempt >= maxRetries) {
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
// A thrown error is retried only when it is a network failure, or when shouldRetry
|
|
306
|
+
// says so. Before, every thrown error was retried: a validation error, an open
|
|
307
|
+
// circuit breaker, a rate limit (deep dive TRN-8).
|
|
308
|
+
const item = thrownItem<TRes>(context.message.id, error);
|
|
309
|
+
if (!shouldRetryItem(item, attempt)) {
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
retryItem = item;
|
|
292
313
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
throw error;
|
|
314
|
+
|
|
315
|
+
if (retryItem === undefined) {
|
|
316
|
+
// Success, a non-retryable error, or the last attempt
|
|
317
|
+
outcome = { success: lastItem?.status.type === "success", attempt };
|
|
318
|
+
return;
|
|
299
319
|
}
|
|
300
|
-
retryItem = item;
|
|
301
|
-
}
|
|
302
320
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
321
|
+
// The budget is empty: the error goes to the caller (R12)
|
|
322
|
+
if (!takeRetry()) {
|
|
323
|
+
outcome = { success: false, attempt };
|
|
324
|
+
yield retryItem;
|
|
325
|
+
return;
|
|
307
326
|
}
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
327
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
yield retryItem;
|
|
314
|
-
if (onAfterRetry) await onAfterRetry(false, attempt);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
328
|
+
// A server that asks for a wait (Retry-After) gets at least that wait (R12)
|
|
329
|
+
let delay = Math.max(retryDelayOf(attempt, overrides), retryAfterOf(retryItem) ?? 0);
|
|
317
330
|
|
|
318
|
-
|
|
319
|
-
|
|
331
|
+
// Before retry hook
|
|
332
|
+
if (onBeforeRetry) {
|
|
333
|
+
const hookResult = await onBeforeRetry(retryItem, attempt);
|
|
334
|
+
if (!hookResult.shouldRetry) {
|
|
335
|
+
// Hook decided not to retry - yield the error and return
|
|
336
|
+
outcome = { success: false, attempt };
|
|
337
|
+
yield retryItem;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
320
340
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (!hookResult.shouldRetry) {
|
|
325
|
-
// Hook decided not to retry - yield the error and return
|
|
326
|
-
yield retryItem;
|
|
327
|
-
if (onAfterRetry) {
|
|
328
|
-
await onAfterRetry(false, attempt);
|
|
341
|
+
// Use custom delay if provided by hook
|
|
342
|
+
if (hookResult.delayMs !== undefined) {
|
|
343
|
+
delay = hookResult.delayMs;
|
|
329
344
|
}
|
|
330
|
-
return;
|
|
331
345
|
}
|
|
332
346
|
|
|
333
|
-
//
|
|
334
|
-
if (
|
|
335
|
-
|
|
347
|
+
// The backoff stops at once when the caller aborts (deep dive TRN-8)
|
|
348
|
+
if (!(await sleep(delay, signal, clock))) {
|
|
349
|
+
yield abortedItem<TRes>(context.message.id);
|
|
350
|
+
return;
|
|
336
351
|
}
|
|
352
|
+
attempt++;
|
|
337
353
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (!(await sleep(delay, signal, clock))) {
|
|
341
|
-
yield abortedItem<TRes>(context.message.id);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
attempt++;
|
|
354
|
+
} finally {
|
|
355
|
+
if (outcome && onAfterRetry) await onAfterRetry(outcome.success, outcome.attempt);
|
|
345
356
|
}
|
|
346
357
|
};
|
|
347
358
|
};
|