terrier-engine 4.14.1 → 4.14.2
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/package.json +1 -1
- package/terrier/api-subscriber.ts +114 -87
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import Api, {ApiResponse, noArgListener, Streamer} from "./api"
|
|
|
2
2
|
import {LogEntry} from "./logging"
|
|
3
3
|
import dayjs from "dayjs"
|
|
4
4
|
import duration from "dayjs/plugin/duration"
|
|
5
|
+
import {Logger} from "tuff-core/logging";
|
|
5
6
|
|
|
6
7
|
dayjs.extend(duration)
|
|
7
8
|
|
|
@@ -73,10 +74,7 @@ export type CloseEvent = BaseSubscriptionEvent & { _type: '_close' }
|
|
|
73
74
|
* Each subclass is responsible for providing a constructor with whatever dependencies are required, but all subclasses
|
|
74
75
|
* must implement `params` and `options` properties and handle them appropriately.
|
|
75
76
|
*/
|
|
76
|
-
export abstract class ApiSubscriber<TResult, TParams extends SubscriptionParams> {
|
|
77
|
-
|
|
78
|
-
public abstract params: TParams
|
|
79
|
-
public abstract options?: SubscriptionOptions
|
|
77
|
+
export abstract class ApiSubscriber<TResult, TParams extends SubscriptionParams, TOptions extends SubscriptionOptions = SubscriptionOptions> {
|
|
80
78
|
|
|
81
79
|
protected isSubscribed: boolean = false
|
|
82
80
|
|
|
@@ -84,6 +82,17 @@ export abstract class ApiSubscriber<TResult, TParams extends SubscriptionParams>
|
|
|
84
82
|
protected lifecycleHandlers: SubscriptionLifecycleHandlers = { onSubscribe: [], onUnsubscribe: [], onClose: [] }
|
|
85
83
|
protected otherHandlers: Record<string, ((event: unknown) => boolean | void)[]> = {}
|
|
86
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @param params parameters sent with subscription requests to customize server-side behavior
|
|
87
|
+
* @param options options that customize client-side behavior of the subscriber
|
|
88
|
+
* @param logger
|
|
89
|
+
* @protected
|
|
90
|
+
*/
|
|
91
|
+
protected constructor(public params: TParams, public options?: TOptions, public logger?: Logger) {
|
|
92
|
+
this.options ??= {} as TOptions
|
|
93
|
+
this.logger ??= new Logger(this.constructor.name)
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
// Handler registration
|
|
88
97
|
|
|
89
98
|
/** Register a handler for a custom event type */
|
|
@@ -153,9 +162,7 @@ export abstract class ApiSubscriber<TResult, TParams extends SubscriptionParams>
|
|
|
153
162
|
|
|
154
163
|
/** Stops the subscription and closes the connection. */
|
|
155
164
|
public unsubscribe(): void {
|
|
156
|
-
if (!this.isSubscribed)
|
|
157
|
-
throw new Error("Can't unsubscribe from a un-started subscription!")
|
|
158
|
-
}
|
|
165
|
+
if (!this.isSubscribed) return
|
|
159
166
|
this.unsubscribeImpl()
|
|
160
167
|
this.isSubscribed = false
|
|
161
168
|
this.notifyLifecycle('onUnsubscribe')
|
|
@@ -176,6 +183,7 @@ export abstract class ApiSubscriber<TResult, TParams extends SubscriptionParams>
|
|
|
176
183
|
protected notifyLifecycle(key: keyof SubscriptionLifecycleHandlers): void
|
|
177
184
|
protected notifyLifecycle(key: 'onClose', reason: string | undefined): void
|
|
178
185
|
protected notifyLifecycle(key: keyof SubscriptionLifecycleHandlers, reason: string | undefined = undefined): void {
|
|
186
|
+
this.logNotification(key, reason)
|
|
179
187
|
if (key == 'onClose') {
|
|
180
188
|
this.lifecycleHandlers[key].forEach(handler => handler(reason))
|
|
181
189
|
} else {
|
|
@@ -183,10 +191,47 @@ export abstract class ApiSubscriber<TResult, TParams extends SubscriptionParams>
|
|
|
183
191
|
}
|
|
184
192
|
}
|
|
185
193
|
|
|
194
|
+
protected notifyResult(event: ResultEvent<TResult>) {
|
|
195
|
+
this.logNotification('_result', event)
|
|
196
|
+
const shouldContinue = this.eventHandlers.onResult
|
|
197
|
+
.map(handler => handler(event))
|
|
198
|
+
.every(b => b)
|
|
199
|
+
if (!shouldContinue) this.unsubscribe()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
protected notifyError(event: ErrorEvent) {
|
|
203
|
+
this.logNotification('_error', event)
|
|
204
|
+
const shouldContinue = this.eventHandlers.onError
|
|
205
|
+
.map(handler => handler(event))
|
|
206
|
+
.every(b => b)
|
|
207
|
+
if (!shouldContinue) this.unsubscribe()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
protected notifyLog(event: LogEvent) {
|
|
211
|
+
this.logNotification('_log', event)
|
|
212
|
+
this.eventHandlers.onLog.forEach(handler => handler(event))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
protected notifyOther(key: string, event: any) {
|
|
216
|
+
this.logNotification(key, event)
|
|
217
|
+
const shouldContinue = this.otherHandlers[key]
|
|
218
|
+
.map(handler => handler(event) !== false)
|
|
219
|
+
.every(b => b)
|
|
220
|
+
if (!shouldContinue) this.unsubscribe()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
protected logNotification(key: string, event: any) {
|
|
224
|
+
if (event === undefined) {
|
|
225
|
+
this.logger?.debug('notify', key)
|
|
226
|
+
} else {
|
|
227
|
+
this.logger?.debug('notify', key, event)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
186
231
|
/** Subclasses implement this method to start the subscription */
|
|
187
232
|
protected abstract subscribeImpl(): void
|
|
188
233
|
|
|
189
|
-
/** Subclasses
|
|
234
|
+
/** Subclasses implement this method to end the subscription */
|
|
190
235
|
protected abstract unsubscribeImpl(): void
|
|
191
236
|
}
|
|
192
237
|
|
|
@@ -194,10 +239,12 @@ export abstract class ApiSubscriber<TResult, TParams extends SubscriptionParams>
|
|
|
194
239
|
// Polling Subscriber
|
|
195
240
|
////////////////////////////////////////////////////////////////////////////////
|
|
196
241
|
|
|
242
|
+
type PollingResponse<TResult> = (ApiResponse & SubscriptionEvent<TResult>) | { events: SubscriptionEvent<TResult>[] }
|
|
243
|
+
|
|
197
244
|
/**
|
|
198
245
|
* An implementation of ApiSubscriber that uses polling to periodically make a request to the server
|
|
199
246
|
*/
|
|
200
|
-
export class PollingSubscriber<TResult, TParams extends SubscriptionParams> extends ApiSubscriber<TResult, TParams> {
|
|
247
|
+
export class PollingSubscriber<TResult, TParams extends SubscriptionParams> extends ApiSubscriber<TResult, TParams, (SubscriptionOptions & GetSubscriberOptions)> {
|
|
201
248
|
|
|
202
249
|
// used to ensure we only schedule one interval at a time and allows for cancellation.
|
|
203
250
|
private timeoutHandle: NodeJS.Timeout | null = null
|
|
@@ -207,14 +254,16 @@ export class PollingSubscriber<TResult, TParams extends SubscriptionParams> exte
|
|
|
207
254
|
* @param params params to use to make the request.
|
|
208
255
|
* @param interval the interval on which to poll. Either a number of milliseconds or a dayjs Duration.
|
|
209
256
|
* @param options extra subscription options
|
|
257
|
+
* @param logger a specific logger to use
|
|
210
258
|
*/
|
|
211
259
|
constructor(
|
|
212
260
|
public url: string,
|
|
213
|
-
public params: TParams,
|
|
214
261
|
public interval: number | duration.Duration,
|
|
215
|
-
|
|
262
|
+
params: TParams,
|
|
263
|
+
options?: (SubscriptionOptions & GetSubscriberOptions),
|
|
264
|
+
logger?: Logger
|
|
216
265
|
) {
|
|
217
|
-
super()
|
|
266
|
+
super(params, options, logger)
|
|
218
267
|
}
|
|
219
268
|
|
|
220
269
|
subscribeImpl() {
|
|
@@ -234,63 +283,49 @@ export class PollingSubscriber<TResult, TParams extends SubscriptionParams> exte
|
|
|
234
283
|
private makeRequest() {
|
|
235
284
|
this.clearTimeout()
|
|
236
285
|
this.request().then(response => {
|
|
237
|
-
let shouldContinuePolling = true
|
|
238
286
|
if ('events' in response) {
|
|
239
287
|
for (const event of response.events) {
|
|
240
|
-
|
|
288
|
+
this.handleEvent(event)
|
|
241
289
|
}
|
|
242
290
|
} else {
|
|
243
291
|
if (response.status == 'error') response._type = '_error'
|
|
244
|
-
|
|
292
|
+
this.handleEvent(response)
|
|
245
293
|
}
|
|
246
294
|
|
|
247
|
-
|
|
248
|
-
this.startTimeout()
|
|
249
|
-
} else {
|
|
250
|
-
this.unsubscribe()
|
|
251
|
-
}
|
|
295
|
+
this.startTimeout()
|
|
252
296
|
})
|
|
253
297
|
}
|
|
254
298
|
|
|
255
|
-
protected request(): Promise<
|
|
299
|
+
protected async request(): Promise<PollingResponse<TResult>> {
|
|
256
300
|
const params = Api.objectToQueryParams(this.params, this.options?.snakifyKeys)
|
|
257
|
-
return Api.get<((ApiResponse & SubscriptionEvent<TResult>) | { events: SubscriptionEvent<TResult>[] })>(this.url, params)
|
|
258
|
-
}
|
|
259
301
|
|
|
260
|
-
|
|
261
|
-
|
|
302
|
+
// Add _polling param so that the server-side can adjust behavior based on whether it is being polled or requested normally.
|
|
303
|
+
params._polling = true.toString()
|
|
304
|
+
const result = await Api.get<PollingResponse<TResult>>(this.url, params);
|
|
305
|
+
this.logger?.debug("polling request", params, result)
|
|
306
|
+
return result
|
|
307
|
+
}
|
|
262
308
|
|
|
309
|
+
protected handleEvent(event: SubscriptionEvent<TResult>) {
|
|
263
310
|
switch (event._type) {
|
|
264
311
|
case '_result':
|
|
265
312
|
case undefined:
|
|
266
|
-
|
|
267
|
-
const r = handler(event)
|
|
268
|
-
shouldContinuePolling &&= r
|
|
269
|
-
}
|
|
313
|
+
this.notifyResult(event)
|
|
270
314
|
break
|
|
271
315
|
case '_error':
|
|
272
|
-
|
|
273
|
-
const r = handler(event)
|
|
274
|
-
shouldContinuePolling &&= r
|
|
275
|
-
}
|
|
316
|
+
this.notifyError(event)
|
|
276
317
|
break
|
|
277
318
|
case '_log':
|
|
278
|
-
this.
|
|
319
|
+
this.notifyLog(event)
|
|
279
320
|
break
|
|
280
321
|
case '_close':
|
|
281
322
|
this.notifyLifecycle('onClose')
|
|
282
323
|
this.close()
|
|
283
|
-
shouldContinuePolling = this.options?.keepAlive ?? false
|
|
284
324
|
break
|
|
285
325
|
default:
|
|
286
326
|
const otherEvent = event as any
|
|
287
|
-
|
|
288
|
-
const r = handler(otherEvent) ?? true
|
|
289
|
-
shouldContinuePolling &&= r
|
|
290
|
-
}
|
|
327
|
+
this.notifyOther(otherEvent._type, otherEvent)
|
|
291
328
|
}
|
|
292
|
-
|
|
293
|
-
return shouldContinuePolling
|
|
294
329
|
}
|
|
295
330
|
|
|
296
331
|
protected clearTimeout() {
|
|
@@ -301,8 +336,12 @@ export class PollingSubscriber<TResult, TParams extends SubscriptionParams> exte
|
|
|
301
336
|
}
|
|
302
337
|
|
|
303
338
|
protected startTimeout() {
|
|
339
|
+
if (!this.isSubscribed) return
|
|
304
340
|
if (this.timeoutHandle) return
|
|
305
341
|
const intervalMs = (dayjs.isDuration(this.interval)) ? this.interval.asMilliseconds() : this.interval
|
|
342
|
+
|
|
343
|
+
this.logger?.debug("polling in", intervalMs, "milliseconds")
|
|
344
|
+
|
|
306
345
|
this.timeoutHandle = setTimeout(this.makeRequest.bind(this), intervalMs)
|
|
307
346
|
}
|
|
308
347
|
}
|
|
@@ -319,11 +358,12 @@ export class CableSubscriber<TResult, TParams extends SubscriptionParams> extend
|
|
|
319
358
|
|
|
320
359
|
constructor(
|
|
321
360
|
public channelName: string,
|
|
322
|
-
|
|
323
|
-
|
|
361
|
+
params: TParams,
|
|
362
|
+
options?: SubscriptionOptions,
|
|
363
|
+
logger?: Logger,
|
|
324
364
|
) {
|
|
325
365
|
throw new Error("Method not implemented.")
|
|
326
|
-
super()
|
|
366
|
+
super(params, options, logger)
|
|
327
367
|
}
|
|
328
368
|
|
|
329
369
|
public updateParams(_newParams: TParams): void {
|
|
@@ -341,72 +381,59 @@ export class CableSubscriber<TResult, TParams extends SubscriptionParams> extend
|
|
|
341
381
|
// Event Stream Subscriber
|
|
342
382
|
////////////////////////////////////////////////////////////////////////////////
|
|
343
383
|
|
|
344
|
-
export class StreamingSubscriber<TResult, TParams extends SubscriptionParams> extends ApiSubscriber<TResult, TParams> {
|
|
384
|
+
export class StreamingSubscriber<TResult, TParams extends SubscriptionParams> extends ApiSubscriber<TResult, TParams, (SubscriptionOptions & GetSubscriberOptions)> {
|
|
345
385
|
|
|
346
386
|
private streamer?: Streamer
|
|
347
387
|
|
|
348
388
|
constructor(
|
|
349
389
|
public url: string,
|
|
350
|
-
|
|
351
|
-
|
|
390
|
+
params: TParams,
|
|
391
|
+
options?: (SubscriptionOptions & GetSubscriberOptions),
|
|
392
|
+
logger?: Logger
|
|
352
393
|
) {
|
|
353
|
-
super()
|
|
394
|
+
super(params, options, logger)
|
|
354
395
|
}
|
|
355
396
|
|
|
356
397
|
public on<EventType>(eventType: string, handler: (event: EventType) => boolean | void): this {
|
|
357
|
-
this.streamer
|
|
398
|
+
if (!(eventType in this.otherHandlers) && this.streamer) {
|
|
399
|
+
this.listenOther(eventType, this.streamer)
|
|
400
|
+
}
|
|
358
401
|
return super.on(eventType, handler)
|
|
359
402
|
}
|
|
360
403
|
|
|
361
|
-
public
|
|
362
|
-
this.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
public onError(handler: (error: ErrorEvent) => boolean): this {
|
|
367
|
-
this.streamer?.on<ErrorEvent>('_error', handler)
|
|
368
|
-
return super.onError(handler)
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
public onLog(handler: (logEntry: LogEvent) => void): this {
|
|
372
|
-
this.streamer?.on<LogEvent>('_log', handler)
|
|
373
|
-
return super.onLog(handler)
|
|
404
|
+
public updateParams(newParams: TParams): void {
|
|
405
|
+
this.unsubscribeImpl()
|
|
406
|
+
this.params = newParams
|
|
407
|
+
this.subscribeImpl()
|
|
374
408
|
}
|
|
375
409
|
|
|
376
410
|
protected subscribeImpl(): void {
|
|
377
411
|
const params = Api.objectToQueryParams(this.params, this.options?.snakifyKeys)
|
|
378
|
-
const
|
|
379
|
-
const streamer = new Streamer(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
412
|
+
const paramsString = new URLSearchParams(params).toString()
|
|
413
|
+
const streamer = new Streamer(`${this.url}?${paramsString}`, this.options ?? {})
|
|
414
|
+
|
|
415
|
+
streamer
|
|
416
|
+
.on<ResultEvent<TResult>>('_result', this.notifyResult.bind(this))
|
|
417
|
+
.on<ErrorEvent>('_error', this.notifyError.bind(this))
|
|
418
|
+
.on<LogEvent>('_log', this.notifyLog.bind(this))
|
|
419
|
+
.onClose(() => {
|
|
420
|
+
this.close()
|
|
421
|
+
this.notifyLifecycle('onClose')
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
Object.keys(this.otherHandlers).forEach(key => {
|
|
425
|
+
this.listenOther(key, streamer)
|
|
387
426
|
})
|
|
388
|
-
Object.entries(this.otherHandlers)
|
|
389
|
-
.forEach(([key, handlers]) =>
|
|
390
|
-
handlers.forEach(handler => streamer.on(key, this.wrapHandler(handler)))
|
|
391
|
-
)
|
|
392
427
|
|
|
393
428
|
this.streamer = streamer
|
|
394
429
|
}
|
|
430
|
+
|
|
395
431
|
protected unsubscribeImpl(): void {
|
|
396
432
|
this.streamer?.sse.close()
|
|
397
433
|
this.streamer = undefined
|
|
398
434
|
}
|
|
399
435
|
|
|
400
|
-
|
|
401
|
-
this.
|
|
402
|
-
this.params = newParams
|
|
403
|
-
this.subscribeImpl()
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
private wrapHandler<T>(handler: (param: T) => boolean | void): (param: T) => void {
|
|
407
|
-
return (param: T) => {
|
|
408
|
-
const shouldContinue = handler(param) ?? true
|
|
409
|
-
if (!shouldContinue) this.unsubscribe()
|
|
410
|
-
}
|
|
436
|
+
protected listenOther(key: string, streamer: Streamer) {
|
|
437
|
+
streamer.on(key, (event) => this.notifyOther(key, event))
|
|
411
438
|
}
|
|
412
|
-
}
|
|
439
|
+
}
|