@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.
@@ -1,4 +1,5 @@
1
- import { ExecutionConfig } from './gen/processor/protos/processor.js'
1
+ import { ExecutionConfigSchema } from '@sentio/protos'
2
+ import { type MessageInitShape } from '@bufbuild/protobuf'
2
3
 
3
4
  /**
4
5
  * Configuration for the in-memory cache feature.
@@ -33,7 +34,7 @@ export interface GlobalConfig {
33
34
  * Execution configuration controlling how handlers are processed.
34
35
  * Includes settings for sequential vs parallel execution, block time handling, etc.
35
36
  */
36
- execution: Partial<ExecutionConfig>
37
+ execution: MessageInitShape<typeof ExecutionConfigSchema>
37
38
 
38
39
  /**
39
40
  * Optional cache configuration for enabling in-memory key-value storage.
package/src/plugin.ts CHANGED
@@ -1,15 +1,20 @@
1
1
  import {
2
- DataBinding,
2
+ type DataBinding,
3
3
  HandlerType,
4
- PreparedData,
5
- PreprocessResult,
6
- ProcessConfigResponse,
7
- ProcessResult,
8
- ProcessStreamResponse_Partitions,
4
+ type PreparedData,
5
+ type PreprocessResult,
6
+ PreprocessResultSchema,
7
+ type ProcessConfigResponse,
8
+ type ProcessResult,
9
+ ProcessResultSchema,
10
+ type StartRequest,
11
+ StartRequestSchema,
12
+ type ProcessStreamResponse_Partitions,
13
+ ProcessStreamResponse_PartitionsSchema,
9
14
  ProcessStreamResponse_Partitions_Partition_SysValue,
10
- StartRequest,
11
- UpdateTemplatesRequest
15
+ type UpdateTemplatesRequest
12
16
  } from '@sentio/protos'
17
+ import { create } from '@bufbuild/protobuf'
13
18
  import { IDataBindingContext, IStoreContext } from './db-context.js'
14
19
  import { AsyncLocalStorage } from 'node:async_hooks'
15
20
 
@@ -29,25 +34,28 @@ export abstract class Plugin {
29
34
  }
30
35
 
31
36
  async processBinding(request: DataBinding, preparedData: PreparedData | undefined): Promise<ProcessResult> {
32
- return ProcessResult.create()
37
+ return create(ProcessResultSchema)
33
38
  }
34
39
 
35
40
  async preprocessBinding(request: DataBinding, preprocessStore: { [k: string]: any }): Promise<PreprocessResult> {
36
- return PreprocessResult.create()
41
+ return create(PreprocessResultSchema)
37
42
  }
38
43
 
39
44
  async partition(request: DataBinding): Promise<ProcessStreamResponse_Partitions> {
40
- return {
45
+ return create(ProcessStreamResponse_PartitionsSchema, {
41
46
  partitions: request.handlerIds.reduce(
42
47
  (acc, id) => ({
43
48
  ...acc,
44
49
  [id]: {
45
- sysValue: ProcessStreamResponse_Partitions_Partition_SysValue.UNRECOGNIZED
50
+ value: {
51
+ case: 'sysValue',
52
+ value: ProcessStreamResponse_Partitions_Partition_SysValue.BLOCK_NUMBER
53
+ }
46
54
  }
47
55
  }),
48
56
  {}
49
57
  )
50
- }
58
+ })
51
59
  }
52
60
 
53
61
  /**
@@ -147,9 +155,11 @@ export class PluginManager {
147
155
 
148
156
  async updateTemplates(request: UpdateTemplatesRequest) {
149
157
  for (const plugin of this.plugins) {
150
- await plugin.start({
151
- templateInstances: request.templateInstances
152
- })
158
+ await plugin.start(
159
+ create(StartRequestSchema, {
160
+ templateInstances: request.templateInstances
161
+ })
162
+ )
153
163
  }
154
164
  }
155
165
  }
@@ -1,18 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'fs-extra'
4
- // import { compressionAlgorithms } from '@grpc/grpc-js'
5
- import { createServer } from 'nice-grpc'
6
- import { errorDetailsServerMiddleware } from 'nice-grpc-error-details'
7
- // import { registry as niceGrpcRegistry } from 'nice-grpc-prometheus'
8
- // import { openTelemetryServerMiddleware } from 'nice-grpc-opentelemetry'
4
+ import { type ConnectRouter } from '@connectrpc/connect'
5
+ import { connectNodeAdapter } from '@connectrpc/connect-node'
9
6
  import http from 'http'
7
+ import http2 from 'node:http2'
10
8
  // @ts-ignore inspector promises is not included in @type/node
11
9
  import { Session } from 'node:inspector/promises'
12
10
  import { fork, ChildProcess } from 'child_process'
13
11
  import { fileURLToPath } from 'url'
14
12
 
15
- import { ProcessorDefinition } from './gen/processor/protos/processor.js'
16
13
  import { ProcessorServiceImpl } from './service.js'
17
14
  import { configureEndpoints } from './endpoints.js'
18
15
  import { FullProcessorServiceImpl, FullProcessorServiceV3Impl } from './full-service.js'
@@ -20,7 +17,7 @@ import { setupLogger } from './logger.js'
20
17
 
21
18
  import { setupOTLP } from './otlp.js'
22
19
  import { ActionServer } from './action-server.js'
23
- import { ProcessorV3Definition } from '@sentio/protos'
20
+ import { Processor, ProcessorV3 } from '@sentio/protos'
24
21
  import { ProcessorServiceImplV3 } from './service-v3.js'
25
22
  import { dirname, join } from 'path'
26
23
  import { program, ProcessorRuntimeOptions } from './processor-runner-program.js'
@@ -69,6 +66,7 @@ if (!isChildProcess) {
69
66
  }
70
67
 
71
68
  let server: any
69
+ let processorHttp2Server: http2.Http2Server | undefined
72
70
  let baseService: ProcessorServiceImpl
73
71
  let httpServer: http.Server | undefined
74
72
 
@@ -82,28 +80,30 @@ if (options.startActionServer) {
82
80
  server = new ActionServer(loader)
83
81
  server.listen(options.port)
84
82
  } else {
85
- server = createServer({
86
- 'grpc.max_send_message_length': 768 * 1024 * 1024,
87
- 'grpc.max_receive_message_length': 768 * 1024 * 1024
88
- // 'grpc.default_compression_algorithm': compressionAlgorithms.gzip
89
- })
90
- // .use(prometheusServerMiddleware())
91
- // .use(openTelemetryServerMiddleware())
92
- .use(errorDetailsServerMiddleware)
83
+ const shutdown = () => processorHttp2Server?.close()
93
84
 
94
85
  // for V2
95
- baseService = new ProcessorServiceImpl(loader, options, server.shutdown)
96
- const service = new FullProcessorServiceImpl(baseService)
97
-
98
- server.add(ProcessorDefinition, service)
86
+ baseService = new ProcessorServiceImpl(loader, options, shutdown)
87
+ const serviceV2 = new FullProcessorServiceImpl(baseService)
99
88
 
100
89
  // for V3
101
- server.add(
102
- ProcessorV3Definition,
103
- new FullProcessorServiceV3Impl(new ProcessorServiceImplV3(loader, options, server.shutdown))
104
- )
90
+ const serviceV3 = new FullProcessorServiceV3Impl(new ProcessorServiceImplV3(loader, options, shutdown))
91
+
92
+ const routes = (router: ConnectRouter) => {
93
+ router.service(Processor, serviceV2)
94
+ router.service(ProcessorV3, serviceV3)
95
+ }
105
96
 
106
- server.listen('0.0.0.0:' + options.port)
97
+ const adapter = connectNodeAdapter({
98
+ routes,
99
+ readMaxBytes: 768 * 1024 * 1024,
100
+ writeMaxBytes: 768 * 1024 * 1024
101
+ })
102
+
103
+ // h2c (cleartext HTTP/2) gRPC server — connect-node serves grpc + grpc-web + connect
104
+ // on the same port, so the Go launcher's gRPC client is unaffected.
105
+ processorHttp2Server = http2.createServer(adapter)
106
+ processorHttp2Server.listen(Number(options.port))
107
107
  console.log('Processor Server Started at:', options.port)
108
108
  }
109
109
 
@@ -245,7 +245,11 @@ async function dumpHeap(file: string): Promise<void> {
245
245
  }
246
246
 
247
247
  function shutdownServers(exitCode: number): void {
248
- server.forceShutdown()
248
+ if (processorHttp2Server) {
249
+ processorHttp2Server.close()
250
+ } else if (server?.forceShutdown) {
251
+ server.forceShutdown()
252
+ }
249
253
  console.log('RPC server shut down')
250
254
 
251
255
  if (httpServer) {
package/src/service-v3.ts CHANGED
@@ -1,20 +1,18 @@
1
1
  import {
2
- DataBinding,
3
- DeepPartial,
4
- Empty,
2
+ type DataBinding,
3
+ EmptySchema,
5
4
  HandlerType,
6
- ProcessConfigRequest,
7
- ProcessConfigResponse,
8
- ProcessorV3ServiceImplementation,
9
- ProcessResult,
10
- ProcessStreamRequest,
11
- ProcessStreamResponse,
12
- ProcessStreamResponseV3,
13
- StartRequest,
14
- UpdateTemplatesRequest
5
+ type ProcessConfigRequest,
6
+ ProcessConfigResponseSchema,
7
+ ProcessorV3,
8
+ ProcessResultSchema,
9
+ type ProcessStreamRequest,
10
+ ProcessStreamResponseV3Schema,
11
+ type StartRequest,
12
+ type UpdateTemplatesRequest
15
13
  } from '@sentio/protos'
16
- import { CallContext, ServerError, Status } from 'nice-grpc'
17
- import { AsyncIterable } from 'ix'
14
+ import { clone, create, type MessageInitShape } from '@bufbuild/protobuf'
15
+ import { ConnectError, Code, type HandlerContext, type ServiceImpl } from '@connectrpc/connect'
18
16
  import { PluginManager } from './plugin.js'
19
17
  import { Subject } from 'rxjs'
20
18
  import { from } from 'ix/asynciterable'
@@ -27,12 +25,14 @@ import { DataBindingContext } from './db-context.js'
27
25
  import { freezeGlobalConfig } from './global-config.js'
28
26
  import { ProcessorRuntimeOptions } from './processor-runner-program.js'
29
27
 
28
+ type ProcessStreamResponseV3Init = MessageInitShape<typeof ProcessStreamResponseV3Schema>
29
+
30
30
  const { process_binding_count, process_binding_time, process_binding_error } = processMetrics
31
31
 
32
32
  const WRITE_V2_EVENT_LOGS = process.env.WRITE_V2_EVENT_LOGS !== 'false'
33
33
  const TIME_SERIES_RESULT_BATCH_SIZE = 1000
34
34
 
35
- export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation {
35
+ export class ProcessorServiceImplV3 implements ServiceImpl<typeof ProcessorV3> {
36
36
  readonly enablePartition: boolean
37
37
  private readonly loader: () => Promise<any>
38
38
  private readonly shutdownHandler?: () => void
@@ -45,9 +45,9 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
45
45
  this.enablePartition = options?.enablePartition == true
46
46
  }
47
47
 
48
- async start(request: StartRequest, context: CallContext): Promise<Empty> {
48
+ async start(request: StartRequest, context: HandlerContext) {
49
49
  if (this.started) {
50
- return {}
50
+ return create(EmptySchema)
51
51
  }
52
52
 
53
53
  freezeGlobalConfig()
@@ -55,27 +55,27 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
55
55
  try {
56
56
  await this.loader()
57
57
  } catch (e) {
58
- throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
58
+ throw new ConnectError('Failed to load processor: ' + errorString(e), Code.InvalidArgument)
59
59
  }
60
60
 
61
61
  await PluginManager.INSTANCE.start(request)
62
62
 
63
63
  this.started = true
64
- return {}
64
+ return create(EmptySchema)
65
65
  }
66
66
 
67
- async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
67
+ async getConfig(request: ProcessConfigRequest, context: HandlerContext) {
68
68
  if (!this.started) {
69
- throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
69
+ throw new ConnectError('Service Not started.', Code.Unavailable)
70
70
  }
71
71
 
72
- const newConfig = ProcessConfigResponse.fromPartial({})
72
+ const newConfig = create(ProcessConfigResponseSchema, {})
73
73
  await PluginManager.INSTANCE.configure(newConfig)
74
74
  return newConfig
75
75
  }
76
76
 
77
- async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: CallContext) {
78
- const subject = new Subject<DeepPartial<ProcessStreamResponseV3>>()
77
+ async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: HandlerContext) {
78
+ const subject = new Subject<ProcessStreamResponseV3Init>()
79
79
  this.handleRequests(requests, subject)
80
80
  .then(() => {
81
81
  subject.complete()
@@ -89,14 +89,14 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
89
89
 
90
90
  protected async handleRequests(
91
91
  requests: AsyncIterable<ProcessStreamRequest>,
92
- subject: Subject<DeepPartial<ProcessStreamResponse>>
92
+ subject: Subject<ProcessStreamResponseV3Init>
93
93
  ) {
94
94
  let lastBinding: DataBinding | undefined = undefined
95
95
  for await (const request of requests) {
96
96
  try {
97
97
  // console.log('received request:', request, 'lastBinding:', lastBinding)
98
- if (request.binding) {
99
- lastBinding = request.binding
98
+ if (request.value.case === 'binding') {
99
+ lastBinding = request.value.value
100
100
  }
101
101
  this.handleRequest(request, lastBinding, subject)
102
102
  } catch (e) {
@@ -111,26 +111,27 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
111
111
  async handleRequest(
112
112
  request: ProcessStreamRequest,
113
113
  lastBinding: DataBinding | undefined,
114
- subject: Subject<DeepPartial<ProcessStreamResponseV3>>
114
+ subject: Subject<ProcessStreamResponseV3Init>
115
115
  ) {
116
- if (request.binding) {
116
+ const binding = request.value.case === 'binding' ? request.value.value : undefined
117
+ if (binding) {
117
118
  process_binding_count.add(1)
118
119
 
119
- if (request.binding.handlerType === HandlerType.UNKNOWN) {
120
+ if (binding.handlerType === HandlerType.UNKNOWN) {
120
121
  subject.next({
121
122
  processId: request.processId,
122
- result: ProcessResult.create()
123
+ value: { case: 'result', value: create(ProcessResultSchema) }
123
124
  })
124
125
  return
125
126
  }
126
127
 
127
128
  if (this.enablePartition) {
128
129
  try {
129
- console.debug('sending partition request', request.binding)
130
- const partitions = await PluginManager.INSTANCE.partition(request.binding)
130
+ console.debug('sending partition request', binding)
131
+ const partitions = await PluginManager.INSTANCE.partition(binding)
131
132
  subject.next({
132
133
  processId: request.processId,
133
- partitions
134
+ value: { case: 'partitions', value: partitions }
134
135
  })
135
136
  } catch (e) {
136
137
  console.error('Partition error:', e)
@@ -138,11 +139,11 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
138
139
  return
139
140
  }
140
141
  } else {
141
- this.startProcess(request.processId, request.binding, subject)
142
+ this.startProcess(request.processId, binding, subject)
142
143
  }
143
144
  }
144
145
 
145
- if (request.start) {
146
+ if (request.value.case === 'start') {
146
147
  if (!lastBinding) {
147
148
  console.error('start request received without binding')
148
149
  subject.error(new Error('start request received without binding'))
@@ -151,44 +152,41 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
151
152
  this.startProcess(request.processId, lastBinding, subject)
152
153
  }
153
154
 
154
- if (request.dbResult) {
155
+ if (request.value.case === 'dbResult') {
155
156
  const context = this.contexts.get(request.processId)
156
157
  try {
157
- context?.result(request.dbResult)
158
+ context?.result(request.value.value)
158
159
  } catch (e) {
159
160
  subject.error(new Error('db result error, process should stop'))
160
161
  }
161
162
  }
162
163
  }
163
164
 
164
- private startProcess(
165
- processId: number,
166
- binding: DataBinding,
167
- subject: Subject<DeepPartial<ProcessStreamResponseV3>>
168
- ) {
165
+ private startProcess(processId: number, binding: DataBinding, subject: Subject<ProcessStreamResponseV3Init>) {
169
166
  const context = this.contexts.new(processId, subject)
170
167
  const start = Date.now()
171
168
  PluginManager.INSTANCE.processBinding(binding, undefined, context)
172
169
  .then(async (result) => {
173
170
  await context.awaitPendings()
174
- const { timeseriesResult, ...otherResults } = result
171
+ const timeseriesResult = result.timeseriesResult
175
172
  for (let i = 0; i < timeseriesResult.length; i += TIME_SERIES_RESULT_BATCH_SIZE) {
176
173
  const batch = timeseriesResult.slice(i, i + TIME_SERIES_RESULT_BATCH_SIZE)
177
174
  subject.next({
178
175
  processId,
179
- tsRequest: {
180
- data: batch
181
- }
176
+ value: { case: 'tsRequest', value: { data: batch } }
182
177
  })
183
178
  }
184
179
 
180
+ // Send everything except the (already-batched) timeseries result back.
181
+ const otherResults = clone(ProcessResultSchema, result)
182
+ otherResults.timeseriesResult = []
183
+
185
184
  subject.next({
186
- result: WRITE_V2_EVENT_LOGS
187
- ? otherResults
188
- : {
189
- states: otherResults.states
190
- },
191
- processId: processId
185
+ processId,
186
+ value: {
187
+ case: 'result',
188
+ value: WRITE_V2_EVENT_LOGS ? otherResults : create(ProcessResultSchema, { states: result.states })
189
+ }
192
190
  })
193
191
  recordRuntimeInfo(result, binding.handlerType)
194
192
  })
@@ -204,9 +202,9 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
204
202
  })
205
203
  }
206
204
 
207
- async updateTemplates(request: UpdateTemplatesRequest, context: CallContext): Promise<DeepPartial<Empty>> {
205
+ async updateTemplates(request: UpdateTemplatesRequest, context: HandlerContext) {
208
206
  await PluginManager.INSTANCE.updateTemplates(request)
209
- return {}
207
+ return create(EmptySchema)
210
208
  }
211
209
  }
212
210
 
@@ -217,7 +215,7 @@ class Contexts {
217
215
  return this.contexts.get(processId)
218
216
  }
219
217
 
220
- new(processId: number, subject: Subject<DeepPartial<ProcessStreamResponseV3>>) {
218
+ new(processId: number, subject: Subject<ProcessStreamResponseV3Init>) {
221
219
  const context = new DataBindingContext(processId, subject)
222
220
  this.contexts.set(processId, context)
223
221
  return context