@sentio/runtime 4.0.0-rc.1 → 4.0.0-rc.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/src/service.ts CHANGED
@@ -1,28 +1,32 @@
1
- import { CallContext, ServerError, Status } from 'nice-grpc'
2
- import { DebugInfo, RichServerError } from 'nice-grpc-error-details'
1
+ import { ConnectError, Code, type HandlerContext, type ServiceImpl } from '@connectrpc/connect'
3
2
  import { from } from 'ix/Ix.asynciterable'
4
3
  import { withAbort } from 'ix/Ix.asynciterable.operators'
5
4
 
6
5
  import {
7
- DataBinding,
8
- DeepPartial,
9
- Empty,
10
- EthCallParam,
6
+ type DataBinding,
7
+ type Empty,
8
+ EmptySchema,
9
+ type EthCallParam,
11
10
  HandlerType,
12
- PreparedData,
13
- PreprocessResult,
14
- PreprocessStreamRequest,
15
- PreprocessStreamResponse,
16
- ProcessBindingResponse,
17
- ProcessBindingsRequest,
18
- ProcessConfigRequest,
19
- ProcessConfigResponse,
20
- ProcessorServiceImplementation,
21
- ProcessResult,
22
- ProcessStreamRequest,
23
- ProcessStreamResponse,
24
- StartRequest
11
+ type PreparedData,
12
+ PreparedDataSchema,
13
+ type PreprocessResult,
14
+ type PreprocessStreamRequest,
15
+ ProcessBindingResponseSchema,
16
+ type ProcessBindingsRequest,
17
+ type ProcessConfigRequest,
18
+ type ProcessConfigResponse,
19
+ ProcessConfigResponseSchema,
20
+ Processor,
21
+ type ProcessResult,
22
+ ProcessResultSchema,
23
+ RuntimeInfoSchema,
24
+ type ProcessStreamRequest,
25
+ ProcessStreamResponseSchema,
26
+ PreprocessStreamResponseSchema,
27
+ type StartRequest
25
28
  } from '@sentio/protos'
29
+ import { create, type MessageInitShape } from '@bufbuild/protobuf'
26
30
 
27
31
  import { PluginManager } from './plugin.js'
28
32
  import { errorString, makeEthCallKey, mergeProcessResults } from './utils.js'
@@ -44,7 +48,13 @@ const { process_binding_count, process_binding_time, process_binding_error } = p
44
48
  return this.toString()
45
49
  }
46
50
 
