@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
1 lines • 29.9 kB
Source Map (JSON)
{"version":3,"sources":["../../../packages/core/data/query-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,UAAU,EAAsD,MAAM,MAAM,CAAC;AAUpG;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,OAAO;IACtC;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,oBAAY,iBAAiB,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,KAAK,UAAU,CAAC,KAAK,CAAC,CAAC;AAEtF;;GAEG;AACH,oBAAY,yBAAyB,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;AAE5E;;GAEG;AACH,oBAAY,iBAAiB,GAAG,MAAM,IAAI,CAAC;AA6D3C;;;;;;;;;;GAUG;AACH,qBAAa,UAAU,CAAC,KAAK,EAAE,OAAO;IA2E9B,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,eAAe,CAAC;IACxB,OAAO,CAAC,OAAO,CAAC;IA5EpB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,EAAE,CAAS;IAE1B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,SAAS,CAAqB;IAE7C;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc,CAAM;IAEnC;;OAEG;IACH,OAAO,CAAC,UAAU,CAAkC;IAEpD;;OAEG;IACH,OAAO,CAAC,OAAO,CAA6B;IAE5C;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAkB;IAElC;;OAEG;IACH,OAAO,CAAC,GAAG,CAAS;IAEpB;;OAEG;IACH,OAAO,CAAC,UAAU,CAAM;IAExB;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAA6B;IAErD;;OAEG;IACH,OAAO,CAAC,eAAe,CAAkB;IAEzC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAqE;IAE/E;;OAEG;IACH,OAAO,CAAC,IAAI,CAAS;IAErB;;OAEG;IACH,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;;;OAMG;gBAES,MAAM,EAAE,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,EACzC,eAAe,CAAC,EAAE,yBAAyB,CAAC,OAAO,CAAC,EACpD,OAAO,CAAC,EAAE,iBAAiB;IAGvC;;;;OAIG;WACW,iBAAiB,IAAI,IAAI;IAIvC;;;;;OAKG;IACI,gBAAgB,CAAC,gBAAgB,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;IA2BzF;;;OAGG;IACI,UAAU,IAAI,IAAI;IAezB;;;;;;;;OAQG;IACI,KAAK,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,IAAI;IAwBvD;;OAEG;IACI,OAAO,IAAI,IAAI;IAgBtB;;;;;;;OAOG;IACI,OAAO,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;IAmDxC;;;;OAIG;IACI,KAAK,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI;IAM/B;;;;OAIG;IACI,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMhC,OAAO,CAAC,GAAG;IAKX,OAAO,CAAC,uBAAuB;IA8E/B,OAAO,CAAC,mBAAmB;IA4D3B,OAAO,CAAC,OAAO;IAyCf;;;;OAIG;IACH,OAAO,CAAC,MAAM;IA4Cd,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,cAAc;IAkDtB,OAAO,CAAC,aAAa;CAsBxB","file":"query-cache.d.ts","sourcesContent":["import { EMPTY, merge, Observable, Observer, of, ReplaySubject, Subject, Subscription } from 'rxjs';\r\nimport { delay, expand, multicast, refCount, switchMap } from 'rxjs/operators';\r\nimport { Logging } from '../diagnostics/logging';\r\nimport { Strings } from '../generated/strings';\r\n\r\n// test logging hook.\r\n// function testLog(message: string): void {\r\n// (<any>window).testLog(message);\r\n// }\r\n\r\n/**\r\n * Query cache options\r\n */\r\nexport interface QueryCacheOptions<TParams> {\r\n /**\r\n * parameters passed to create observable.\r\n */\r\n params?: TParams;\r\n\r\n /**\r\n * interval with milliseconds to make auto background query.\r\n */\r\n interval?: number;\r\n\r\n /**\r\n * enable recovery capability.\r\n * caller should clean up environment by calling clearCache() when finished to use the instance of query cache.\r\n */\r\n enableRecovery?: boolean;\r\n\r\n /**\r\n * enable delaying clean up of observable after all subscriptions were unsubscribed.\r\n */\r\n delayClean?: boolean;\r\n}\r\n\r\n/**\r\n * Function to create a new observable with specified parameters.\r\n */\r\nexport type QueryCacheCreator<TData, TParams> = (param: TParams) => Observable<TData>;\r\n\r\n/**\r\n * Function to generate serialized string from specified parameters.\r\n */\r\nexport type QueryCacheSerializeParams<TParams> = (param: TParams) => string;\r\n\r\n/**\r\n * Function to destroy any remained condition to clear up.\r\n */\r\nexport type QueryCacheDestroy = () => void;\r\n\r\n/**\r\n * Query cache context for a subscription.\r\n */\r\ninterface QueryCacheContext<TData> {\r\n subscription: Subscription;\r\n next: (value: TData) => void;\r\n error: (error: any) => void;\r\n complete: () => void;\r\n unsubscribe: () => void;\r\n errorCount: number;\r\n unsubscribeCount: number;\r\n}\r\n\r\n/**\r\n * Query cache data interface.\r\n */\r\ninterface QueryCachedData<TData, TParams> {\r\n fetch: Subject<QueryCacheOptions<TParams>>;\r\n refresh: Subject<number>;\r\n apply: Subject<TData>;\r\n publish: Observable<TData>;\r\n subscribers: QueryCacheContext<TData>[];\r\n}\r\n\r\n/**\r\n * Cycle repeater context interface.\r\n */\r\ninterface RepeaterContext {\r\n /**\r\n * Current cycle counter value.\r\n */\r\n counter: number;\r\n\r\n /**\r\n * Requested cycle counter to trigger.\r\n */\r\n request: number;\r\n\r\n /**\r\n * Cycle interval millisecond.\r\n */\r\n pulse: number;\r\n\r\n /**\r\n * One cycle number.\r\n */\r\n cycle: number;\r\n\r\n /**\r\n * Validation counter.\r\n */\r\n verify: number;\r\n\r\n /**\r\n * setInterval timer object.\r\n */\r\n timer: any;\r\n}\r\n\r\n/**\r\n * Query Cache class.\r\n * - Create a cache entry by \"create\" call with creator object.\r\n * - Subscribe with options to get query result.\r\n * - Refresh data on-demand and interval.\r\n * - Dispose the resource when it's done.\r\n * - Recover and re-subscribe with the same handlers if necessary after an error.\r\n *\r\n * TData the data type of observable responds.\r\n * TParams the options parameters to pass the creator to create new observable.\r\n */\r\nexport class QueryCache<TData, TParams> {\r\n /**\r\n * Internal instance id counter.\r\n * (set id number to be 0 to bring back older behavior)\r\n */\r\n private static id = 10000;\r\n\r\n /**\r\n * Delay clean up time (10 seconds)\r\n */\r\n private static delayTime: number = 10 * 1000;\r\n\r\n /**\r\n * Repeater is parked state.\r\n */\r\n private static repeaterParked = -1;\r\n\r\n /**\r\n * Cached data collection with observable ReplaySubject.\r\n */\r\n private cachedData: QueryCachedData<TData, TParams>;\r\n\r\n /**\r\n * Current options, passed from the fetch call.\r\n */\r\n private options: QueryCacheOptions<TParams>;\r\n\r\n /**\r\n * Current observer of create observable.\r\n */\r\n private observer: Observer<TData>;\r\n\r\n /**\r\n * Key string serialized by TParams.\r\n */\r\n private key: string;\r\n\r\n /**\r\n * Delay clean timer object.\r\n */\r\n private delayTimer: any;\r\n\r\n /**\r\n * Auto fetch options.\r\n */\r\n private autoFetchOptions: QueryCacheOptions<TParams>;\r\n\r\n /**\r\n * Repeater context to cycle repeating observable.\r\n */\r\n private repeaterContext: RepeaterContext;\r\n\r\n /**\r\n * Instance id of query cache for internal debugging.\r\n */\r\n private id = '{0}-{1}'.format(MsftSme.self().Init.moduleName, ++QueryCache.id);\r\n\r\n /**\r\n * The tracing name.\r\n */\r\n private name: string;\r\n\r\n /**\r\n * The start time of tracing.\r\n */\r\n private traceTime: number;\r\n\r\n /**\r\n * Initializes a new instance of the QueryCache.\r\n *\r\n * @param create the function to create a new observable with specified parameters.\r\n * @param serializeParams the function to generate serialized string from specified parameters. (optional)\r\n * @param destroy the function to clean up any residue after all reference was gone. (optional)\r\n */\r\n constructor(\r\n private create: QueryCacheCreator<TData, TParams>,\r\n private serializeParams?: QueryCacheSerializeParams<TParams>,\r\n private destroy?: QueryCacheDestroy) {\r\n }\r\n\r\n /**\r\n * Use the QueryCache inside of Worker or background tab view.\r\n * setInterval time lost accuracy within background. Use setTimeout recursive query by expand observable operator.\r\n * Call the static function once before creating new instance of QueryCache, probably set at app.component.ts file.\r\n */\r\n public static useExpandInterval(): void {\r\n QueryCache.id = 0;\r\n }\r\n\r\n /**\r\n * Create or return the cached observable.\r\n *\r\n * @param autoFetchOptions the options parameter auto-fetch when subscribe is called.\r\n * @return Observable<TData> the observable object.\r\n */\r\n public createObservable(autoFetchOptions?: QueryCacheOptions<TParams>): Observable<TData> {\r\n // for recovery scenario, this.cachedData.subscribers is retained.\r\n // clear them here if no publish ever made.\r\n if (this.cachedData && this.cachedData.publish) {\r\n if (autoFetchOptions) {\r\n // schedule the fetch immediately after current call stack.\r\n setTimeout(() => {\r\n // cancel auto fetch if already unsubscribed.\r\n if (this.cachedData && this.cachedData.publish) {\r\n this.fetch(autoFetchOptions);\r\n }\r\n });\r\n }\r\n\r\n return this.cachedData.publish;\r\n }\r\n\r\n const fetch = new Subject<QueryCacheOptions<TParams>>();\r\n const refresh = new Subject<number>();\r\n const apply = new Subject<TData>();\r\n const subscribers = [];\r\n const publish: Observable<TData> = this.createPublishObservable(fetch, refresh, apply, subscribers);\r\n this.cachedData = <QueryCachedData<TData, TParams>>{ fetch, refresh, publish, apply, subscribers };\r\n this.autoFetchOptions = autoFetchOptions;\r\n return publish;\r\n }\r\n\r\n /**\r\n * Unsubscribe any remained subscribers and dispose all remained resources.\r\n * - clearCache() is not necessary to be called if all subscriptions are unsubscribe'ed properly.\r\n */\r\n public clearCache(): void {\r\n if (!this.cachedData) {\r\n return;\r\n }\r\n\r\n if (this.cachedData.subscribers) {\r\n const tempSubscribers = this.cachedData.subscribers.slice(0);\r\n for (const context of tempSubscribers) {\r\n context.subscription.unsubscribe();\r\n }\r\n }\r\n\r\n this.cleanup(false);\r\n }\r\n\r\n /**\r\n * Fetch with new query options.\r\n * - The fetch starts new query when cache is empty, current cache doesn't match the key generated by\r\n * serializedParams function which was configured at the constructor, interval was changed (ex.\r\n * changing zero interval to active interval or other way around), or the first subscriber like when\r\n * delayClean comes back active subscription.\r\n *\r\n * @param options the options with interval, delay clean, recovery and parameters.\r\n */\r\n public fetch(options: QueryCacheOptions<TParams>): void {\r\n if (!this.cachedData) {\r\n const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.QueryCacheFetchOrder.message;\r\n Logging.logError('QueryCache', message);\r\n throw new Error(message);\r\n }\r\n\r\n if (this.cachedData.fetch) {\r\n // send new request when cache doesn't match the key, interval was changed, or single subscriber.\r\n const key = this.serializeParams ? this.serializeParams(options.params) : '';\r\n if (!this.options\r\n || this.key !== key\r\n || this.options.interval !== options.interval\r\n || this.key == null) {\r\n this.key = key;\r\n this.cachedData.fetch.next(options);\r\n }\r\n } else {\r\n Logging.logWarning(\r\n 'QueryCache',\r\n MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.QueryCacheFetchErrorOnce.message);\r\n }\r\n }\r\n\r\n /**\r\n * Refresh the query cache with last options and parameters provided on the fetch call.\r\n */\r\n public refresh(): void {\r\n if (!this.cachedData) {\r\n const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.QueryCacheRefreshOrder.message;\r\n Logging.logError('QueryCache', message);\r\n throw new Error(message);\r\n }\r\n\r\n if (this.cachedData.refresh) {\r\n this.cachedData.refresh.next(undefined);\r\n } else {\r\n Logging.logWarning(\r\n 'QueryCache',\r\n MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.QueryCacheRefreshErrorOnce.message);\r\n }\r\n }\r\n\r\n /**\r\n * Recover the observable and subscription.\r\n * - Recover can be used to resubscribe when the observable got any error situation. The observable would\r\n * be unsubscribed state when it got an error response, this function allows to resubscribe without\r\n * recreate observable and subscription.\r\n *\r\n * @param autoFetch if true auto fetch after re-subscribed.\r\n */\r\n public recover(autoFetch: boolean): void {\r\n if (!this.cachedData) {\r\n const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.QueryCacheRecoverNoCachedResource.message;\r\n Logging.logError('QueryCache', message);\r\n throw new Error(message);\r\n }\r\n\r\n if (!this.options || !this.options.enableRecovery) {\r\n const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.QueryCacheRecoverMissingRecoveryOption.message;\r\n Logging.logError('QueryCache', message);\r\n throw new Error(message);\r\n }\r\n\r\n const tempCachedData = this.cachedData;\r\n if (tempCachedData.refresh) {\r\n tempCachedData.refresh.complete();\r\n }\r\n\r\n if (tempCachedData.fetch) {\r\n tempCachedData.fetch.complete();\r\n }\r\n\r\n if (tempCachedData.apply) {\r\n tempCachedData.apply.complete();\r\n }\r\n\r\n for (const context of this.cachedData.subscribers) {\r\n // call original unsubscribe function.\r\n context.subscription.unsubscribe();\r\n }\r\n\r\n // recreate new publish observable and subscribe to original set of handlers.\r\n const tempSubscribers = this.cachedData.subscribers.slice(0);\r\n\r\n const fetch = new Subject<QueryCacheOptions<TParams>>();\r\n const refresh = new Subject<number>();\r\n const apply = new Subject<TData>();\r\n const subscribers = [];\r\n const publish: Observable<TData> = this.createPublishObservable(fetch, refresh, apply, subscribers);\r\n this.cachedData = <QueryCachedData<TData, TParams>>{ fetch, refresh, publish, apply, subscribers };\r\n for (const context of tempSubscribers) {\r\n (<any>this.cachedData.publish).subscribe(context.next, context.error, context.complete, context.subscription);\r\n }\r\n\r\n if (autoFetch) {\r\n fetch.next(this.options);\r\n } else {\r\n this.options = {};\r\n }\r\n }\r\n\r\n /**\r\n * Apply instant data to the query cache. The data will be delivered to the subscriber immediately.\r\n *\r\n * @param data the data to apply to the replay.\r\n */\r\n public apply(data: TData): void {\r\n if (this.cachedData.apply) {\r\n this.cachedData.apply.next(data);\r\n }\r\n }\r\n\r\n /**\r\n * Enable tracing of this query cache instance.\r\n *\r\n * @param name the name of query cache.\r\n */\r\n public trace(name: string): void {\r\n this.name = name;\r\n this.traceTime = Date.now();\r\n this.log(`tracing ${this.name}: ${this.id}`);\r\n }\r\n\r\n private log(message: string): void {\r\n const time = ((Date.now() - this.traceTime)) / 1000;\r\n Logging.debug(`QC: ${this.id} ${this.name}: ${time}sec: ${message}`);\r\n }\r\n\r\n private createPublishObservable(\r\n fetch: Subject<QueryCacheOptions<TParams>>,\r\n refresh: Subject<number>,\r\n apply: Subject<TData>,\r\n subscribers: QueryCacheContext<TData>[]): Observable<TData> {\r\n const publish: Observable<TData> =\r\n // start data query when new fetch is requested.\r\n fetch\r\n .pipe(\r\n // switch map unsubscribe previous fetch observable tree, and re-create new one.\r\n switchMap(options => {\r\n // remember last options.\r\n this.options = options || {};\r\n // merge output from expand-delay object and refresh object.\r\n return merge<[TData, TData, TData]>(\r\n // submit initial query.\r\n this.repeat(),\r\n // refresh to trigger new observable.\r\n refresh.pipe(switchMap(() => this.create(this.options.params))),\r\n // apply data.\r\n apply\r\n );\r\n }),\r\n // multicast the result so multiple subscribers can share.\r\n // and Replay last result if later subscriber looks for the result.\r\n multicast(new ReplaySubject(1)),\r\n // keep the reference count so publish observable active.\r\n refCount());\r\n\r\n // override subscribe call to keep track subscription.\r\n publish.subscribe = <any>((next, error, complete, recoverSubscription) => {\r\n if (this.delayTimer) {\r\n clearTimeout(this.delayTimer);\r\n this.delayTimer = null;\r\n if (this.options && this.options.interval) {\r\n this.options.interval = null;\r\n }\r\n }\r\n\r\n // override default handler with No-OP call if not set.\r\n // this allows all subscribe call to be get called when an error reported.\r\n next = next || MsftSme.noop;\r\n error = error || MsftSme.noop;\r\n\r\n const context = <QueryCacheContext<TData>>{ next, error, complete, errorCount: 0, unsubscribeCount: 0 };\r\n const hookError = (errorData) => {\r\n context.errorCount++;\r\n error(errorData);\r\n };\r\n context.subscription = Object.getPrototypeOf(publish).subscribe.call(publish, next, hookError, complete);\r\n\r\n // hook up old subscription so old subscriber can unsubscribe properly.\r\n if (recoverSubscription) {\r\n recoverSubscription.unsubscribe = () => context.subscription.unsubscribe();\r\n }\r\n\r\n // add subscribers to the inventory before adding to the un-subscription list. add() could call unsubscribe immediately.\r\n subscribers.push(context);\r\n\r\n // add internal unsubscribe to the original subscription.\r\n context.subscription.add(this.internalUnsubscribe.bind(this, context));\r\n\r\n // if createObservable() is called with autoFetchOptions, it start fetching data immediately when subscribe() is called.\r\n if (this.autoFetchOptions) {\r\n this.fetch(this.autoFetchOptions);\r\n this.autoFetchOptions = null;\r\n }\r\n\r\n if (this.name) {\r\n this.log(`subscribe count=${subscribers.length}`);\r\n }\r\n\r\n return context.subscription;\r\n });\r\n\r\n return publish;\r\n }\r\n\r\n private internalUnsubscribe(context: QueryCacheContext<TData>): void {\r\n context.unsubscribeCount++;\r\n if (context.unsubscribeCount > 1) {\r\n // ignore if it's called twice and more.\r\n return;\r\n }\r\n\r\n if (!this.cachedData) {\r\n return;\r\n }\r\n\r\n const options = this.options || {};\r\n if (options.enableRecovery) {\r\n // indicating normal unsubscribe call, so delete it.\r\n if (context.errorCount === 0) {\r\n const contextIndex = this.cachedData.subscribers.indexOf(context);\r\n if (contextIndex >= 0) {\r\n this.cachedData.subscribers.splice(contextIndex, 1);\r\n }\r\n }\r\n\r\n // on the recovery mode, retains all subscriber context, and don't clean up.\r\n // if there no active subscription, make cleanup call.\r\n const findAny = this.cachedData.subscribers.find(item => !item.subscription.closed);\r\n if (!findAny) {\r\n if (options.delayClean) {\r\n this.delayTimer = setTimeout(\r\n () => {\r\n this.cleanup(true);\r\n this.delayTimer = null;\r\n },\r\n QueryCache.delayTime);\r\n } else {\r\n this.cleanup(true);\r\n }\r\n }\r\n\r\n return;\r\n }\r\n\r\n // non recovery mode.\r\n const index = this.cachedData.subscribers.indexOf(context);\r\n if (index >= 0) {\r\n this.cachedData.subscribers.splice(index, 1);\r\n }\r\n\r\n if (this.cachedData.subscribers.length === 0) {\r\n if (options.delayClean) {\r\n this.delayTimer = setTimeout(\r\n () => {\r\n this.cleanup(false);\r\n this.delayTimer = null;\r\n },\r\n QueryCache.delayTime);\r\n } else {\r\n this.cleanup(false);\r\n }\r\n }\r\n }\r\n\r\n private cleanup(recovery: boolean): void {\r\n if (this.name) {\r\n this.log('unsubscribed');\r\n }\r\n\r\n this.repeaterStop();\r\n\r\n if (!this.cachedData) {\r\n return;\r\n }\r\n\r\n if (this.cachedData.fetch) {\r\n this.cachedData.fetch.complete();\r\n this.cachedData.fetch = null;\r\n }\r\n\r\n if (this.cachedData.refresh) {\r\n this.cachedData.refresh.complete();\r\n this.cachedData.refresh = null;\r\n }\r\n\r\n if (this.cachedData.apply) {\r\n this.cachedData.apply.complete();\r\n this.cachedData.apply = null;\r\n }\r\n\r\n this.cachedData.publish = null;\r\n this.key = null;\r\n\r\n // on recovery cleanup, retain subscribers and options.\r\n if (!recovery) {\r\n this.cachedData.subscribers = null;\r\n this.cachedData = null;\r\n this.options = {};\r\n }\r\n\r\n if (this.destroy) {\r\n this.destroy();\r\n }\r\n }\r\n\r\n /**\r\n * Repeat observable.\r\n * Using free running interval to avoid setTimeout recursive or Observable.expand recursive stacks.\r\n * It reduces the usage of browser memory but use more frequent timer event as pulse in the code.\r\n */\r\n private repeat(): Observable<TData> {\r\n if (QueryCache.id < 10000) {\r\n // Legacy code pattern which use expand/recursive/setTimeout\r\n // submit initial query.\r\n return this.create(this.options.params)\r\n // expand to re-query next interval\r\n .pipe(\r\n expand((result) => {\r\n if (!this.options.interval || this.options.interval <= 0) {\r\n // stop the interval delay.\r\n return EMPTY;\r\n }\r\n // return new observable after the delay.\r\n return of(result)\r\n .pipe(\r\n // delay for the interval.\r\n delay(this.options.interval),\r\n // complete previous observable and switch to new observable.\r\n switchMap(() => this.create(this.options.params)));\r\n }));\r\n }\r\n\r\n this.repeaterStop();\r\n if (!this.options.interval) {\r\n // single observable. (no repeat)\r\n return this.create(this.options.params);\r\n }\r\n\r\n // create new observable to repeat calling repeaterCreate() function.\r\n return new Observable(\r\n (observer: Observer<TData>) => {\r\n this.observer = observer;\r\n this.repeaterInitialize();\r\n this.repeaterStart();\r\n return () => {\r\n // either closed by unsubscribe or observer is completed.\r\n this.observer?.complete();\r\n\r\n this.observer = null;\r\n this.repeaterStop();\r\n };\r\n });\r\n }\r\n\r\n private repeaterInitialize(): void {\r\n if (this.options.interval < 1000) {\r\n // min 1 sec interval.\r\n this.options.interval = 1000;\r\n }\r\n\r\n // 100 or 200 or 1000 pulse.\r\n const pulse = this.options.interval <= 5000 ? 100 : (this.options.interval <= 10000 ? 200 : 1000);\r\n const cycle = Math.floor(this.options.interval / pulse);\r\n this.repeaterContext = {\r\n counter: 0,\r\n request: QueryCache.repeaterParked,\r\n timer: 0,\r\n pulse,\r\n cycle,\r\n verify: 0\r\n };\r\n }\r\n\r\n private repeaterStart(): void {\r\n // run first query.\r\n this.repeaterCreate();\r\n\r\n // start interval counter is incremented but cycled by cycle number.\r\n // if it hits request === counter, call this.repeaterCreate();\r\n this.repeaterContext.timer = setInterval(this.repeaterCheck.bind(this), this.repeaterContext.pulse);\r\n }\r\n\r\n private repeaterStop(): void {\r\n if (!this.repeaterContext) {\r\n return;\r\n }\r\n\r\n if (this.repeaterContext.timer) {\r\n clearInterval(this.repeaterContext.timer);\r\n this.repeaterContext.timer = null;\r\n }\r\n\r\n this.repeaterContext.request = QueryCache.repeaterParked;\r\n this.repeaterContext.verify = 0;\r\n this.repeaterContext.counter = 0;\r\n }\r\n\r\n private repeaterCreate(): void {\r\n if (!this.observer) {\r\n this.repeaterStop();\r\n return;\r\n }\r\n\r\n if (!this.options.interval) {\r\n if (this.observer) {\r\n this.observer.complete();\r\n this.observer = null;\r\n }\r\n\r\n this.repeaterStop();\r\n return;\r\n }\r\n\r\n // subscribe to the create observable with params passed.\r\n this.create(this.options.params)\r\n .subscribe({\r\n next: data => {\r\n this.observer?.next(data);\r\n },\r\n error: error => {\r\n this.observer?.error(error);\r\n this.repeaterStop();\r\n },\r\n complete: () => {\r\n if (!this.options.interval) {\r\n // complete if the interval was reset.\r\n if (this.observer) {\r\n this.observer.complete();\r\n this.observer = null;\r\n }\r\n\r\n this.repeaterStop();\r\n return;\r\n }\r\n\r\n if (this.name) {\r\n this.log(`repeat: request=${this.repeaterContext.request}, counter=${this.repeaterContext.counter}`);\r\n }\r\n\r\n // set next request counter number if it's parked.\r\n if (this.repeaterContext.request === QueryCache.repeaterParked) {\r\n this.repeaterContext.request = this.repeaterContext.counter;\r\n }\r\n }\r\n });\r\n }\r\n\r\n private repeaterCheck(): void {\r\n if (this.repeaterContext.request === QueryCache.repeaterParked) {\r\n return;\r\n }\r\n\r\n // update cycle counter\r\n this.repeaterContext.counter = ++this.repeaterContext.counter % this.repeaterContext.cycle;\r\n\r\n // update verify counter which starts from 0 to cycle.\r\n this.repeaterContext.verify++;\r\n\r\n // check if it hits a cycle.\r\n if (this.repeaterContext.counter === this.repeaterContext.request) {\r\n // wait for repeatCreate observable to complete. reset verify counter first before activate the observable.\r\n this.repeaterContext.request = QueryCache.repeaterParked;\r\n this.repeaterContext.verify = 0;\r\n this.repeaterCreate();\r\n } else if (this.repeaterContext.verify > this.repeaterContext.cycle + 1) {\r\n throw new Error('QueryCache: Cycle counter running over. cycle={0}, counter={1}, request={2}, verify={3}'.format(\r\n this.repeaterContext.cycle, this.repeaterContext.counter, this.repeaterContext.request, this.repeaterContext.verify));\r\n }\r\n }\r\n}\r\n"]}