@sentio/runtime 2.59.4 → 2.60.0-rc.1

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.
@@ -313,6 +313,17 @@ export interface UsageTracker {
313
313
  versionField: string;
314
314
  }
315
315
 
316
+ export interface Auth {
317
+ permission: string[];
318
+ metadata: { [key: string]: string };
319
+ allowAnonymous: boolean;
320
+ }
321
+
322
+ export interface Auth_MetadataEntry {
323
+ key: string;
324
+ value: string;
325
+ }
326
+
316
327
  export interface AccessMeta {
317
328
  projectIdField: string;
318
329
  projectSlugField: string;
@@ -2183,6 +2194,190 @@ export const UsageTracker = {
2183
2194
  },
2184
2195
  };
2185
2196
 
2197
+ function createBaseAuth(): Auth {
2198
+ return { permission: [], metadata: {}, allowAnonymous: false };
2199
+ }
2200
+
2201
+ export const Auth = {
2202
+ encode(message: Auth, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
2203
+ for (const v of message.permission) {
2204
+ writer.uint32(10).string(v!);
2205
+ }
2206
+ Object.entries(message.metadata).forEach(([key, value]) => {
2207
+ Auth_MetadataEntry.encode({ key: key as any, value }, writer.uint32(18).fork()).ldelim();
2208
+ });
2209
+ if (message.allowAnonymous !== false) {
2210
+ writer.uint32(24).bool(message.allowAnonymous);
2211
+ }
2212
+ return writer;
2213
+ },
2214
+
2215
+ decode(input: _m0.Reader | Uint8Array, length?: number): Auth {
2216
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
2217
+ let end = length === undefined ? reader.len : reader.pos + length;
2218
+ const message = createBaseAuth();
2219
+ while (reader.pos < end) {
2220
+ const tag = reader.uint32();
2221
+ switch (tag >>> 3) {
2222
+ case 1:
2223
+ if (tag !== 10) {
2224
+ break;
2225
+ }
2226
+
2227
+ message.permission.push(reader.string());
2228
+ continue;
2229
+ case 2:
2230
+ if (tag !== 18) {
2231
+ break;
2232
+ }
2233
+
2234
+ const entry2 = Auth_MetadataEntry.decode(reader, reader.uint32());
2235
+ if (entry2.value !== undefined) {
2236
+ message.metadata[entry2.key] = entry2.value;
2237
+ }
2238
+ continue;
2239
+ case 3:
2240
+ if (tag !== 24) {
2241
+ break;
2242
+ }
2243
+
2244
+ message.allowAnonymous = reader.bool();
2245
+ continue;
2246
+ }
2247
+ if ((tag & 7) === 4 || tag === 0) {
2248
+ break;
2249
+ }
2250
+ reader.skipType(tag & 7);
2251
+ }
2252
+ return message;
2253
+ },
2254
+
2255
+ fromJSON(object: any): Auth {
2256
+ return {
2257
+ permission: globalThis.Array.isArray(object?.permission)
2258
+ ? object.permission.map((e: any) => globalThis.String(e))
2259
+ : [],
2260
+ metadata: isObject(object.metadata)
2261
+ ? Object.entries(object.metadata).reduce<{ [key: string]: string }>((acc, [key, value]) => {
2262
+ acc[key] = String(value);
2263
+ return acc;
2264
+ }, {})
2265
+ : {},
2266
+ allowAnonymous: isSet(object.allowAnonymous) ? globalThis.Boolean(object.allowAnonymous) : false,
2267
+ };
2268
+ },
2269
+
2270
+ toJSON(message: Auth): unknown {
2271
+ const obj: any = {};
2272
+ if (message.permission?.length) {
2273
+ obj.permission = message.permission;
2274
+ }
2275
+ if (message.metadata) {
2276
+ const entries = Object.entries(message.metadata);
2277
+ if (entries.length > 0) {
2278
+ obj.metadata = {};
2279
+ entries.forEach(([k, v]) => {
2280
+ obj.metadata[k] = v;
2281
+ });
2282
+ }
2283
+ }
2284
+ if (message.allowAnonymous !== false) {
2285
+ obj.allowAnonymous = message.allowAnonymous;
2286
+ }
2287
+ return obj;
2288
+ },
2289
+
2290
+ create(base?: DeepPartial<Auth>): Auth {
2291
+ return Auth.fromPartial(base ?? {});
2292
+ },
2293
+ fromPartial(object: DeepPartial<Auth>): Auth {
2294
+ const message = createBaseAuth();
2295
+ message.permission = object.permission?.map((e) => e) || [];
2296
+ message.metadata = Object.entries(object.metadata ?? {}).reduce<{ [key: string]: string }>((acc, [key, value]) => {
2297
+ if (value !== undefined) {
2298
+ acc[key] = globalThis.String(value);
2299
+ }
2300
+ return acc;
2301
+ }, {});
2302
+ message.allowAnonymous = object.allowAnonymous ?? false;
2303
+ return message;
2304
+ },
2305
+ };
2306
+
2307
+ function createBaseAuth_MetadataEntry(): Auth_MetadataEntry {
2308
+ return { key: "", value: "" };
2309
+ }
2310
+
2311
+ export const Auth_MetadataEntry = {
2312
+ encode(message: Auth_MetadataEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
2313
+ if (message.key !== "") {
2314
+ writer.uint32(10).string(message.key);
2315
+ }
2316
+ if (message.value !== "") {
2317
+ writer.uint32(18).string(message.value);
2318
+ }
2319
+ return writer;
2320
+ },
2321
+
2322
+ decode(input: _m0.Reader | Uint8Array, length?: number): Auth_MetadataEntry {
2323
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
2324
+ let end = length === undefined ? reader.len : reader.pos + length;
2325
+ const message = createBaseAuth_MetadataEntry();
2326
+ while (reader.pos < end) {
2327
+ const tag = reader.uint32();
2328
+ switch (tag >>> 3) {
2329
+ case 1:
2330
+ if (tag !== 10) {
2331
+ break;
2332
+ }
2333
+
2334
+ message.key = reader.string();
2335
+ continue;
2336
+ case 2:
2337
+ if (tag !== 18) {
2338
+ break;
2339
+ }
2340
+
2341
+ message.value = reader.string();
2342
+ continue;
2343
+ }
2344
+ if ((tag & 7) === 4 || tag === 0) {
2345
+ break;
2346
+ }
2347
+ reader.skipType(tag & 7);
2348
+ }
2349
+ return message;
2350
+ },
2351
+
2352
+ fromJSON(object: any): Auth_MetadataEntry {
2353
+ return {
2354
+ key: isSet(object.key) ? globalThis.String(object.key) : "",
2355
+ value: isSet(object.value) ? globalThis.String(object.value) : "",
2356
+ };
2357
+ },
2358
+
2359
+ toJSON(message: Auth_MetadataEntry): unknown {
2360
+ const obj: any = {};
2361
+ if (message.key !== "") {
2362
+ obj.key = message.key;
2363
+ }
2364
+ if (message.value !== "") {
2365
+ obj.value = message.value;
2366
+ }
2367
+ return obj;
2368
+ },
2369
+
2370
+ create(base?: DeepPartial<Auth_MetadataEntry>): Auth_MetadataEntry {
2371
+ return Auth_MetadataEntry.fromPartial(base ?? {});
2372
+ },
2373
+ fromPartial(object: DeepPartial<Auth_MetadataEntry>): Auth_MetadataEntry {
2374
+ const message = createBaseAuth_MetadataEntry();
2375
+ message.key = object.key ?? "";
2376
+ message.value = object.value ?? "";
2377
+ return message;
2378
+ },
2379
+ };
2380
+
2186
2381
  function createBaseAccessMeta(): AccessMeta {
2187
2382
  return {
2188
2383
  projectIdField: "",
package/src/plugin.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  DataBinding,
3
3
  HandlerType,
4
+ InitResponse,
4
5
  PreparedData,
5
6
  PreprocessResult,
6
7
  ProcessConfigResponse,
@@ -16,7 +17,7 @@ export abstract class Plugin {
16
17
  name: string
17
18
  supportedHandlers: HandlerType[] = []
18
19
 
19
- async configure(config: ProcessConfigResponse): Promise<void> {}
20
+ async configure(config: ProcessConfigResponse, forChainId?: string): Promise<void> {}
20
21
 
21
22
  async start(start: StartRequest): Promise<void> {}
22
23
 
@@ -59,6 +60,12 @@ export abstract class Plugin {
59
60
  * method used by action server only
60
61
  */
61
62
  shutdownServer() {}
63
+
64
+ /**
65
+ * Initialize the plugin, for service v2.
66
+ * @param config
67
+ */
68
+ async init(config: InitResponse): Promise<void> {}
62
69
  }
63
70
 
64
71
  export class PluginManager {
@@ -83,8 +90,8 @@ export class PluginManager {
83
90
  }
84
91
  }
85
92
 
86
- configure(config: ProcessConfigResponse) {
87
- return Promise.all(this.plugins.map((plugin) => plugin.configure(config)))
93
+ async configure(config: ProcessConfigResponse, forChainId?: string): Promise<void> {
94
+ await Promise.all(this.plugins.map((plugin) => plugin.configure(config, forChainId)))
88
95
  }
89
96
 
90
97
  start(start: StartRequest, actionServerPort?: number) {
@@ -141,4 +148,8 @@ export class PluginManager {
141
148
  return plugin.preprocessBinding(request, preprocessStore)
142
149
  })
143
150
  }
151
+
152
+ async init(resp: InitResponse) {
153
+ return Promise.all(this.plugins.map((plugin) => plugin.init(resp)))
154
+ }
144
155
  }
@@ -0,0 +1,207 @@
1
+ import {
2
+ ConfigureHandlersRequest,
3
+ DataBinding,
4
+ DeepPartial,
5
+ Empty,
6
+ HandlerType,
7
+ InitResponse,
8
+ ProcessConfigResponse,
9
+ ProcessorV2ServiceImplementation,
10
+ ProcessResult,
11
+ ProcessStreamRequest,
12
+ ProcessStreamResponseV2,
13
+ StartRequest
14
+ } from '@sentio/protos'
15
+ import { CallContext } from 'nice-grpc'
16
+ import { AsyncIterable } from 'ix'
17
+ import { PluginManager } from './plugin.js'
18
+ import { Subject } from 'rxjs'
19
+ import { from } from 'ix/asynciterable'
20
+ import { withAbort } from 'ix/asynciterable/operators'
21
+ import { errorString } from './utils.js'
22
+
23
+ import { processMetrics } from './metrics.js'
24
+ import { recordRuntimeInfo } from './service.js'
25
+ import { DataBindingContext } from './db-context.js'
26
+ import { TemplateInstanceState } from './state.js'
27
+
28
+ const { process_binding_count, process_binding_time, process_binding_error } = processMetrics
29
+
30
+ export class ProcessorServiceImplV2 implements ProcessorV2ServiceImplementation {
31
+ readonly enablePartition: boolean
32
+ private readonly loader: () => Promise<any>
33
+ private readonly shutdownHandler?: () => void
34
+
35
+ constructor(loader: () => Promise<any>, options?: any, shutdownHandler?: () => void) {
36
+ this.loader = loader
37
+ this.shutdownHandler = shutdownHandler
38
+
39
+ this.enablePartition = options?.['enable-partition'] == true
40
+ }
41
+
42
+ async init(request: Empty, context: CallContext): Promise<DeepPartial<InitResponse>> {
43
+ const resp = InitResponse.fromPartial({
44
+ chainIds: []
45
+ })
46
+ await PluginManager.INSTANCE.init(resp)
47
+ resp.chainIds = Array.from(new Set(resp.chainIds))
48
+ return resp
49
+ }
50
+
51
+ async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: CallContext) {
52
+ const subject = new Subject<DeepPartial<ProcessStreamResponseV2>>()
53
+ let lastBinding: DataBinding | undefined = undefined
54
+ for await (const request of requests) {
55
+ try {
56
+ // console.log('received request:', request, 'lastBinding:', lastBinding)
57
+ if (request.binding) {
58
+ lastBinding = request.binding
59
+ }
60
+ this.handleRequest(request, lastBinding, subject)
61
+ } catch (e) {
62
+ // should not happen
63
+ console.error('unexpect error during handle loop', e)
64
+ }
65
+ }
66
+ yield* from(subject).pipe(withAbort(context.signal))
67
+ }
68
+ private contexts = new Contexts()
69
+
70
+ async handleRequest(
71
+ request: ProcessStreamRequest,
72
+ lastBinding: DataBinding | undefined,
73
+ subject: Subject<DeepPartial<ProcessStreamResponseV2>>
74
+ ) {
75
+ if (request.binding) {
76
+ process_binding_count.add(1)
77
+
78
+ if (request.binding.handlerType === HandlerType.UNKNOWN) {
79
+ subject.next({
80
+ processId: request.processId,
81
+ result: ProcessResult.create()
82
+ })
83
+ return
84
+ }
85
+
86
+ if (this.enablePartition) {
87
+ try {
88
+ const partitions = await PluginManager.INSTANCE.partition(request.binding)
89
+ subject.next({
90
+ processId: request.processId,
91
+ partitions
92
+ })
93
+ } catch (e) {
94
+ console.error('Partition error:', e)
95
+ subject.error(new Error('Partition error: ' + errorString(e)))
96
+ return
97
+ }
98
+ } else {
99
+ this.startProcess(request.processId, request.binding, subject)
100
+ }
101
+ }
102
+
103
+ if (request.start) {
104
+ if (!lastBinding) {
105
+ console.error('start request received without binding')
106
+ subject.error(new Error('start request received without binding'))
107
+ return
108
+ }
109
+ this.startProcess(request.processId, lastBinding, subject)
110
+ }
111
+
112
+ if (request.dbResult) {
113
+ const context = this.contexts.get(request.processId)
114
+ try {
115
+ context?.result(request.dbResult)
116
+ } catch (e) {
117
+ subject.error(new Error('db result error, process should stop'))
118
+ }
119
+ }
120
+ }
121
+
122
+ private startProcess(
123
+ processId: number,
124
+ binding: DataBinding,
125
+ subject: Subject<DeepPartial<ProcessStreamResponseV2>>
126
+ ) {
127
+ const context = this.contexts.new(processId, subject)
128
+ const start = Date.now()
129
+ PluginManager.INSTANCE.processBinding(binding, undefined, context)
130
+ .then(async (result) => {
131
+ // await all pending db requests
132
+ await context.awaitPendings()
133
+
134
+ for (const ts of result.timeseriesResult) {
135
+ subject.next({
136
+ processId,
137
+ tsRequest: {
138
+ data: [ts]
139
+ }
140
+ })
141
+ }
142
+
143
+ if (result.states?.configUpdated) {
144
+ subject.next({
145
+ processId,
146
+ tplRequest: {
147
+ templates: TemplateInstanceState.INSTANCE.getValues()
148
+ }
149
+ })
150
+ }
151
+
152
+ subject.next({
153
+ result: {
154
+ states: result.states,
155
+ exports: result.exports
156
+ },
157
+ processId: processId
158
+ })
159
+ recordRuntimeInfo(result, binding.handlerType)
160
+ })
161
+ .catch((e) => {
162
+ console.error(e, e.stack)
163
+ context.error(processId, e)
164
+ process_binding_error.add(1)
165
+ })
166
+ .finally(() => {
167
+ const cost = Date.now() - start
168
+ process_binding_time.add(cost)
169
+ this.contexts.delete(processId)
170
+ })
171
+ }
172
+
173
+ async configureHandlers(
174
+ request: ConfigureHandlersRequest,
175
+ context: CallContext
176
+ ): Promise<DeepPartial<ProcessConfigResponse>> {
177
+ await PluginManager.INSTANCE.start(
178
+ StartRequest.fromPartial({
179
+ templateInstances: request.templateInstances
180
+ })
181
+ )
182
+
183
+ const newConfig = ProcessConfigResponse.fromPartial({})
184
+ await PluginManager.INSTANCE.configure(newConfig, request.chainId)
185
+ return newConfig
186
+ }
187
+ }
188
+
189
+ class Contexts {
190
+ private contexts: Map<number, DataBindingContext> = new Map()
191
+
192
+ get(processId: number) {
193
+ return this.contexts.get(processId)
194
+ }
195
+
196
+ new(processId: number, subject: Subject<DeepPartial<ProcessStreamResponseV2>>) {
197
+ const context = new DataBindingContext(processId, subject)
198
+ this.contexts.set(processId, context)
199
+ return context
200
+ }
201
+
202
+ delete(processId: number) {
203
+ const context = this.get(processId)
204
+ context?.close()
205
+ this.contexts.delete(processId)
206
+ }
207
+ }