47
- export class ProcessorServiceImpl implements ProcessorServiceImplementation {
51
+ // Init-shapes carried over the rxjs Subject before being yielded by connect.
52
+ // connect accepts MessageInitShape for streaming outputs, so the oneof discriminated
53
+ // union must be filled in (e.g. { value: { case: 'result', value: ... } }).
54
+ export type ProcessStreamResponseInit = MessageInitShape<typeof ProcessStreamResponseSchema>
55
+ export type PreprocessStreamResponseInit = MessageInitShape<typeof PreprocessStreamResponseSchema>
56
+
57
+ export class ProcessorServiceImpl implements ServiceImpl<typeof Processor> {
48
58
  private started = false
49
59
  // When there is unhandled error, stop process and return unavailable error
50
60
  unhandled: Error
@@ -70,16 +80,16 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
70
80
  this.enablePartition = options?.enablePartition == true
71
81
  }
72
82
 
73
- async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
83
+ async getConfig(request: ProcessConfigRequest, context: HandlerContext): Promise<ProcessConfigResponse> {
74
84
  if (!this.started) {
75
- throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
85
+ throw new ConnectError('Service Not started.', Code.Unavailable)
76
86
  }
77
87
  // if (!this.processorConfig) {
78
- // throw new ServerError(Status.INTERNAL, 'Process config empty.')
88
+ // throw new ConnectError('Process config empty.', Code.Internal)
79
89
  // }
80
90
 
81
91
  // Don't use .create to keep compatiblity
82
- const newConfig = ProcessConfigResponse.fromPartial({})
92
+ const newConfig = create(ProcessConfigResponseSchema, {})
83
93
  await PluginManager.INSTANCE.configure(newConfig)
84
94
  return newConfig
85
95
  }
@@ -90,7 +100,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
90
100
  // await PluginManager.INSTANCE.configure(this.processorConfig)
91
101
  // }
92
102
 
93
- async start(request: StartRequest, context: CallContext): Promise<Empty> {
103
+ async start(request: StartRequest, context: HandlerContext): Promise<MessageInitShape<typeof EmptySchema>> {
94
104
  if (this.started) {
95
105
  return {}
96
106
  }
@@ -114,7 +124,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
114
124
 
115
125
  await this.loader()
116
126
  } catch (e) {
117
- throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
127
+ throw new ConnectError('Failed to load processor: ' + errorString(e), Code.InvalidArgument)
118
128
  }
119
129
 
120
130
  await PluginManager.INSTANCE.start(request)
@@ -122,13 +132,13 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
122
132
  // try {
123
133
  // await this.configure()
124
134
  // } catch (e) {
125
- // throw new ServerError(Status.INTERNAL, 'Failed to start processor : ' + errorString(e))
135
+ // throw new ConnectError('Failed to start processor : ' + errorString(e), Code.Internal)
126
136
  // }
127
137
  this.started = true
128
138
  return {}
129
139
  }
130
140
 
131
- async stop(request: Empty, context: CallContext): Promise<Empty> {
141
+ async stop(request: Empty, context: HandlerContext): Promise<MessageInitShape<typeof EmptySchema>> {
132
142
  console.log('Server Shutting down in 5 seconds')
133
143
  if (this.shutdownHandler) {
134
144
  setTimeout(this.shutdownHandler, 5000)
@@ -136,10 +146,13 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
136
146
  return {}
137
147
  }
138
148
 
139
- async processBindings(request: ProcessBindingsRequest, options?: CallContext): Promise<ProcessBindingResponse> {
149
+ async processBindings(
150
+ request: ProcessBindingsRequest,
151
+ context?: HandlerContext
152
+ ): Promise<MessageInitShape<typeof ProcessBindingResponseSchema>> {
140
153
  const preparedData = this.enablePreprocess
141
- ? await this.preprocessBindings(request.bindings, {}, undefined, options)
142
- : { ethCallResults: {} }
154
+ ? await this.preprocessBindings(request.bindings, {}, undefined, context)
155
+ : create(PreparedDataSchema, { ethCallResults: {} })
143
156
 
144
157
  const promises = []
145
158
  for (const binding of request.bindings) {
@@ -174,7 +187,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
174
187
  bindings: DataBinding[],
175
188
  preprocessStore: { [k: string]: any },
176
189
  dbContext?: StoreContext,
177
- options?: CallContext
190
+ options?: HandlerContext
178
191
  ): Promise<PreparedData> {
179
192
  // console.debug(`preprocessBindings start, bindings: ${bindings.length}`)
180
193
  const promises = []
@@ -274,30 +287,24 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
274
287
  // console.debug(
275
288
  // `${Object.keys(results).length} calls finished, actual calls: ${callPromises.length + multicallPromises.length}, elapsed: ${Date.now() - start}ms`
276
289
  // )
277
- return {
290
+ return create(PreparedDataSchema, {
278
291
  ethCallResults: results
279
- }
292
+ })
280
293
  }
281
294
 
282
295
  async preprocessBinding(
283
296
  request: DataBinding,
284
297
  preprocessStore: { [k: string]: any },
285
298
  dbContext?: StoreContext,
286
- options?: CallContext
299
+ options?: HandlerContext
287
300
  ): Promise<PreprocessResult> {
288
301
  if (!this.started) {
289
- throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
302
+ throw new ConnectError('Service Not started.', Code.Unavailable)
290
303
  }
291
304
  if (this.unhandled) {
292
- throw new RichServerError(
293
- Status.UNAVAILABLE,
305
+ throw new ConnectError(
294
306
  'Unhandled exception/rejection in previous request: ' + errorString(this.unhandled),
295
- [
296
- DebugInfo.fromPartial({
297
- detail: this.unhandled.message,
298
- stackEntries: this.unhandled.stack?.split('\n')
299
- })
300
- ]
307
+ Code.Unavailable
301
308
  )
302
309
  }
303
310
  return await PluginManager.INSTANCE.preprocessBinding(request, preprocessStore, dbContext)
@@ -306,21 +313,15 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
306
313
  async processBinding(
307
314
  request: DataBinding,
308
315
  preparedData: PreparedData | undefined,
309
- options?: CallContext
316
+ options?: HandlerContext
310
317
  ): Promise<ProcessResult> {
311
318
  if (!this.started) {
312
- throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
319
+ throw new ConnectError('Service Not started.', Code.Unavailable)
313
320
  }
314
321
  if (this.unhandled) {
315
- throw new RichServerError(
316
- Status.UNAVAILABLE,
322
+ throw new ConnectError(
317
323
  'Unhandled exception/rejection in previous request: ' + errorString(this.unhandled),
318
- [
319
- DebugInfo.fromPartial({
320
- detail: this.unhandled.message,
321
- stackEntries: this.unhandled.stack?.split('\n')
322
- })
323
- ]
324
+ Code.Unavailable
324
325
  )
325
326
  }
326
327
 
@@ -333,16 +334,16 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
333
334
  return result
334
335
  }
335
336
 
336
- async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: CallContext) {
337
+ async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: HandlerContext) {
337
338
  if (!this.started) {
338
- throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
339
+ throw new ConnectError('Service Not started.', Code.Unavailable)
339
340
  }
340
341
 
341
- const subject = new Subject<DeepPartial<ProcessStreamResponse>>()
342
+ const subject = new Subject<ProcessStreamResponseInit>()
342
343
  this.handleRequests(requests, subject)
343
344
  .then(() => {
344
345
  if (this.preparedData) {
345
- this.preparedData = { ethCallResults: {} }
346
+ this.preparedData = create(PreparedDataSchema, { ethCallResults: {} })
346
347
  }
347
348
  subject.complete()
348
349
  })
@@ -355,26 +356,31 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
355
356
 
356
357
  async handlePreprocessRequests(
357
358
  requests: AsyncIterable<PreprocessStreamRequest>,
358
- subject: Subject<DeepPartial<PreprocessStreamResponse>>
359
+ subject: Subject<PreprocessStreamResponseInit>
359
360
  ) {
360
361
  const contexts = new Contexts()
361
362
  const preprocessStore: { [k: string]: any } = {}
362
363
 
363
364
  for await (const request of requests) {
364
365
  try {
365
- if (request.bindings) {
366
- const bindings = request.bindings.bindings
367
- const dbContext = contexts.new(request.processId, subject)
366
+ if (request.value.case === 'bindings') {
367
+ const bindings = request.value.value.bindings
368
+ // NOTE: StoreContext/Contexts are typed for the V2 ProcessStreamResponse stream, but the
369
+ // preprocess flow reuses them only to drive DB request/response plumbing. The preprocess
370
+ // stream message (flat `dbRequest`) differs from the V2 oneof shape, so we hand the
371
+ // preprocess subject in via a cast. db-context.ts owns the actual emit shape; integrator
372
+ // should confirm StoreContext.doSend stays compatible with both stream message types.
373
+ const dbContext = contexts.new(request.processId, subject as unknown as Subject<ProcessStreamResponseInit>)
368
374
  const start = Date.now()
369
375
  this.preprocessBindings(bindings, preprocessStore, dbContext, undefined)
370
376
  .then((preparedData) => {
371
377
  // TODO maybe not proper to pass data in this way
372
- this.preparedData = {
378
+ this.preparedData = create(PreparedDataSchema, {
373
379
  ethCallResults: {
374
380
  ...this.preparedData?.ethCallResults,
375
381
  ...preparedData.ethCallResults
376
382
  }
377
- }
383
+ })
378
384
  subject.next({
379
385
  processId: request.processId
380
386
  })
@@ -389,9 +395,9 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
389
395
  contexts.delete(request.processId)
390
396
  })
391
397
  }
392
- if (request.dbResult) {
398
+ if (request.value.case === 'dbResult') {
393
399
  const dbContext = contexts.get(request.processId)
394
- dbContext?.result(request.dbResult)
400
+ dbContext?.result(request.value.value)
395
401
  }
396
402
  } catch (e) {
397
403
  // should not happen
@@ -400,12 +406,12 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
400
406
  }
401
407
  }
402
408
 
403
- async *preprocessBindingsStream(requests: AsyncIterable<PreprocessStreamRequest>, context: CallContext) {
409
+ async *preprocessBindingsStream(requests: AsyncIterable<PreprocessStreamRequest>, context: HandlerContext) {
404
410
  if (!this.started) {
405
- throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
411
+ throw new ConnectError('Service Not started.', Code.Unavailable)
406
412
  }
407
413
 
408
- const subject = new Subject<DeepPartial<PreprocessStreamResponse>>()
414
+ const subject = new Subject<PreprocessStreamResponseInit>()
409
415
  this.handlePreprocessRequests(requests, subject)
410
416
  .then(() => {
411
417
  subject.complete()
@@ -421,14 +427,14 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
421
427
 
422
428
  protected async handleRequests(
423
429
  requests: AsyncIterable<ProcessStreamRequest>,
424
- subject: Subject<DeepPartial<ProcessStreamResponse>>
430
+ subject: Subject<ProcessStreamResponseInit>
425
431
  ) {
426
432
  let lastBinding: DataBinding | undefined = undefined
427
433
  for await (const request of requests) {
428
434
  try {
429
435
  // console.log('received request:', request, 'lastBinding:', lastBinding)
430
- if (request.binding) {
431
- lastBinding = request.binding
436
+ if (request.value.case === 'binding') {
437
+ lastBinding = request.value.value
432
438
  }
433
439
  this.handleRequest(request, lastBinding, subject)
434
440
  } catch (e) {
@@ -441,27 +447,28 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
441
447
  async handleRequest(
442
448
  request: ProcessStreamRequest,
443
449
  lastBinding: DataBinding | undefined,
444
- subject: Subject<DeepPartial<ProcessStreamResponse>>
450
+ subject: Subject<ProcessStreamResponseInit>
445
451
  ) {
446
- if (request.binding) {
452
+ if (request.value.case === 'binding') {
453
+ const binding = request.value.value
447
454
  process_binding_count.add(1)
448
455
 
449
456
  // Adjust binding will make some request become invalid by setting UNKNOWN HandlerType
450
457
  // for older SDK version, so we just return empty result for them here
451
- if (request.binding.handlerType === HandlerType.UNKNOWN) {
458
+ if (binding.handlerType === HandlerType.UNKNOWN) {
452
459
  subject.next({
453
460
  processId: request.processId,
454
- result: ProcessResult.create()
461
+ value: { case: 'result', value: create(ProcessResultSchema) }
455
462
  })
456
463
  return
457
464
  }
458
465
 
459
466
  if (this.enablePartition) {
460
467
  try {
461
- const partitions = await PluginManager.INSTANCE.partition(request.binding)
468
+ const partitions = await PluginManager.INSTANCE.partition(binding)
462
469
  subject.next({
463
470
  processId: request.processId,
464
- partitions
471
+ value: { case: 'partitions', value: partitions }
465
472
  })
466
473
  } catch (e) {
467
474
  console.error('Partition error:', e)
@@ -469,11 +476,11 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
469
476
  return
470
477
  }
471
478
  } else {
472
- this.startProcess(request.processId, request.binding, subject)
479
+ this.startProcess(request.processId, binding, subject)
473
480
  }
474
481
  }
475
482
 
476
- if (request.start) {
483
+ if (request.value.case === 'start') {
477
484
  if (!lastBinding) {
478
485
  console.error('start request received without binding')
479
486
  subject.error(new Error('start request received without binding'))
@@ -482,17 +489,17 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
482
489
  this.startProcess(request.processId, lastBinding, subject)
483
490
  }
484
491
 
485
- if (request.dbResult) {
492
+ if (request.value.case === 'dbResult') {
486
493
  const dbContext = this.dbContexts.get(request.processId)
487
494
  try {
488
- dbContext?.result(request.dbResult)
495
+ dbContext?.result(request.value.value)
489
496
  } catch (e) {
490
497
  subject.error(new Error('db result error, process should stop'))
491
498
  }
492
499
  }
493
500
  }
494
501
 
495
- private startProcess(processId: number, binding: DataBinding, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
502
+ private startProcess(processId: number, binding: DataBinding, subject: Subject<ProcessStreamResponseInit>) {
496
503
  const dbContext = this.dbContexts.new(processId, subject)
497
504
  const start = Date.now()
498
505
  PluginManager.INSTANCE.processBinding(binding, this.preparedData, dbContext)
@@ -500,7 +507,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
500
507
  // await all pending db requests
501
508
  await dbContext.awaitPendings()
502
509
  subject.next({
503
- result,
510
+ value: { case: 'result', value: result },
504
511
  processId: processId
505
512
  })
506
513
  recordRuntimeInfo(result, binding.handlerType)
@@ -521,9 +528,9 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
521
528
  export function recordRuntimeInfo(results: ProcessResult, handlerType: HandlerType) {
522
529
  for (const list of [results.gauges, results.counters, results.events, results.exports]) {
523
530
  list.forEach((e) => {
524
- e.runtimeInfo = {
531
+ e.runtimeInfo = create(RuntimeInfoSchema, {
525
532
  from: handlerType
526
- }
533
+ })
527
534
  })
528
535
  }
529
536
  }
@@ -535,7 +542,7 @@ class Contexts {
535
542
  return this.contexts.get(processId)
536
543
  }
537
544
 
538
- new(processId: number, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
545
+ new(processId: number, subject: Subject<ProcessStreamResponseInit>) {
539
546
  const context = new StoreContext(subject, processId)
540
547
  this.contexts.set(processId, context)
541
548
  return context
package/src/utils.ts CHANGED
@@ -1,18 +1,15 @@
1
- import { EthCallParam, ProcessResult } from '@sentio/protos'
1
+ import { type EthCallParam, type ProcessResult, ProcessResultSchema, StateResultSchema } from '@sentio/protos'
2
+ import { create } from '@bufbuild/protobuf'
2
3
  import { createRequire } from 'module'
3
4
 
4
- // TODO better handling this, because old proto doesn't have this
5
- import { StateResult, ProcessResult as ProcessResultFull } from './gen/processor/protos/processor.js'
6
-
7
5
  import { Required } from 'utility-types'
8
6
  import path from 'path'
9
7
  import fs from 'fs'
10
8
 
11
9
  export function mergeProcessResults(results: ProcessResult[]): Required<ProcessResult, 'states'> {
12
- const res = {
13
- ...ProcessResultFull.create(),
14
- states: StateResult.create()
15
- }
10
+ const res = create(ProcessResultSchema, {
11
+ states: create(StateResultSchema)
12
+ })
16
13
  return mergeProcessResultsInPlace(res, results)
17
14
  }
18
15
 
@@ -20,7 +17,7 @@ export function mergeProcessResultsInPlace(
20
17
  res: ProcessResult,
21
18
  results: ProcessResult[]
22
19
  ): Required<ProcessResult, 'states'> {
23
- res.states = res.states || StateResult.create()
20
+ res.states = res.states || create(StateResultSchema)
24
21
  for (const r of results) {
25
22
  // not using spread operator since it puts all element on the stack
26
23
  // cause maximum call stack size exceeded error if it's a large array
@@ -30,11 +27,11 @@ export function mergeProcessResultsInPlace(
30
27
  res.events = mergeArrayInPlace(res.events, r.events)
31
28
  res.exports = mergeArrayInPlace(res.exports, r.exports)
32
29
  res.timeseriesResult = mergeArrayInPlace(res.timeseriesResult, r.timeseriesResult)
33
- res.states = {
30
+ res.states = create(StateResultSchema, {
34
31
  configUpdated: res.states?.configUpdated || r.states?.configUpdated || false
35
- }
32
+ })
36
33
  }
37
- return res
34
+ return res as Required<ProcessResult, 'states'>
38
35
  }
39
36
 
40
37
  function mergeArrayInPlace<T>(dst: T[], src: T[]): T[] {