@@ -4,7 +4,7 @@ import type {
44 APIResponse ,
55} from "../proto.ts" ;
66
7- export function cacheGeneratorMethod < Sync extends ( ...args : any [ ] ) => any , Gen extends ( ...args : any [ ] ) => APIRequestGenerator > (
7+ export function cacheGeneratorMethod < Sync extends ( ...args : any [ ] ) => any , Gen extends ( ...args : any [ ] ) => AnyAPIRequestGenerator > (
88 owner : object ,
99 name : PropertyKey ,
1010 sync : Sync ,
@@ -22,82 +22,144 @@ export function apiRequest<Method extends keyof APIMethodInfo>(
2222export function * apiRequest ( method : PropertyKey , params : unknown ) : Generator < { method : PropertyKey ; params : unknown ; } , unknown , unknown > {
2323 return yield { method, params } ;
2424}
25- export type APIRequestGenerator < Return = any > = Generator < APIRequest | readonly APIRequest [ ] , Return , any > ;
25+ const deferredGeneratorMarker : unique symbol = Symbol ( ) ;
26+ interface DeferredAPIRequest {
27+ readonly deferred : APIRequestGenerator ;
28+ }
29+ type APIRequestGeneratorYield = APIRequest | readonly APIRequest [ ] | DeferredAPIRequest ;
30+ export type APIRequestGenerator < Return = any > = Generator < APIRequestGeneratorYield , Return , any > ;
31+ export type DeferredAPIRequestGenerator = Generator < DeferredAPIRequest , void , unknown > & { readonly [ deferredGeneratorMarker ] : true ; } ;
32+ export type AnyAPIRequestGenerator < Return = any > = APIRequestGenerator < Return > | DeferredAPIRequestGenerator ;
2633type GeneratorReturn < T > = T extends Generator < any , infer R , any > ? R : never ;
27- export type ExecutedGeneratorsResults < T extends readonly APIRequestGenerator [ ] > = {
28- [ K in keyof T ] : GeneratorReturn < T [ K ] > ;
29- } ;
34+ export type ExecutedGeneratorsResults < T extends readonly AnyAPIRequestGenerator [ ] > = number extends T [ "length" ] ? GeneratorReturn < Exclude < T [ number ] , DeferredAPIRequestGenerator > > [ ]
35+ : T extends readonly [ infer Head extends AnyAPIRequestGenerator , ...infer Tail extends readonly AnyAPIRequestGenerator [ ] ] ? Head extends DeferredAPIRequestGenerator ? ExecutedGeneratorsResults < Tail > : [ GeneratorReturn < Head > , ... ExecutedGeneratorsResults < Tail > ]
36+ : [ ] ;
3037
3138interface GeneratorResponse {
3239 result : unknown ;
3340 error ?: string | undefined ;
3441}
3542
36- export function all < const T extends readonly APIRequestGenerator [ ] > (
37- ...requestGenerators : T
38- ) : Generator < APIRequest [ ] , ExecutedGeneratorsResults < T > , readonly GeneratorResponse [ ] > ;
39- export function * all < T extends readonly APIRequestGenerator [ ] > (
40- ...requestGenerators : T
41- ) : Generator < APIRequest [ ] , ExecutedGeneratorsResults < T > , readonly GeneratorResponse [ ] > {
42- const results : any [ ] = [ ] ;
43- const requestObjects : ( APIRequest | readonly APIRequest [ ] | undefined ) [ ] = [ ] ;
44- const completedIndices = new Set < number > ( ) ;
45- for ( let i = 0 ; i < requestGenerators . length ; i ++ ) {
46- const state = requestGenerators [ i ] . next ( ) ;
43+ interface RequestRunnerOptions {
44+ executeDeferred ?: boolean ;
45+ }
46+
47+ function createRequestRunner < T extends readonly AnyAPIRequestGenerator [ ] > ( requestGenerators : T , options : RequestRunnerOptions = { } ) {
48+ const registeredGenerators = new Set < AnyAPIRequestGenerator > ( ) ;
49+ const requestsByGenerator = new Map < AnyAPIRequestGenerator , APIRequestGeneratorYield > ( ) ;
50+ const resultsByGenerator = new Map < AnyAPIRequestGenerator , unknown > ( ) ;
51+ for ( const generator of requestGenerators ) {
52+ addGenerator ( generator ) ;
53+ }
54+ const requestRounds = runRequestRounds ( ) ;
55+ return { requestRounds, getResults } ;
56+
57+ function advanceGenerator ( generator : AnyAPIRequestGenerator , value ?: unknown , error ?: string ) : void {
58+ let state = error === undefined
59+ ? generator . next ( value )
60+ : generator . throw ( new Error ( error ) ) ;
61+ while ( ! state . done && isDeferredAPIRequest ( state . value ) && options . executeDeferred ) {
62+ addGenerator ( state . value . deferred ) ;
63+ state = generator . next ( ) ;
64+ }
4765 if ( state . done ) {
48- results [ i ] = state . value ;
49- completedIndices . add ( i ) ;
66+ requestsByGenerator . delete ( generator ) ;
67+ resultsByGenerator . set ( generator , state . value ) ;
5068 }
5169 else {
52- requestObjects [ i ] = state . value ;
70+ requestsByGenerator . set ( generator , state . value ) ;
5371 }
5472 }
55- while ( completedIndices . size < requestGenerators . length ) {
56- const requests : APIRequest [ ] = [ ] ;
57- const responseIndices : ( number | readonly number [ ] | undefined ) [ ] = [ ] ;
58- const responseIndexByDeduplicationKey = new Map < string , number > ( ) ;
59- const addRequest = ( request : APIRequest ) : number => {
60- const deduplicationKey = getRequestDeduplicationKey ( request ) ;
61- let responseIndex = deduplicationKey === undefined ? undefined : responseIndexByDeduplicationKey . get ( deduplicationKey ) ;
62- if ( responseIndex === undefined ) {
63- responseIndex = requests . length ;
64- requests . push ( request ) ;
65- if ( deduplicationKey !== undefined ) responseIndexByDeduplicationKey . set ( deduplicationKey , responseIndex ) ;
66- }
67- return responseIndex ;
68- } ;
69- for ( let i = 0 ; i < requestGenerators . length ; i ++ ) {
70- if ( completedIndices . has ( i ) ) continue ;
7173
72- const request = requestObjects [ i ] ! ;
73- responseIndices [ i ] = isRequestGroup ( request ) ? request . map ( addRequest ) : addRequest ( request ) ;
74- }
74+ function addGenerator ( generator : AnyAPIRequestGenerator ) : void {
75+ if ( registeredGenerators . has ( generator ) ) throw new Error ( "Cannot execute the same generator instance more than once" ) ;
76+ registeredGenerators . add ( generator ) ;
77+ advanceGenerator ( generator ) ;
78+ }
7579
76- const responses = yield requests ;
77- for ( let i = 0 ; i < requestGenerators . length ; i ++ ) {
78- if ( completedIndices . has ( i ) ) continue ;
79-
80- const requestGenerator = requestGenerators [ i ] ;
81- const responseIndex = responseIndices [ i ] ! ;
82- let state : IteratorResult < APIRequest | readonly APIRequest [ ] , any > ;
83- if ( typeof responseIndex === "number" ) {
84- const result = responses [ responseIndex ] ;
85- state = result . error
86- ? requestGenerator . throw ( new Error ( result . error ) )
87- : requestGenerator . next ( result . result ) ;
80+ function * runRequestRounds ( ) : Generator < APIRequest [ ] | DeferredAPIRequest , void , readonly GeneratorResponse [ ] > {
81+ while ( requestsByGenerator . size ) {
82+ for ( const generator of registeredGenerators ) {
83+ let request = requestsByGenerator . get ( generator ) ;
84+ while ( request && isDeferredAPIRequest ( request ) ) {
85+ yield request ;
86+ advanceGenerator ( generator ) ;
87+ request = requestsByGenerator . get ( generator ) ;
88+ }
8889 }
89- else {
90- state = requestGenerator . next ( responseIndex . map ( index => responses [ index ] ) ) ;
90+ if ( ! requestsByGenerator . size ) break ;
91+
92+ const requests : APIRequest [ ] = [ ] ;
93+ const responseIndexByDeduplicationKey = new Map < string , number > ( ) ;
94+ const addRequest = ( request : APIRequest ) : number => {
95+ const deduplicationKey = getRequestDeduplicationKey ( request ) ;
96+ let responseIndex = deduplicationKey === undefined ? undefined : responseIndexByDeduplicationKey . get ( deduplicationKey ) ;
97+ if ( responseIndex === undefined ) {
98+ responseIndex = requests . length ;
99+ requests . push ( request ) ;
100+ if ( deduplicationKey !== undefined ) responseIndexByDeduplicationKey . set ( deduplicationKey , responseIndex ) ;
101+ }
102+ return responseIndex ;
103+ } ;
104+ // TODO: Use Iterator.prototype.filter when target >= ES2025
105+ const roundGenerators = [ ...registeredGenerators ] . filter ( generator => requestsByGenerator . has ( generator ) ) ;
106+ const responseIndices = new Map < AnyAPIRequestGenerator , number | readonly number [ ] > ( ) ;
107+ for ( const generator of roundGenerators ) {
108+ const request = requestsByGenerator . get ( generator ) as APIRequest | readonly APIRequest [ ] ;
109+ responseIndices . set ( generator , isRequestGroup ( request ) ? request . map ( addRequest ) : addRequest ( request ) ) ;
91110 }
92- if ( state . done ) {
93- results [ i ] = state . value ;
94- completedIndices . add ( i ) ;
95- requestObjects [ i ] = undefined ;
111+
112+ const responses = yield requests ;
113+ for ( const generator of roundGenerators ) {
114+ const responseIndex = responseIndices . get ( generator ) ! ;
115+ if ( typeof responseIndex === "number" ) {
116+ const result = responses [ responseIndex ] ;
117+ advanceGenerator ( generator , result . result , result . error || undefined ) ;
118+ }
119+ else {
120+ advanceGenerator ( generator , responseIndex . map ( index => responses [ index ] ) ) ;
121+ }
96122 }
97- else requestObjects [ i ] = state . value ;
98123 }
99124 }
100- return results as any [ ] as ExecutedGeneratorsResults < T > ;
125+
126+ function getResults ( ) : ExecutedGeneratorsResults < T > {
127+ return requestGenerators
128+ . filter ( generator => ! isDeferredGenerator ( generator ) )
129+ . map ( generator => resultsByGenerator . get ( generator ) ) as ExecutedGeneratorsResults < T > ;
130+ }
131+ }
132+
133+ export function all < const T extends readonly AnyAPIRequestGenerator [ ] > (
134+ ...requestGenerators : T
135+ ) : APIRequestGenerator < ExecutedGeneratorsResults < T > > ;
136+ export function * all < T extends readonly AnyAPIRequestGenerator [ ] > (
137+ ...requestGenerators : T
138+ ) : APIRequestGenerator < ExecutedGeneratorsResults < T > > {
139+ const { requestRounds, getResults } = createRequestRunner ( requestGenerators ) ;
140+ yield * requestRounds ;
141+ return getResults ( ) ;
142+ }
143+
144+ export function executeRequestGenerators < T extends readonly AnyAPIRequestGenerator [ ] > (
145+ requestGenerators : T ,
146+ executeRequests : ( requests : APIRequest [ ] ) => readonly GeneratorResponse [ ] ,
147+ ) : ExecutedGeneratorsResults < T > {
148+ const { requestRounds, getResults } = createRequestRunner ( requestGenerators , { executeDeferred : true } ) ;
149+ let state = requestRounds . next ( ) ;
150+ while ( ! state . done ) {
151+ if ( isDeferredAPIRequest ( state . value ) ) throw new Error ( "Unexpected deferred request" ) ;
152+ state = requestRounds . next ( executeRequests ( state . value ) ) ;
153+ }
154+ return getResults ( ) ;
155+ }
156+
157+ function isDeferredAPIRequest ( request : APIRequestGeneratorYield ) : request is DeferredAPIRequest {
158+ return ! Array . isArray ( request ) && "deferred" in request ;
159+ }
160+
161+ function isDeferredGenerator ( generator : AnyAPIRequestGenerator ) : generator is DeferredAPIRequestGenerator {
162+ return deferredGeneratorMarker in generator ;
101163}
102164
103165function isRequestGroup ( request : APIRequest | readonly APIRequest [ ] ) : request is readonly APIRequest [ ] {
@@ -112,3 +174,11 @@ function getRequestDeduplicationKey(request: APIRequest): string | undefined {
112174 return undefined ;
113175 }
114176}
177+
178+ export function defer ( gen : APIRequestGenerator ) : DeferredAPIRequestGenerator {
179+ const deferred = ( function * ( ) : Generator < DeferredAPIRequest , void , unknown > {
180+ yield { deferred : gen } ;
181+ } ) ( ) as DeferredAPIRequestGenerator ;
182+ Object . defineProperty ( deferred , deferredGeneratorMarker , { value : true } ) ;
183+ return deferred ;
184+ }
0 commit comments