@@ -35,12 +35,12 @@ module.exports = __toCommonJS(client_exports);
3535var import_crypto2 = __toESM ( require ( "crypto" ) , 1 ) ;
3636var import_fs2 = __toESM ( require ( "fs" ) , 1 ) ;
3737var import_url = require ( "url" ) ;
38- var import_avro_js2 = __toESM ( require ( "avro-js" ) , 1 ) ;
38+ var import_avro_js3 = __toESM ( require ( "avro-js" ) , 1 ) ;
3939var import_certifi = __toESM ( require ( "certifi" ) , 1 ) ;
4040var import_grpc_js = __toESM ( require ( "@grpc/grpc-js" ) , 1 ) ;
4141var import_proto_loader = __toESM ( require ( "@grpc/proto-loader" ) , 1 ) ;
4242
43- // src/eventParseError.js
43+ // src/utils/ eventParseError.js
4444var EventParseError = class extends Error {
4545 /**
4646 * The cause of the error.
@@ -87,12 +87,13 @@ var EventParseError = class extends Error {
8787 }
8888} ;
8989
90- // src/pubSubEventEmitter.js
90+ // src/utils/ pubSubEventEmitter.js
9191var import_events = require ( "events" ) ;
9292var PubSubEventEmitter = class extends import_events . EventEmitter {
9393 #topicName;
9494 #requestedEventCount;
9595 #receivedEventCount;
96+ #latestReplayId;
9697 /**
9798 * Create a new EventEmitter for Pub/Sub API events
9899 * @param {string } topicName
@@ -104,36 +105,85 @@ var PubSubEventEmitter = class extends import_events.EventEmitter {
104105 this . #topicName = topicName ;
105106 this . #requestedEventCount = requestedEventCount ;
106107 this . #receivedEventCount = 0 ;
108+ this . #latestReplayId = null ;
107109 }
108110 emit ( eventName , args ) {
109111 if ( eventName === "data" ) {
110112 this . #receivedEventCount++ ;
113+ this . #latestReplayId = args . replayId ;
111114 }
112115 return super . emit ( eventName , args ) ;
113116 }
114117 /**
115- * Returns the number of events that were requested during the subscription
118+ * Returns the number of events that were requested when subscribing.
116119 * @returns {number } the number of events that were requested
117120 */
118121 getRequestedEventCount ( ) {
119122 return this . #requestedEventCount;
120123 }
121124 /**
122- * Returns the number of events that were received since the subscription
125+ * Returns the number of events that were received since subscribing.
123126 * @returns {number } the number of events that were received
124127 */
125128 getReceivedEventCount ( ) {
126129 return this . #receivedEventCount;
127130 }
128131 /**
129- * Returns the topic name for this subscription
132+ * Returns the topic name for this subscription.
130133 * @returns {string } the topic name
131134 */
132135 getTopicName ( ) {
133136 return this . #topicName;
134137 }
138+ /**
139+ * Returns the replay ID of the last processed event or null if no event was processed yet.
140+ * @return {number } replay ID
141+ */
142+ getLatestReplayId ( ) {
143+ return this . #latestReplayId;
144+ }
145+ /**
146+ * @protected
147+ * Resets the requested/received event counts.
148+ * This method should only be be used internally by the client when it resubscribes.
149+ * @param {number } newRequestedEventCount
150+ */
151+ _resetEventCount ( newRequestedEventCount ) {
152+ this . #requestedEventCount = newRequestedEventCount ;
153+ this . #receivedEventCount = 0 ;
154+ }
135155} ;
136156
157+ // src/utils/avroHelper.js
158+ var import_avro_js = __toESM ( require ( "avro-js" ) , 1 ) ;
159+ var CustomLongAvroType = import_avro_js . default . types . LongType . using ( {
160+ fromBuffer : ( buf ) => {
161+ const big = buf . readBigInt64LE ( ) ;
162+ if ( big < Number . MIN_SAFE_INTEGER || big > Number . MAX_SAFE_INTEGER ) {
163+ return big ;
164+ }
165+ return Number ( BigInt . asIntN ( 64 , big ) ) ;
166+ } ,
167+ toBuffer : ( n ) => {
168+ const buf = Buffer . allocUnsafe ( 8 ) ;
169+ if ( n instanceof BigInt ) {
170+ buf . writeBigInt64LE ( n ) ;
171+ } else {
172+ buf . writeBigInt64LE ( BigInt ( n ) ) ;
173+ }
174+ return buf ;
175+ } ,
176+ fromJSON : BigInt ,
177+ toJSON : Number ,
178+ isValid : ( n ) => {
179+ const type = typeof n ;
180+ return type === "number" && n % 1 === 0 || type === "bigint" ;
181+ } ,
182+ compare : ( n1 , n2 ) => {
183+ return n1 === n2 ? 0 : n1 < n2 ? - 1 : 1 ;
184+ }
185+ } ) ;
186+
137187// src/utils/configuration.js
138188var dotenv = __toESM ( require ( "dotenv" ) , 1 ) ;
139189var import_fs = __toESM ( require ( "fs" ) , 1 ) ;
@@ -230,7 +280,7 @@ var Configuration = class _Configuration {
230280} ;
231281
232282// src/utils/eventParser.js
233- var import_avro_js = __toESM ( require ( "avro-js" ) , 1 ) ;
283+ var import_avro_js2 = __toESM ( require ( "avro-js" ) , 1 ) ;
234284function parseEvent ( schema , event ) {
235285 const allFields = schema . type . getFields ( ) ;
236286 const replayId = decodeReplayId ( event . replayId ) ;
@@ -313,7 +363,7 @@ function getChildFields(parentField) {
313363 const types = parentField . _type . getTypes ( ) ;
314364 let fields = [ ] ;
315365 types . forEach ( ( type ) => {
316- if ( type instanceof import_avro_js . default . types . RecordType ) {
366+ if ( type instanceof import_avro_js2 . default . types . RecordType ) {
317367 fields = fields . concat ( type . getFields ( ) ) ;
318368 }
319369 } ) ;
@@ -477,33 +527,7 @@ function base64url(input) {
477527}
478528
479529// src/client.js
480- var CUSTOM_LONG_AVRO_TYPE = import_avro_js2 . default . types . LongType . using ( {
481- fromBuffer : ( buf ) => {
482- const big = buf . readBigInt64LE ( ) ;
483- if ( big < Number . MIN_SAFE_INTEGER || big > Number . MAX_SAFE_INTEGER ) {
484- return big ;
485- }
486- return Number ( BigInt . asIntN ( 64 , big ) ) ;
487- } ,
488- toBuffer : ( n ) => {
489- const buf = Buffer . allocUnsafe ( 8 ) ;
490- if ( n instanceof BigInt ) {
491- buf . writeBigInt64LE ( n ) ;
492- } else {
493- buf . writeBigInt64LE ( BigInt ( n ) ) ;
494- }
495- return buf ;
496- } ,
497- fromJSON : BigInt ,
498- toJSON : Number ,
499- isValid : ( n ) => {
500- const type = typeof n ;
501- return type === "number" && n % 1 === 0 || type === "bigint" ;
502- } ,
503- compare : ( n1 , n2 ) => {
504- return n1 === n2 ? 0 : n1 < n2 ? - 1 : 1 ;
505- }
506- } ) ;
530+ var MAX_EVENT_BATCH_SIZE = 100 ;
507531var PubSubApiClient = class {
508532 /**
509533 * gRPC client
@@ -515,14 +539,20 @@ var PubSubApiClient = class {
515539 * @type {Map<string,Schema> }
516540 */
517541 #schemaChache;
542+ /**
543+ * Map of subscribitions indexed by topic name
544+ * @type {Map<string,Object> }
545+ */
546+ #subscriptions;
518547 #logger;
519548 /**
520549 * Builds a new Pub/Sub API client
521- * @param {Logger } logger an optional custom logger. The client uses the console if no value is supplied.
550+ * @param {Logger } [ logger] an optional custom logger. The client uses the console if no value is supplied.
522551 */
523552 constructor ( logger = console ) {
524553 this . #logger = logger ;
525554 this . #schemaChache = /* @__PURE__ */ new Map ( ) ;
555+ this . #subscriptions = /* @__PURE__ */ new Map ( ) ;
526556 try {
527557 Configuration . load ( ) ;
528558 } catch ( error ) {
@@ -636,21 +666,21 @@ var PubSubApiClient = class {
636666 /**
637667 * Subscribes to a topic and retrieves all past events in retention window.
638668 * @param {string } topicName name of the topic that we're subscribing to
639- * @param {number } numRequested number of events requested
669+ * @param {number } [ numRequested] optional number of events requested. If not supplied or null, the client keeps the subscription alive forever.
640670 * @returns {Promise<EventEmitter> } Promise that holds an emitter that allows you to listen to received events and stream lifecycle events
641671 * @memberof PubSubApiClient.prototype
642672 */
643- async subscribeFromEarliestEvent ( topicName , numRequested ) {
673+ async subscribeFromEarliestEvent ( topicName , numRequested = null ) {
644674 return this . #subscribe( {
645675 topicName,
646676 numRequested,
647677 replayPreset : 1
648678 } ) ;
649679 }
650680 /**
651- * Subscribes to a topic and retrieve past events starting from a replay ID
681+ * Subscribes to a topic and retrieves past events starting from a replay ID.
652682 * @param {string } topicName name of the topic that we're subscribing to
653- * @param {number } numRequested number of events requested
683+ * @param {number } numRequested number of events requested. If null, the client keeps the subscription alive forever.
654684 * @param {number } replayId replay ID
655685 * @returns {Promise<EventEmitter> } Promise that holds an emitter that allows you to listen to received events and stream lifecycle events
656686 * @memberof PubSubApiClient.prototype
@@ -664,13 +694,13 @@ var PubSubApiClient = class {
664694 } ) ;
665695 }
666696 /**
667- * Subscribes to a topic
697+ * Subscribes to a topic.
668698 * @param {string } topicName name of the topic that we're subscribing to
669- * @param {number } numRequested number of events requested
699+ * @param {number } [ numRequested] optional number of events requested. If not supplied or null, the client keeps the subscription alive forever.
670700 * @returns {Promise<EventEmitter> } Promise that holds an emitter that allows you to listen to received events and stream lifecycle events
671701 * @memberof PubSubApiClient.prototype
672702 */
673- async subscribe ( topicName , numRequested ) {
703+ async subscribe ( topicName , numRequested = null ) {
674704 return this . #subscribe( {
675705 topicName,
676706 numRequested
@@ -682,28 +712,44 @@ var PubSubApiClient = class {
682712 * @return {PubSubEventEmitter } emitter that allows you to listen to received events and stream lifecycle events
683713 */
684714 async #subscribe( subscribeRequest ) {
715+ let { topicName, numRequested } = subscribeRequest ;
685716 try {
686- if ( typeof subscribeRequest . numRequested !== "number" ) {
687- throw new Error (
688- `Expected a number type for number of requested events but got ${ typeof subscribeRequest . numRequested } `
689- ) ;
690- }
691- if ( ! Number . isSafeInteger ( subscribeRequest . numRequested ) || subscribeRequest . numRequested < 1 ) {
692- throw new Error (
693- `Expected an integer greater than 1 for number of requested events but got ${ subscribeRequest . numRequested } `
694- ) ;
717+ let isInfiniteEventRequest = false ;
718+ if ( numRequested === null ) {
719+ isInfiniteEventRequest = true ;
720+ subscribeRequest . numRequested = numRequested = MAX_EVENT_BATCH_SIZE ;
721+ } else {
722+ if ( typeof numRequested !== "number" ) {
723+ throw new Error (
724+ `Expected a number type for number of requested events but got ${ typeof numRequested } `
725+ ) ;
726+ }
727+ if ( ! Number . isSafeInteger ( numRequested ) || numRequested < 1 ) {
728+ throw new Error (
729+ `Expected an integer greater than 1 for number of requested events but got ${ numRequested } `
730+ ) ;
731+ }
732+ if ( numRequested > MAX_EVENT_BATCH_SIZE ) {
733+ this . #logger. warn (
734+ `The number of requested events for ${ topicName } exceeds max event batch size (${ MAX_EVENT_BATCH_SIZE } ).`
735+ ) ;
736+ }
695737 }
696738 if ( ! this . #client) {
697739 throw new Error ( "Pub/Sub API client is not connected." ) ;
698740 }
699- const subscription = this . #client. Subscribe ( ) ;
741+ let subscription = this . #subscriptions. get ( topicName ) ;
742+ if ( ! subscription ) {
743+ subscription = this . #client. Subscribe ( ) ;
744+ this . #subscriptions. set ( topicName , subscription ) ;
745+ }
700746 subscription . write ( subscribeRequest ) ;
701747 this . #logger. info (
702- `Subscribe request sent for ${ subscribeRequest . numRequested } events from ${ subscribeRequest . topicName } ...`
748+ `Subscribe request sent for ${ numRequested } events from ${ topicName } ...`
703749 ) ;
704750 const eventEmitter = new PubSubEventEmitter (
705- subscribeRequest . topicName ,
706- subscribeRequest . numRequested
751+ topicName ,
752+ numRequested
707753 ) ;
708754 subscription . on ( "data" , ( data ) => {
709755 const latestReplayId = decodeReplayId ( data . latestReplayId ) ;
@@ -713,19 +759,13 @@ var PubSubApiClient = class {
713759 ) ;
714760 data . events . forEach ( async ( event ) => {
715761 try {
716- let schema = await this . #getEventSchema(
717- subscribeRequest . topicName
718- ) ;
719- if ( schema . id !== event . schemaId ) {
762+ let schema = await this . #getEventSchema( topicName ) ;
763+ if ( schema . id !== event . event . schemaId ) {
720764 this . #logger. info (
721- `Event schema changed, reloading: ${ subscribeRequest . topicName } `
722- ) ;
723- this . #clearEventSchemaFromCache(
724- subscribeRequest . topicName
725- ) ;
726- schema = await this . #getEventSchema(
727- subscribeRequest . topicName
765+ `Event schema changed (${ schema . id } != ${ event . event . schemaId } ), reloading: ${ topicName } `
728766 ) ;
767+ this . #schemaChache. delete ( topicName ) ;
768+ schema = await this . #getEventSchema( topicName ) ;
729769 }
730770 const parsedEvent = parseEvent ( schema , event ) ;
731771 this . #logger. debug ( parsedEvent ) ;
@@ -748,7 +788,14 @@ var PubSubApiClient = class {
748788 this . #logger. error ( parseError ) ;
749789 }
750790 if ( eventEmitter . getReceivedEventCount ( ) === eventEmitter . getRequestedEventCount ( ) ) {
751- eventEmitter . emit ( "lastevent" ) ;
791+ if ( isInfiniteEventRequest ) {
792+ this . requestAdditionalEvents (
793+ eventEmitter ,
794+ MAX_EVENT_BATCH_SIZE
795+ ) ;
796+ } else {
797+ eventEmitter . emit ( "lastevent" ) ;
798+ }
752799 }
753800 } ) ;
754801 } else {
@@ -760,6 +807,7 @@ var PubSubApiClient = class {
760807 }
761808 } ) ;
762809 subscription . on ( "end" , ( ) => {
810+ this . #subscriptions. delete ( topicName ) ;
763811 this . #logger. info ( "gRPC stream ended" ) ;
764812 eventEmitter . emit ( "end" ) ;
765813 } ) ;
@@ -778,11 +826,33 @@ var PubSubApiClient = class {
778826 return eventEmitter ;
779827 } catch ( error ) {
780828 throw new Error (
781- `Failed to subscribe to events for topic ${ subscribeRequest . topicName } ` ,
829+ `Failed to subscribe to events for topic ${ topicName } ` ,
782830 { cause : error }
783831 ) ;
784832 }
785833 }
834+ /**
835+ * Request additional events on an existing subscription.
836+ * @param {PubSubEventEmitter } eventEmitter event emitter that was obtained in the first subscribe call
837+ * @param {number } numRequested number of events requested.
838+ */
839+ async requestAdditionalEvents ( eventEmitter , numRequested ) {
840+ const topicName = eventEmitter . getTopicName ( ) ;
841+ const subscription = this . #subscriptions. get ( topicName ) ;
842+ if ( ! subscription ) {
843+ throw new Error (
844+ `Failed to request additional events for topic ${ topicName } , no active subscription found.`
845+ ) ;
846+ }
847+ eventEmitter . _resetEventCount ( numRequested ) ;
848+ subscription . write ( {
849+ topicName,
850+ numRequested
851+ } ) ;
852+ this . #logger. debug (
853+ `Resubscribing to a batch of ${ numRequested } events for: ${ topicName } `
854+ ) ;
855+ }
786856 /**
787857 * Publishes a payload to a topic using the gRPC client.
788858 * @param {string } topicName name of the topic that we're subscribing to
@@ -874,8 +944,8 @@ var PubSubApiClient = class {
874944 if ( schemaError ) {
875945 reject ( schemaError ) ;
876946 } else {
877- const schemaType = import_avro_js2 . default . parse ( res . schemaJson , {
878- registry : { long : CUSTOM_LONG_AVRO_TYPE }
947+ const schemaType = import_avro_js3 . default . parse ( res . schemaJson , {
948+ registry : { long : CustomLongAvroType }
879949 } ) ;
880950 this . #logger. info (
881951 `Topic schema loaded: ${ topicName } `
@@ -890,7 +960,4 @@ var PubSubApiClient = class {
890960 } ) ;
891961 } ) ;
892962 }
893- #clearEventSchemaFromCache( topicName ) {
894- this . #schemaChache. delete ( topicName ) ;
895- }
896963} ;
0 commit comments