envio 3.14.1 → 3.15.0-sidecars.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/evm.schema.json +96 -0
- package/fuel.schema.json +96 -0
- package/index.d.ts +129 -0
- package/index.js +2 -0
- package/package.json +24 -10
- package/sidecar.d.ts +1 -0
- package/sidecar.js +1 -0
- package/src/Batch.res +78 -7
- package/src/Batch.res.mjs +52 -13
- package/src/Bin.res +12 -0
- package/src/Bin.res.mjs +12 -0
- package/src/ChainState.res +32 -4
- package/src/ChainState.res.mjs +68 -45
- package/src/ChainState.resi +4 -0
- package/src/Config.res +45 -0
- package/src/Config.res.mjs +32 -0
- package/src/Core.res +46 -1
- package/src/Ecosystem.res +10 -0
- package/src/Ecosystem.res.mjs +21 -9
- package/src/Env.res +22 -1
- package/src/Env.res.mjs +27 -2
- package/src/EventProcessing.res +56 -0
- package/src/EventProcessing.res.mjs +135 -76
- package/src/ExitOnCaughtUp.res +8 -0
- package/src/ExitOnCaughtUp.res.mjs +12 -7
- package/src/FetchState.res +2 -0
- package/src/FetchState.res.mjs +16 -5
- package/src/HandlerLoader.res +2 -0
- package/src/HandlerLoader.res.mjs +2 -0
- package/src/HandlerRegister.res +48 -1
- package/src/HandlerRegister.res.mjs +40 -1
- package/src/HandlerRegister.resi +4 -0
- package/src/Hasura.res +52 -0
- package/src/Hasura.res.mjs +40 -2
- package/src/IndexerState.res +109 -1
- package/src/IndexerState.res.mjs +111 -7
- package/src/IndexerState.resi +6 -0
- package/src/Internal.res +51 -0
- package/src/Internal.res.mjs +12 -4
- package/src/Main.res +51 -0
- package/src/Main.res.mjs +34 -4
- package/src/Metrics.res +186 -0
- package/src/Metrics.res.mjs +153 -1
- package/src/Persistence.res +10 -13
- package/src/Persistence.res.mjs +4 -5
- package/src/PgStorage.res +906 -2160
- package/src/PgStorage.res.mjs +669 -1274
- package/src/PgWriting.res +36 -0
- package/src/PgWriting.res.mjs +34 -0
- package/src/PruneStaleHistory.res +1 -6
- package/src/PruneStaleHistory.res.mjs +1 -2
- package/src/Reading.res +238 -0
- package/src/Reading.res.mjs +197 -0
- package/src/Server.res +34 -2
- package/src/Server.res.mjs +28 -2
- package/src/SimulateDeadInputTracker.res +2 -3
- package/src/SimulateDeadInputTracker.res.mjs +6 -4
- package/src/Staging.res +43 -22
- package/src/Staging.res.mjs +17 -11
- package/src/Supervisor.res +46 -8
- package/src/Supervisor.res.mjs +48 -11
- package/src/TestIndexer.res +63 -6
- package/src/TestIndexer.res.mjs +45 -6
- package/src/UserContext.res +152 -0
- package/src/UserContext.res.mjs +138 -0
- package/src/Utils.res +13 -46
- package/src/Utils.res.mjs +6 -27
- package/src/Worker.res +8 -1
- package/src/Worker.res.mjs +8 -2
- package/src/Writing.res +25 -0
- package/src/Writing.res.mjs +6 -1
- package/src/bindings/ClickHouseSink.res +1 -1
- package/src/bindings/ClickHouseSink.res.mjs +1 -1
- package/src/bindings/Express.res +9 -0
- package/src/bindings/NodeJs.res +6 -0
- package/src/bindings/PgClient.res +360 -0
- package/src/bindings/PgClient.res.mjs +53 -0
- package/src/db/CheckpointSequence.res +0 -44
- package/src/db/CheckpointSequence.res.mjs +0 -37
- package/src/db/EntityFilter.res +3 -2
- package/src/db/EntityFilter.res.mjs +10 -2
- package/src/db/EntityHistory.res +0 -181
- package/src/db/EntityHistory.res.mjs +1 -100
- package/src/db/IndexDefinition.res +21 -105
- package/src/db/IndexDefinition.res.mjs +40 -111
- package/src/db/InternalTable.res +5 -820
- package/src/db/InternalTable.res.mjs +8 -649
- package/src/db/LoadCondition.res +31 -0
- package/src/db/LoadCondition.res.mjs +2 -0
- package/src/db/LoadFilter.res +127 -0
- package/src/db/LoadFilter.res.mjs +177 -0
- package/src/db/Sql.res +103 -0
- package/src/db/Sql.res.mjs +111 -0
- package/src/db/Table.res +52 -165
- package/src/db/Table.res.mjs +147 -131
- package/src/sidecar/ChildOutput.res +19 -0
- package/src/sidecar/ChildOutput.res.mjs +28 -0
- package/src/sidecar/ResolverActions.res +27 -0
- package/src/sidecar/ResolverActions.res.mjs +17 -0
- package/src/sidecar/ServeProcess.res +162 -0
- package/src/sidecar/ServeProcess.res.mjs +223 -0
- package/src/sidecar/SidecarCall.res +13 -0
- package/src/sidecar/SidecarCall.res.mjs +2 -0
- package/src/sidecar/SidecarContract.res +77 -0
- package/src/sidecar/SidecarContract.res.mjs +85 -0
- package/src/sidecar/SidecarProcess.res +220 -0
- package/src/sidecar/SidecarProcess.res.mjs +266 -0
- package/src/sidecar/SidecarSender.res +590 -0
- package/src/sidecar/SidecarSender.res.mjs +666 -0
- package/src/sidecar/WebhookListener.res +265 -0
- package/src/sidecar/WebhookListener.res.mjs +288 -0
- package/src/sidecar/client.d.ts +107 -0
- package/src/sidecar/client.js +443 -0
- package/src/sidecar/contract.d.ts +67 -0
- package/src/sidecar/contract.js +62 -0
- package/src/sidecar/emit.d.ts +105 -0
- package/src/sidecar/emit.js +284 -0
- package/src/sidecar/env.d.ts +75 -0
- package/src/sidecar/env.js +120 -0
- package/src/sidecar/index.d.ts +8 -0
- package/src/sidecar/index.js +8 -0
- package/src/sidecar/log.d.ts +23 -0
- package/src/sidecar/log.js +50 -0
- package/src/sidecar/metrics.d.ts +16 -0
- package/src/sidecar/metrics.js +80 -0
- package/src/sidecar/resolvers.d.ts +104 -0
- package/src/sidecar/resolvers.js +111 -0
- package/src/sidecar/runtime.d.ts +168 -0
- package/src/sidecar/runtime.js +727 -0
- package/src/sidecar/sql.d.ts +60 -0
- package/src/sidecar/sql.js +263 -0
- package/src/tui/Tui.res +14 -4
- package/src/tui/Tui.res.mjs +18 -7
- package/svm.schema.json +96 -0
- package/src/bindings/Postgres.res +0 -131
- package/src/bindings/Postgres.res.mjs +0 -20
- package/src/db/IndexCatalog.res +0 -248
- package/src/db/IndexCatalog.res.mjs +0 -222
- package/src/db/IndexManager.res +0 -145
- package/src/db/IndexManager.res.mjs +0 -130
package/evm.schema.json
CHANGED
|
@@ -56,6 +56,40 @@
|
|
|
56
56
|
"null"
|
|
57
57
|
]
|
|
58
58
|
},
|
|
59
|
+
"sidecar": {
|
|
60
|
+
"description": "A sidecar: your own long-running TypeScript process, run next to the indexer by `envio sidecar` (and started by `envio dev`). Handlers call its functions through `context.sidecar.<name>(input)` without waiting.",
|
|
61
|
+
"anyOf": [
|
|
62
|
+
{
|
|
63
|
+
"$ref": "#/$defs/SidecarConfig"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "null"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"resolvers": {
|
|
71
|
+
"description": "Extra root query fields in the GraphQL API, answered by your sidecar or a webhook. Each one is published as a Hasura action, so Hasura and envio serve call the handler with Hasura's action payload.",
|
|
72
|
+
"type": [
|
|
73
|
+
"object",
|
|
74
|
+
"null"
|
|
75
|
+
],
|
|
76
|
+
"additionalProperties": {
|
|
77
|
+
"$ref": "#/$defs/ResolverConfig"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"types": {
|
|
81
|
+
"description": "Output object types for `resolvers:`: type name to a map of field name to GraphQL type string (for example `address: \"String!\"`).",
|
|
82
|
+
"type": [
|
|
83
|
+
"object",
|
|
84
|
+
"null"
|
|
85
|
+
],
|
|
86
|
+
"additionalProperties": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": {
|
|
89
|
+
"type": "string"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
59
93
|
"ecosystem": {
|
|
60
94
|
"description": "Ecosystem of the project.",
|
|
61
95
|
"anyOf": [
|
|
@@ -273,6 +307,68 @@
|
|
|
273
307
|
}
|
|
274
308
|
]
|
|
275
309
|
},
|
|
310
|
+
"SidecarConfig": {
|
|
311
|
+
"type": "object",
|
|
312
|
+
"properties": {
|
|
313
|
+
"contract": {
|
|
314
|
+
"description": "Relative path to the contract module: `export default defineSidecar({ functions, webhooks })`. Schemas only, since both the indexer and the sidecar load it. Types `context.sidecar`.",
|
|
315
|
+
"type": "string"
|
|
316
|
+
},
|
|
317
|
+
"entry": {
|
|
318
|
+
"description": "Relative path to the sidecar entry module, which default-exports the `createSidecar(contract, {...})` result. Keep it outside the handlers directory, which the indexer auto-loads.",
|
|
319
|
+
"type": "string"
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
"additionalProperties": false,
|
|
323
|
+
"required": [
|
|
324
|
+
"contract",
|
|
325
|
+
"entry"
|
|
326
|
+
]
|
|
327
|
+
},
|
|
328
|
+
"ResolverConfig": {
|
|
329
|
+
"type": "object",
|
|
330
|
+
"properties": {
|
|
331
|
+
"description": {
|
|
332
|
+
"description": "Shown as the field's description in the GraphQL schema.",
|
|
333
|
+
"type": [
|
|
334
|
+
"string",
|
|
335
|
+
"null"
|
|
336
|
+
]
|
|
337
|
+
},
|
|
338
|
+
"args": {
|
|
339
|
+
"description": "Argument name to GraphQL type string, for example `limit: \"Int!\"`. Scalars or lists of scalars.",
|
|
340
|
+
"type": [
|
|
341
|
+
"object",
|
|
342
|
+
"null"
|
|
343
|
+
],
|
|
344
|
+
"additionalProperties": {
|
|
345
|
+
"type": "string"
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
"output": {
|
|
349
|
+
"description": "GraphQL type string of the result, for example `\"[TopTrader!]!\"`. Scalars or types declared under `types:`.",
|
|
350
|
+
"type": "string"
|
|
351
|
+
},
|
|
352
|
+
"handler": {
|
|
353
|
+
"description": "`sidecar` (answered by the `resolvers` of your `createSidecar`) or an http(s):// URL that accepts Hasura's action payload.",
|
|
354
|
+
"type": "string"
|
|
355
|
+
},
|
|
356
|
+
"timeoutMs": {
|
|
357
|
+
"description": "How long the GraphQL layer waits for the handler. (default: 30000)",
|
|
358
|
+
"type": [
|
|
359
|
+
"integer",
|
|
360
|
+
"null"
|
|
361
|
+
],
|
|
362
|
+
"format": "uint64",
|
|
363
|
+
"minimum": 0
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
"additionalProperties": false,
|
|
367
|
+
"required": [
|
|
368
|
+
"output",
|
|
369
|
+
"handler"
|
|
370
|
+
]
|
|
371
|
+
},
|
|
276
372
|
"EcosystemTag": {
|
|
277
373
|
"type": "string",
|
|
278
374
|
"enum": [
|
package/fuel.schema.json
CHANGED
|
@@ -56,6 +56,40 @@
|
|
|
56
56
|
"null"
|
|
57
57
|
]
|
|
58
58
|
},
|
|
59
|
+
"sidecar": {
|
|
60
|
+
"description": "A sidecar: your own long-running TypeScript process, run next to the indexer by `envio sidecar` (and started by `envio dev`). Handlers call its functions through `context.sidecar.<name>(input)` without waiting.",
|
|
61
|
+
"anyOf": [
|
|
62
|
+
{
|
|
63
|
+
"$ref": "#/$defs/SidecarConfig"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "null"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"resolvers": {
|
|
71
|
+
"description": "Extra root query fields in the GraphQL API, answered by your sidecar or a webhook. Each one is published as a Hasura action, so Hasura and envio serve call the handler with Hasura's action payload.",
|
|
72
|
+
"type": [
|
|
73
|
+
"object",
|
|
74
|
+
"null"
|
|
75
|
+
],
|
|
76
|
+
"additionalProperties": {
|
|
77
|
+
"$ref": "#/$defs/ResolverConfig"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"types": {
|
|
81
|
+
"description": "Output object types for `resolvers:`: type name to a map of field name to GraphQL type string (for example `address: \"String!\"`).",
|
|
82
|
+
"type": [
|
|
83
|
+
"object",
|
|
84
|
+
"null"
|
|
85
|
+
],
|
|
86
|
+
"additionalProperties": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": {
|
|
89
|
+
"type": "string"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
59
93
|
"ecosystem": {
|
|
60
94
|
"description": "Ecosystem of the project.",
|
|
61
95
|
"$ref": "#/$defs/EcosystemTag"
|
|
@@ -231,6 +265,68 @@
|
|
|
231
265
|
}
|
|
232
266
|
]
|
|
233
267
|
},
|
|
268
|
+
"SidecarConfig": {
|
|
269
|
+
"type": "object",
|
|
270
|
+
"properties": {
|
|
271
|
+
"contract": {
|
|
272
|
+
"description": "Relative path to the contract module: `export default defineSidecar({ functions, webhooks })`. Schemas only, since both the indexer and the sidecar load it. Types `context.sidecar`.",
|
|
273
|
+
"type": "string"
|
|
274
|
+
},
|
|
275
|
+
"entry": {
|
|
276
|
+
"description": "Relative path to the sidecar entry module, which default-exports the `createSidecar(contract, {...})` result. Keep it outside the handlers directory, which the indexer auto-loads.",
|
|
277
|
+
"type": "string"
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
"additionalProperties": false,
|
|
281
|
+
"required": [
|
|
282
|
+
"contract",
|
|
283
|
+
"entry"
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
"ResolverConfig": {
|
|
287
|
+
"type": "object",
|
|
288
|
+
"properties": {
|
|
289
|
+
"description": {
|
|
290
|
+
"description": "Shown as the field's description in the GraphQL schema.",
|
|
291
|
+
"type": [
|
|
292
|
+
"string",
|
|
293
|
+
"null"
|
|
294
|
+
]
|
|
295
|
+
},
|
|
296
|
+
"args": {
|
|
297
|
+
"description": "Argument name to GraphQL type string, for example `limit: \"Int!\"`. Scalars or lists of scalars.",
|
|
298
|
+
"type": [
|
|
299
|
+
"object",
|
|
300
|
+
"null"
|
|
301
|
+
],
|
|
302
|
+
"additionalProperties": {
|
|
303
|
+
"type": "string"
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"output": {
|
|
307
|
+
"description": "GraphQL type string of the result, for example `\"[TopTrader!]!\"`. Scalars or types declared under `types:`.",
|
|
308
|
+
"type": "string"
|
|
309
|
+
},
|
|
310
|
+
"handler": {
|
|
311
|
+
"description": "`sidecar` (answered by the `resolvers` of your `createSidecar`) or an http(s):// URL that accepts Hasura's action payload.",
|
|
312
|
+
"type": "string"
|
|
313
|
+
},
|
|
314
|
+
"timeoutMs": {
|
|
315
|
+
"description": "How long the GraphQL layer waits for the handler. (default: 30000)",
|
|
316
|
+
"type": [
|
|
317
|
+
"integer",
|
|
318
|
+
"null"
|
|
319
|
+
],
|
|
320
|
+
"format": "uint64",
|
|
321
|
+
"minimum": 0
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"additionalProperties": false,
|
|
325
|
+
"required": [
|
|
326
|
+
"output",
|
|
327
|
+
"handler"
|
|
328
|
+
]
|
|
329
|
+
},
|
|
234
330
|
"EcosystemTag": {
|
|
235
331
|
"type": "string",
|
|
236
332
|
"enum": [
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import * as Sury from "rescript-schema";
|
|
2
2
|
import type { default as BigDecimalT } from "bignumber.js";
|
|
3
|
+
import type {
|
|
4
|
+
SidecarCaller,
|
|
5
|
+
SidecarContract,
|
|
6
|
+
SidecarFunctionInput,
|
|
7
|
+
SidecarFunctionName,
|
|
8
|
+
SidecarWebhookBody,
|
|
9
|
+
SidecarWebhookName,
|
|
10
|
+
} from "./src/sidecar/contract.js";
|
|
3
11
|
export { default as BigDecimal } from "bignumber.js";
|
|
4
12
|
|
|
5
13
|
// Runtime value stubs used by the `S.*` namespace declarations further down
|
|
@@ -307,6 +315,65 @@ type GlobalConfig = Global extends { config: infer C extends IndexerConfigTypes
|
|
|
307
315
|
? C
|
|
308
316
|
: {};
|
|
309
317
|
|
|
318
|
+
/** `context.sidecar` for the `sidecar.contract` module codegen binds. The
|
|
319
|
+
* contract is matched with `infer C extends SidecarContract`: testing an alias
|
|
320
|
+
* with `extends SidecarContract` and passing it on can hand `SidecarCaller`
|
|
321
|
+
* its intersection with `SidecarContract`, whose index signature lets any
|
|
322
|
+
* function name through. */
|
|
323
|
+
type GlobalSidecarCaller = Global extends { sidecar: infer C extends SidecarContract }
|
|
324
|
+
? SidecarCaller<C>
|
|
325
|
+
: NotConfigured<"context.sidecar", "Add `sidecar: { contract, entry }`">;
|
|
326
|
+
|
|
327
|
+
/** The webhooks the indexer itself posts about `context.sidecar` calls. */
|
|
328
|
+
export type SidecarBuiltinWebhookName = "sidecar.ack" | "sidecar.dropped";
|
|
329
|
+
|
|
330
|
+
/** A name `indexer.onWebhook` accepts: the contract's webhooks, the built-ins,
|
|
331
|
+
* or any other name `emit.raw` may post. The contract is inferred where it is
|
|
332
|
+
* used, as for `GlobalSidecarCaller`, so its index signature never leaks in. */
|
|
333
|
+
export type WebhookName =
|
|
334
|
+
| (Global extends { sidecar: infer C extends SidecarContract } ? SidecarWebhookName<C> : never)
|
|
335
|
+
| SidecarBuiltinWebhookName
|
|
336
|
+
| (string & {});
|
|
337
|
+
|
|
338
|
+
/** The call a built-in webhook is about, with its input decoded. */
|
|
339
|
+
type SidecarCallRefOf<C extends SidecarContract> = {
|
|
340
|
+
[K in SidecarFunctionName<C>]: {
|
|
341
|
+
readonly callId: string;
|
|
342
|
+
readonly fn: K;
|
|
343
|
+
readonly input: SidecarFunctionInput<C, K>;
|
|
344
|
+
};
|
|
345
|
+
}[SidecarFunctionName<C>];
|
|
346
|
+
|
|
347
|
+
type WebhookBodyFrom<C extends SidecarContract, N extends string> = N extends "sidecar.ack"
|
|
348
|
+
? SidecarCallRefOf<C>
|
|
349
|
+
: N extends "sidecar.dropped"
|
|
350
|
+
? SidecarCallRefOf<C> & { readonly reason: string }
|
|
351
|
+
: N extends SidecarWebhookName<C>
|
|
352
|
+
? SidecarWebhookBody<C, N>
|
|
353
|
+
: unknown;
|
|
354
|
+
|
|
355
|
+
type WebhookBodyOf<N extends string> = Global extends { sidecar: infer C extends SidecarContract }
|
|
356
|
+
? WebhookBodyFrom<C, N>
|
|
357
|
+
: N extends SidecarBuiltinWebhookName
|
|
358
|
+
? { readonly callId: string; readonly fn: string; readonly input: unknown; readonly reason?: string }
|
|
359
|
+
: unknown;
|
|
360
|
+
|
|
361
|
+
/** What an `indexer.onWebhook` handler is handed about the webhook. */
|
|
362
|
+
export type OnWebhookEvent<N extends string = string> = {
|
|
363
|
+
readonly name: N;
|
|
364
|
+
/** Decoded with the contract's `webhooks.<name>.body` schema. */
|
|
365
|
+
readonly body: WebhookBodyOf<N>;
|
|
366
|
+
/** The id the listener answered the sidecar with. */
|
|
367
|
+
readonly id: string;
|
|
368
|
+
readonly chainId: number;
|
|
369
|
+
/** When the listener accepted it, in milliseconds since the epoch. */
|
|
370
|
+
readonly receivedAt: number;
|
|
371
|
+
readonly idempotencyKey?: string;
|
|
372
|
+
/** The block the webhook's writes are recorded at: where its chain's batch
|
|
373
|
+
* left the chain. */
|
|
374
|
+
readonly block: { readonly number: number; readonly timestamp?: number };
|
|
375
|
+
};
|
|
376
|
+
|
|
310
377
|
/** Error-message string returned by project-bound aliases when codegen has
|
|
311
378
|
* not run yet. Resolves to `string` so handler signatures stay assignable.
|
|
312
379
|
* Wording is package-manager-neutral — `envio init` lets users pick pnpm,
|
|
@@ -581,6 +648,10 @@ type BaseHandlerContext<Config extends IndexerConfigTypes = GlobalConfig, ChainI
|
|
|
581
648
|
readonly id: ChainId;
|
|
582
649
|
readonly isRealtime: boolean;
|
|
583
650
|
};
|
|
651
|
+
/** Call the sidecar declared by `sidecar:` in config.yaml. A call returns its
|
|
652
|
+
* id synchronously, is skipped in preload, and is delivered after the batch
|
|
653
|
+
* commits. */
|
|
654
|
+
readonly sidecar: GlobalSidecarCaller;
|
|
584
655
|
} & {
|
|
585
656
|
readonly [K in keyof ConfigEntities<Config>]: EntityOperations<ConfigEntities<Config>[K]>;
|
|
586
657
|
};
|
|
@@ -1861,6 +1932,20 @@ export type IndexerFromConfig<Config extends IndexerConfigTypes = GlobalConfig>
|
|
|
1861
1932
|
readonly rollbackToBlock: number;
|
|
1862
1933
|
}) => Promise<void>,
|
|
1863
1934
|
) => void;
|
|
1935
|
+
/**
|
|
1936
|
+
* Handle a webhook the sidecar posts with `emit.<name>(body)`, or a
|
|
1937
|
+
* built-in one about a `context.sidecar` call (`sidecar.ack`,
|
|
1938
|
+
* `sidecar.dropped`). It runs once, in the next batch of its chain after
|
|
1939
|
+
* that batch's events, with a normal handler context. A webhook undone by
|
|
1940
|
+
* a reorg is not replayed.
|
|
1941
|
+
*/
|
|
1942
|
+
readonly onWebhook: <const N extends WebhookName>(
|
|
1943
|
+
options: { readonly name: N },
|
|
1944
|
+
handler: (args: {
|
|
1945
|
+
readonly webhook: OnWebhookEvent<N>;
|
|
1946
|
+
readonly context: BaseHandlerContext<Config, number>;
|
|
1947
|
+
}) => Promise<void>,
|
|
1948
|
+
) => void;
|
|
1864
1949
|
} & SingleEcosystemChains<Config>
|
|
1865
1950
|
>;
|
|
1866
1951
|
|
|
@@ -2066,6 +2151,11 @@ type EntityChange<Config extends IndexerConfigTypes = GlobalConfig> = {
|
|
|
2066
2151
|
readonly addresses?: {
|
|
2067
2152
|
readonly sets?: readonly AddressRegistration[];
|
|
2068
2153
|
};
|
|
2154
|
+
/** `context.sidecar` calls made in this block, in call order. `input` is the
|
|
2155
|
+
* JSON form the sidecar receives (bigint as a string). */
|
|
2156
|
+
readonly sidecar?: {
|
|
2157
|
+
readonly calls: readonly { readonly id: string; readonly fn: string; readonly input: unknown }[];
|
|
2158
|
+
};
|
|
2069
2159
|
} & {
|
|
2070
2160
|
readonly [K in keyof ConfigEntities<Config>]?: EntityChangeValue<
|
|
2071
2161
|
ConfigEntities<Config>[K]
|
|
@@ -2209,6 +2299,32 @@ type SingleEcosystemTestChains<Config extends IndexerConfigTypes = GlobalConfig>
|
|
|
2209
2299
|
? SvmTestEcosystem<Config>
|
|
2210
2300
|
: CodegenRequiredFallback;
|
|
2211
2301
|
|
|
2302
|
+
/** A `context.sidecar` call as the test indexer records it, with `input` as the
|
|
2303
|
+
* handler passed it (a bigint stays a bigint). */
|
|
2304
|
+
export type TestIndexerSidecarCall<C extends SidecarContract> = {
|
|
2305
|
+
[K in SidecarFunctionName<C>]: {
|
|
2306
|
+
readonly id: string;
|
|
2307
|
+
readonly fn: K;
|
|
2308
|
+
readonly chainId: number;
|
|
2309
|
+
readonly blockNumber: number;
|
|
2310
|
+
/** Absent for block handlers. */
|
|
2311
|
+
readonly blockTimestamp?: number;
|
|
2312
|
+
readonly input: SidecarFunctionInput<C, K>;
|
|
2313
|
+
};
|
|
2314
|
+
}[SidecarFunctionName<C>];
|
|
2315
|
+
|
|
2316
|
+
/** `indexer.sidecar` on a test indexer. The test indexer never talks to a
|
|
2317
|
+
* sidecar: it records the calls instead. */
|
|
2318
|
+
type TestIndexerSidecar = Global extends { sidecar: infer C extends SidecarContract }
|
|
2319
|
+
? {
|
|
2320
|
+
/** Every call the handlers made, across `process` runs, in call order.
|
|
2321
|
+
* Calls made during preload are not recorded. */
|
|
2322
|
+
readonly calls: readonly TestIndexerSidecarCall<C>[];
|
|
2323
|
+
/** Forget the recorded calls. */
|
|
2324
|
+
readonly clear: () => void;
|
|
2325
|
+
}
|
|
2326
|
+
: NotConfigured<"indexer.sidecar", "Add `sidecar: { contract, entry }`">;
|
|
2327
|
+
|
|
2212
2328
|
/**
|
|
2213
2329
|
* Test indexer type resolved from config.
|
|
2214
2330
|
* Allows running the indexer for specific block ranges and inspecting results.
|
|
@@ -2221,6 +2337,8 @@ export type TestIndexerFromConfig<Config extends IndexerConfigTypes = GlobalConf
|
|
|
2221
2337
|
/** Changes happened during the processing. */
|
|
2222
2338
|
readonly changes: readonly EntityChange<Config>[];
|
|
2223
2339
|
}>;
|
|
2340
|
+
/** The `context.sidecar` calls the handlers made. */
|
|
2341
|
+
readonly sidecar: TestIndexerSidecar;
|
|
2224
2342
|
} & SingleEcosystemTestChains<Config> & {
|
|
2225
2343
|
/** Entity operations for direct manipulation outside of handlers. */
|
|
2226
2344
|
readonly [K in keyof ConfigEntities<Config>]: TestIndexerEntityOperations<
|
|
@@ -2306,3 +2424,14 @@ export const indexer: Indexer;
|
|
|
2306
2424
|
|
|
2307
2425
|
/** Construct a {@link TestIndexer} for use in unit tests. */
|
|
2308
2426
|
export const createTestIndexer: () => TestIndexer;
|
|
2427
|
+
|
|
2428
|
+
// ============== Sidecars ==============
|
|
2429
|
+
|
|
2430
|
+
export {
|
|
2431
|
+
defineSidecar,
|
|
2432
|
+
type SidecarContract,
|
|
2433
|
+
type SidecarContractDef,
|
|
2434
|
+
type SidecarCaller,
|
|
2435
|
+
type SidecarFunctionInput,
|
|
2436
|
+
type SidecarWebhookBody,
|
|
2437
|
+
} from "./src/sidecar/contract.js";
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0-sidecars.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
6
6
|
"bin": "./bin.mjs",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"types": "./index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"default": "./index.js"
|
|
13
|
+
},
|
|
14
|
+
"./sidecar": {
|
|
15
|
+
"types": "./sidecar.d.ts",
|
|
16
|
+
"default": "./sidecar.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
"./*": "./*"
|
|
20
|
+
},
|
|
9
21
|
"repository": {
|
|
10
22
|
"type": "git",
|
|
11
23
|
"url": "git+https://github.com/enviodev/hyperindex.git"
|
|
@@ -37,27 +49,29 @@
|
|
|
37
49
|
"rescript.json",
|
|
38
50
|
"index.d.ts",
|
|
39
51
|
"index.js",
|
|
52
|
+
"sidecar.d.ts",
|
|
53
|
+
"sidecar.js",
|
|
40
54
|
"src",
|
|
41
55
|
"licenses"
|
|
42
56
|
],
|
|
43
57
|
"dependencies": {
|
|
44
58
|
"@elastic/ecs-pino-format": "1.4.0",
|
|
59
|
+
"@rescript/runtime": "12.2.0",
|
|
45
60
|
"bignumber.js": "9.3.1",
|
|
61
|
+
"dotenv": "16.4.5",
|
|
46
62
|
"eventsource": "4.1.0",
|
|
47
63
|
"express": "4.19.2",
|
|
48
64
|
"pino": "10.3.1",
|
|
49
65
|
"pino-pretty": "13.1.3",
|
|
50
|
-
"
|
|
66
|
+
"postgres": "3.4.8",
|
|
51
67
|
"rescript-schema": "9.5.1",
|
|
52
|
-
"viem": "2.54.0"
|
|
53
|
-
"dotenv": "16.4.5",
|
|
54
|
-
"postgres": "3.4.8"
|
|
68
|
+
"viem": "2.54.0"
|
|
55
69
|
},
|
|
56
70
|
"optionalDependencies": {
|
|
57
|
-
"envio-linux-x64": "3.
|
|
58
|
-
"envio-linux-x64-musl": "3.
|
|
59
|
-
"envio-linux-arm64": "3.
|
|
60
|
-
"envio-darwin-x64": "3.
|
|
61
|
-
"envio-darwin-arm64": "3.
|
|
71
|
+
"envio-linux-x64": "3.15.0-sidecars.2",
|
|
72
|
+
"envio-linux-x64-musl": "3.15.0-sidecars.2",
|
|
73
|
+
"envio-linux-arm64": "3.15.0-sidecars.2",
|
|
74
|
+
"envio-darwin-x64": "3.15.0-sidecars.2",
|
|
75
|
+
"envio-darwin-arm64": "3.15.0-sidecars.2"
|
|
62
76
|
}
|
|
63
77
|
}
|
package/sidecar.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/sidecar/index.js";
|
package/sidecar.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/sidecar/index.js";
|
package/src/Batch.res
CHANGED
|
@@ -14,6 +14,8 @@ type chainAfterBatch = {
|
|
|
14
14
|
totalEventsProcessed: float,
|
|
15
15
|
fetchState: FetchState.t,
|
|
16
16
|
isProgressAtHeadWhenBatchCreated: bool,
|
|
17
|
+
// Taken off the chain's webhook queue.
|
|
18
|
+
webhooksCount?: int,
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// A per-chain snapshot of the scanned block hashes still inside the reorg
|
|
@@ -38,6 +40,7 @@ type chainBeforeBatch = {
|
|
|
38
40
|
sourceBlockNumber: int,
|
|
39
41
|
totalEventsProcessed: float,
|
|
40
42
|
chainConfig: Config.chain,
|
|
43
|
+
pendingWebhooks: array<Internal.pendingWebhook>,
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
type t = {
|
|
@@ -58,6 +61,9 @@ type t = {
|
|
|
58
61
|
// event, however many items it made.
|
|
59
62
|
checkpointEventsProcessed: array<int>,
|
|
60
63
|
registeredAddresses: array<AddressRows.staged>,
|
|
64
|
+
// `context.sidecar` calls the handlers made, in call order. Handed to the
|
|
65
|
+
// sidecar only once the batch has committed.
|
|
66
|
+
sidecarCalls: array<SidecarCall.t>,
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
// What a chain contributed to the batch: items taken off its buffer, and the
|
|
@@ -70,11 +76,12 @@ let getProgressedChainsById = {
|
|
|
70
76
|
~progressBlockNumberAfterBatch,
|
|
71
77
|
~fetchStateAfterBatch,
|
|
72
78
|
~counts: chainBatchCounts,
|
|
79
|
+
~webhooksCount,
|
|
73
80
|
) => {
|
|
74
81
|
// The check is sufficient, since we guarantee to include a full block in a batch
|
|
75
82
|
// Also, this might be true even if batchSize is 0,
|
|
76
83
|
// eg when indexing at the head and chain doesn't have items in a block
|
|
77
|
-
if chainBeforeBatch.progressBlockNumber < progressBlockNumberAfterBatch {
|
|
84
|
+
if chainBeforeBatch.progressBlockNumber < progressBlockNumberAfterBatch || webhooksCount > 0 {
|
|
78
85
|
Some(
|
|
79
86
|
(
|
|
80
87
|
{
|
|
@@ -85,8 +92,13 @@ let getProgressedChainsById = {
|
|
|
85
92
|
totalEventsProcessed: chainBeforeBatch.totalEventsProcessed +.
|
|
86
93
|
counts.eventsProcessed->Int.toFloat,
|
|
87
94
|
fetchState: fetchStateAfterBatch,
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
// A batch of webhooks alone can come before the source has
|
|
96
|
+
// answered the height, while it still reads 0: nothing is at a
|
|
97
|
+
// head that isn't known yet.
|
|
98
|
+
isProgressAtHeadWhenBatchCreated: chainBeforeBatch.sourceBlockNumber > 0 &&
|
|
99
|
+
progressBlockNumberAfterBatch >=
|
|
100
|
+
chainBeforeBatch.sourceBlockNumber - chainBeforeBatch.chainConfig.blockLag,
|
|
101
|
+
webhooksCount,
|
|
90
102
|
}: chainAfterBatch
|
|
91
103
|
),
|
|
92
104
|
)
|
|
@@ -99,6 +111,7 @@ let getProgressedChainsById = {
|
|
|
99
111
|
~chainsBeforeBatch: dict<chainBeforeBatch>,
|
|
100
112
|
~countsPerChain: dict<chainBatchCounts>,
|
|
101
113
|
~progressBlockNumberPerChain: dict<int>,
|
|
114
|
+
~webhooksCountPerChain: dict<int>,
|
|
102
115
|
) => {
|
|
103
116
|
let progressedChainsById = Dict.make()
|
|
104
117
|
|
|
@@ -117,6 +130,10 @@ let getProgressedChainsById = {
|
|
|
117
130
|
| Some(progressBlockNumber) => progressBlockNumber
|
|
118
131
|
| None => chainBeforeBatch.progressBlockNumber
|
|
119
132
|
}
|
|
133
|
+
let webhooksCount =
|
|
134
|
+
webhooksCountPerChain
|
|
135
|
+
->Utils.Dict.dangerouslyGetNonOption(fetchState.chainId->ChainId.toString)
|
|
136
|
+
->Option.getOr(0)
|
|
120
137
|
|
|
121
138
|
switch switch countsPerChain->Utils.Dict.dangerouslyGetNonOption(
|
|
122
139
|
fetchState.chainId->ChainId.toString,
|
|
@@ -128,6 +145,7 @@ let getProgressedChainsById = {
|
|
|
128
145
|
~counts,
|
|
129
146
|
~fetchStateAfterBatch=fetchState->FetchState.updateInternal(~mutItems=leftItems),
|
|
130
147
|
~progressBlockNumberAfterBatch,
|
|
148
|
+
~webhooksCount,
|
|
131
149
|
)
|
|
132
150
|
// Skip not affected chains
|
|
133
151
|
| None =>
|
|
@@ -136,6 +154,7 @@ let getProgressedChainsById = {
|
|
|
136
154
|
~counts={size: 0, eventsProcessed: 0},
|
|
137
155
|
~fetchStateAfterBatch=chainBeforeBatch.fetchState,
|
|
138
156
|
~progressBlockNumberAfterBatch,
|
|
157
|
+
~webhooksCount,
|
|
139
158
|
)
|
|
140
159
|
} {
|
|
141
160
|
| Some(progressedChain) =>
|
|
@@ -230,6 +249,7 @@ let make = (
|
|
|
230
249
|
let cursor = sequence->CheckpointSequence.cursor(~frontier)
|
|
231
250
|
let mutCountsPerChain = Dict.make()
|
|
232
251
|
let mutProgressBlockNumberPerChain = Dict.make()
|
|
252
|
+
let mutWebhooksCountPerChain = Dict.make()
|
|
233
253
|
|
|
234
254
|
let items = []
|
|
235
255
|
let checkpointIds = []
|
|
@@ -263,8 +283,7 @@ let make = (
|
|
|
263
283
|
// The buffer is sorted, so a log's items are consecutive: the first of
|
|
264
284
|
// them is the only one that counts as an event.
|
|
265
285
|
let isNewEvent =
|
|
266
|
-
idx === 0 ||
|
|
267
|
-
!(fetchState.buffer->Array.getUnsafe(idx - 1)->FetchState.isSameLog(item))
|
|
286
|
+
idx === 0 || !(fetchState.buffer->Array.getUnsafe(idx - 1)->FetchState.isSameLog(item))
|
|
268
287
|
if isNewEvent {
|
|
269
288
|
chainEventsProcessed := chainEventsProcessed.contents + 1
|
|
270
289
|
}
|
|
@@ -345,6 +364,56 @@ let make = (
|
|
|
345
364
|
~mutCheckpointEventsProcessed=checkpointEventsProcessed,
|
|
346
365
|
)
|
|
347
366
|
|
|
367
|
+
let webhooksCount = chainBeforeBatch.pendingWebhooks->Array.length
|
|
368
|
+
if webhooksCount > 0 {
|
|
369
|
+
let lastIndex = checkpointIds->Array.length - 1
|
|
370
|
+
|
|
371
|
+
// On a checkpoint of their own block, so a rollback to below it undoes
|
|
372
|
+
// them along with it, and one to it or above keeps them.
|
|
373
|
+
if (
|
|
374
|
+
lastIndex >= 0 &&
|
|
375
|
+
checkpointChainIds->Array.getUnsafe(lastIndex) === fetchState.chainId &&
|
|
376
|
+
checkpointBlockNumbers->Array.getUnsafe(lastIndex) === progressBlockNumberAfterBatch
|
|
377
|
+
) {
|
|
378
|
+
checkpointItemsCount->Array.setUnsafe(
|
|
379
|
+
lastIndex,
|
|
380
|
+
checkpointItemsCount->Array.getUnsafe(lastIndex) + webhooksCount,
|
|
381
|
+
)
|
|
382
|
+
} else {
|
|
383
|
+
checkpointIds
|
|
384
|
+
->Array.push(cursor->CheckpointSequence.next(~chainId=fetchState.chainId))
|
|
385
|
+
->ignore
|
|
386
|
+
checkpointChainIds->Array.push(fetchState.chainId)->ignore
|
|
387
|
+
checkpointBlockNumbers->Array.push(progressBlockNumberAfterBatch)->ignore
|
|
388
|
+
checkpointBlockHashes
|
|
389
|
+
->Array.push(
|
|
390
|
+
switch chainBeforeBatch.scannedHashes.hashByBlockNumber->Utils.Dict.dangerouslyGetByIntNonOption(
|
|
391
|
+
progressBlockNumberAfterBatch,
|
|
392
|
+
) {
|
|
393
|
+
| Some(hash) => Null.Value(hash)
|
|
394
|
+
| None => Null.Null
|
|
395
|
+
},
|
|
396
|
+
)
|
|
397
|
+
->ignore
|
|
398
|
+
checkpointItemsCount->Array.push(webhooksCount)->ignore
|
|
399
|
+
checkpointEventsProcessed->Array.push(0)->ignore
|
|
400
|
+
}
|
|
401
|
+
let blockTimestamp = chainBeforeBatch.blockTimeAt(progressBlockNumberAfterBatch)
|
|
402
|
+
chainBeforeBatch.pendingWebhooks->Array.forEach(pendingWebhook =>
|
|
403
|
+
items
|
|
404
|
+
->Array.push(
|
|
405
|
+
Internal.Webhook({
|
|
406
|
+
pendingWebhook,
|
|
407
|
+
chainId: fetchState.chainId,
|
|
408
|
+
blockNumber: progressBlockNumberAfterBatch,
|
|
409
|
+
blockTimestamp,
|
|
410
|
+
}),
|
|
411
|
+
)
|
|
412
|
+
->ignore
|
|
413
|
+
)
|
|
414
|
+
mutWebhooksCountPerChain->ChainId.Dict.set(fetchState.chainId, webhooksCount)
|
|
415
|
+
}
|
|
416
|
+
|
|
348
417
|
mutProgressBlockNumberPerChain->ChainId.Dict.set(
|
|
349
418
|
fetchState.chainId,
|
|
350
419
|
progressBlockNumberAfterBatch,
|
|
@@ -360,6 +429,7 @@ let make = (
|
|
|
360
429
|
~chainsBeforeBatch,
|
|
361
430
|
~countsPerChain=mutCountsPerChain,
|
|
362
431
|
~progressBlockNumberPerChain=mutProgressBlockNumberPerChain,
|
|
432
|
+
~webhooksCountPerChain=mutWebhooksCountPerChain,
|
|
363
433
|
),
|
|
364
434
|
history,
|
|
365
435
|
checkpointIds,
|
|
@@ -369,6 +439,7 @@ let make = (
|
|
|
369
439
|
checkpointItemsCount,
|
|
370
440
|
checkpointEventsProcessed,
|
|
371
441
|
registeredAddresses: [],
|
|
442
|
+
sidecarCalls: [],
|
|
372
443
|
}
|
|
373
444
|
}
|
|
374
445
|
|
|
@@ -390,7 +461,7 @@ let findFirstEventBlockNumber = (batch: t, ~chainId) => {
|
|
|
390
461
|
let checkpointChainId = batch.checkpointChainIds->Array.getUnsafe(idx.contents)
|
|
391
462
|
if (
|
|
392
463
|
checkpointChainId === chainId &&
|
|
393
|
-
batch.
|
|
464
|
+
batch.checkpointEventsProcessed->Array.getUnsafe(idx.contents) > 0
|
|
394
465
|
) {
|
|
395
466
|
result := Some(batch.checkpointBlockNumbers->Array.getUnsafe(idx.contents))
|
|
396
467
|
} else {
|
|
@@ -414,7 +485,7 @@ let findLastEventItem = (batch: t, ~chainId) => {
|
|
|
414
485
|
idx := idx.contents - 1
|
|
415
486
|
}
|
|
416
487
|
}
|
|
417
|
-
| Internal.Block(_) => idx := idx.contents - 1
|
|
488
|
+
| Internal.Block(_) | Internal.Webhook(_) => idx := idx.contents - 1
|
|
418
489
|
}
|
|
419
490
|
}
|
|
420
491
|
result.contents
|