envio 3.9.0 → 3.10.0
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 +26 -0
- package/fuel.schema.json +26 -0
- package/index.d.ts +35 -3
- package/package.json +6 -7
- package/src/Batch.res +9 -0
- package/src/ChainState.res +35 -39
- package/src/ChainState.res.mjs +14 -11
- package/src/ChainState.resi +15 -7
- package/src/Config.res +52 -44
- package/src/Config.res.mjs +51 -17
- package/src/Core.res +4 -0
- package/src/CrossChainState.res +15 -23
- package/src/CrossChainState.res.mjs +10 -18
- package/src/CrossChainState.resi +4 -1
- package/src/Ecosystem.res +7 -2
- package/src/Envio.res +6 -4
- package/src/EventConfigBuilder.res +169 -26
- package/src/EventConfigBuilder.res.mjs +124 -15
- package/src/HandlerRegister.res +21 -4
- package/src/HandlerRegister.res.mjs +24 -5
- package/src/InMemoryStore.res +9 -7
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/IndexerState.res +43 -6
- package/src/IndexerState.res.mjs +36 -7
- package/src/IndexerState.resi +4 -4
- package/src/Internal.res +16 -17
- package/src/Metrics.res +72 -6
- package/src/Metrics.res.mjs +30 -2
- package/src/Persistence.res +36 -30
- package/src/Persistence.res.mjs +1 -20
- package/src/PgStorage.res +104 -56
- package/src/PgStorage.res.mjs +57 -35
- package/src/PruneStaleHistory.res +85 -52
- package/src/PruneStaleHistory.res.mjs +63 -37
- package/src/Rollback.res +68 -45
- package/src/Rollback.res.mjs +38 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SafeCheckpointTracking.res +11 -11
- package/src/SafeCheckpointTracking.res.mjs +7 -6
- package/src/SimulateItems.res +33 -18
- package/src/SimulateItems.res.mjs +26 -22
- package/src/Sink.res +38 -26
- package/src/Sink.res.mjs +21 -16
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +1 -1
- package/src/Utils.res +170 -0
- package/src/Utils.res.mjs +153 -0
- package/src/Writing.res +2 -2
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +366 -698
- package/src/bindings/ClickHouse.res.mjs +371 -421
- package/src/bindings/ClickHouseSink.res +337 -0
- package/src/bindings/ClickHouseSink.res.mjs +246 -0
- package/src/bindings/EventSource.res +8 -2
- package/src/bindings/NodeJs.res +6 -0
- package/src/bindings/Vitest.res +23 -0
- package/src/bindings/Vitest.res.mjs +3 -0
- package/src/bindings/WebSocket.res +9 -10
- package/src/db/CheckpointBounds.res +53 -0
- package/src/db/CheckpointBounds.res.mjs +44 -0
- package/src/db/EntityFilter.res +36 -17
- package/src/db/EntityFilter.res.mjs +25 -14
- package/src/db/EntityHistory.res +47 -22
- package/src/db/EntityHistory.res.mjs +19 -12
- package/src/db/InternalTable.res +84 -35
- package/src/db/InternalTable.res.mjs +56 -23
- package/src/db/RollbackFloors.res +55 -0
- package/src/db/RollbackFloors.res.mjs +92 -0
- package/src/db/Table.res +22 -4
- package/src/db/Table.res.mjs +30 -9
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcWs.res +124 -0
- package/src/sources/EvmRpcWs.res.mjs +117 -0
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/FuelHyperSyncSource.res +1 -1
- package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
- package/src/sources/HeightFeed.res +568 -0
- package/src/sources/HeightFeed.res.mjs +452 -0
- package/src/sources/HeightStream.res +257 -0
- package/src/sources/HeightStream.res.mjs +194 -0
- package/src/sources/HyperSync.res +5 -0
- package/src/sources/HyperSync.res.mjs +3 -0
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncSSE.res +49 -0
- package/src/sources/HyperSyncSSE.res.mjs +79 -0
- package/src/sources/RpcSource.res +1 -1
- package/src/sources/RpcSource.res.mjs +2 -2
- package/src/sources/Source.res +54 -1
- package/src/sources/Source.res.mjs +25 -0
- package/src/sources/SourceManager.res +220 -227
- package/src/sources/SourceManager.res.mjs +139 -164
- package/src/sources/SourceManager.resi +8 -0
- package/src/sources/Svm.res +2 -0
- package/src/sources/Svm.res.mjs +2 -0
- package/src/sources/SvmHyperSyncClient.res +70 -127
- package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
- package/src/sources/SvmHyperSyncSource.res +18 -15
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/svm.schema.json +1 -75
- package/src/MemoryStorage.res +0 -729
- package/src/MemoryStorage.res.mjs +0 -586
- package/src/sources/HyperSyncHeightStream.res +0 -129
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
- package/src/sources/RpcWebSocketHeightStream.res +0 -175
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
type t
|
|
2
|
+
|
|
3
|
+
type historySchema = {
|
|
4
|
+
idColumn: string,
|
|
5
|
+
checkpointIdColumn: string,
|
|
6
|
+
changeColumn: string,
|
|
7
|
+
changeVariants: array<string>,
|
|
8
|
+
setVariant: string,
|
|
9
|
+
checkpointsTable: string,
|
|
10
|
+
checkpointChainIdColumn: string,
|
|
11
|
+
checkpointBlockNumberColumn: string,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type chainProgressInput = {chainId: string, progressBlockNumber: int}
|
|
15
|
+
|
|
16
|
+
type options = {
|
|
17
|
+
url: string,
|
|
18
|
+
username: string,
|
|
19
|
+
password: string,
|
|
20
|
+
database: string,
|
|
21
|
+
chainIdMode: string,
|
|
22
|
+
history: historySchema,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@send external classNew: (Core.clickHouseSinkCtor, options, string => unit) => t = "new"
|
|
26
|
+
|
|
27
|
+
type columnSpec = {
|
|
28
|
+
name: string,
|
|
29
|
+
// Omitted when it matches `name`. Set when a column rename is configured, so
|
|
30
|
+
// `@storage(clickhouse: {...})` expressions can still name the schema field.
|
|
31
|
+
fieldName?: string,
|
|
32
|
+
fieldType: string,
|
|
33
|
+
isNullable?: bool,
|
|
34
|
+
isArray?: bool,
|
|
35
|
+
precision?: int,
|
|
36
|
+
scale?: int,
|
|
37
|
+
enumVariants?: array<string>,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type skippingIndexSpec = {
|
|
41
|
+
name: string,
|
|
42
|
+
expr: string,
|
|
43
|
+
indexType: string,
|
|
44
|
+
granularity?: int,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type entitySpec = {
|
|
48
|
+
name: string,
|
|
49
|
+
historyTable: string,
|
|
50
|
+
columns: array<columnSpec>,
|
|
51
|
+
chainIdColumn?: string,
|
|
52
|
+
partitionBy?: string,
|
|
53
|
+
orderBy?: array<string>,
|
|
54
|
+
ttl?: string,
|
|
55
|
+
skippingIndexes?: array<skippingIndexSpec>,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type initializeInput = {
|
|
59
|
+
entities: array<entitySpec>,
|
|
60
|
+
checkpointColumns: array<columnSpec>,
|
|
61
|
+
replicated: bool,
|
|
62
|
+
databaseEngine?: string,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type resumeInput = {
|
|
66
|
+
checkpointId: string,
|
|
67
|
+
chainProgress: array<chainProgressInput>,
|
|
68
|
+
historyTables: array<string>,
|
|
69
|
+
replicated: bool,
|
|
70
|
+
databaseEngine?: string,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type registeredTable = {
|
|
74
|
+
handle: int,
|
|
75
|
+
names: array<string>,
|
|
76
|
+
kinds: array<int>,
|
|
77
|
+
nullable: array<bool>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type columnValuesInput = {
|
|
81
|
+
numbers?: Float64Array.t,
|
|
82
|
+
unsigned64?: BigUint64Array.t,
|
|
83
|
+
signed64?: BigInt64Array.t,
|
|
84
|
+
texts?: array<string>,
|
|
85
|
+
bytes?: array<Uint8Array.t>,
|
|
86
|
+
nulls?: Uint8Array.t,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@send
|
|
90
|
+
external registerEntityTable: (t, entitySpec) => registeredTable = "registerEntityTable"
|
|
91
|
+
|
|
92
|
+
@send
|
|
93
|
+
external registerCheckpointsTable: (t, array<columnSpec>) => registeredTable =
|
|
94
|
+
"registerCheckpointsTable"
|
|
95
|
+
|
|
96
|
+
@send external initialize: (t, initializeInput) => promise<unit> = "initialize"
|
|
97
|
+
|
|
98
|
+
@send
|
|
99
|
+
external resume: (t, resumeInput) => promise<unit> = "resume"
|
|
100
|
+
|
|
101
|
+
@send
|
|
102
|
+
external stage: (t, ~table: int, ~rows: int, ~columns: array<columnValuesInput>) => int = "stage"
|
|
103
|
+
|
|
104
|
+
@send
|
|
105
|
+
external writeBatch: (t, ~entities: array<int>, ~checkpoints: Null.t<int>) => promise<unit> =
|
|
106
|
+
"writeBatch"
|
|
107
|
+
|
|
108
|
+
@send external discard: (t, array<int>) => unit = "discard"
|
|
109
|
+
|
|
110
|
+
let historySchema = (): historySchema => {
|
|
111
|
+
idColumn: Table.idFieldName,
|
|
112
|
+
checkpointIdColumn: EntityHistory.checkpointIdFieldName,
|
|
113
|
+
changeColumn: EntityHistory.changeFieldName,
|
|
114
|
+
changeVariants: EntityHistory.RowAction.variants->Array.map(variant => (variant :> string)),
|
|
115
|
+
setVariant: (EntityHistory.RowAction.SET :> string),
|
|
116
|
+
checkpointsTable: InternalTable.Checkpoints.table.tableName,
|
|
117
|
+
checkpointChainIdColumn: (#chain_id: InternalTable.Checkpoints.field :> string),
|
|
118
|
+
checkpointBlockNumberColumn: (#block_number: InternalTable.Checkpoints.field :> string),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let make = (~url, ~username, ~password, ~database, ~chainIdMode: ChainId.mode, ~onWarning) =>
|
|
122
|
+
Core.getAddon().clickHouseSink->classNew(
|
|
123
|
+
{
|
|
124
|
+
url,
|
|
125
|
+
username,
|
|
126
|
+
password,
|
|
127
|
+
database,
|
|
128
|
+
chainIdMode: (chainIdMode :> string),
|
|
129
|
+
history: historySchema(),
|
|
130
|
+
},
|
|
131
|
+
onWarning,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
type kind =
|
|
135
|
+
| @as(0) F64
|
|
136
|
+
| @as(1) U64
|
|
137
|
+
| @as(2) I64
|
|
138
|
+
| @as(3) Text
|
|
139
|
+
| @as(4) Bytes
|
|
140
|
+
|
|
141
|
+
let kindOfOrdinal = ordinal =>
|
|
142
|
+
switch ordinal {
|
|
143
|
+
| 0 => F64
|
|
144
|
+
| 1 => U64
|
|
145
|
+
| 2 => I64
|
|
146
|
+
| 3 => Text
|
|
147
|
+
| 4 => Bytes
|
|
148
|
+
| unknown => JsError.throwWithMessage(`Unknown ClickHouse column kind ${unknown->Int.toString}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
external asString: unknown => string = "%identity"
|
|
152
|
+
@val external toNumber: unknown => float = "Number"
|
|
153
|
+
@val external toBigInt: unknown => bigint = "BigInt"
|
|
154
|
+
@val external stringOf: unknown => string = "String"
|
|
155
|
+
|
|
156
|
+
// No column holds NaN or Infinity, but nothing downstream refuses them well: a
|
|
157
|
+
// list renders one as `null` on the way into JSON text, which the column then
|
|
158
|
+
// refuses for a reason naming `null` rather than what the handler wrote, and a
|
|
159
|
+
// scalar Float64 takes the raw bytes and stores a value no reader can render.
|
|
160
|
+
// Refused here instead, where the value is still itself, with one message for
|
|
161
|
+
// both shapes.
|
|
162
|
+
%%private(
|
|
163
|
+
let finiteOrThrow = (number: float, ~column) =>
|
|
164
|
+
if number->Float.isFinite {
|
|
165
|
+
number
|
|
166
|
+
} else {
|
|
167
|
+
JsError.throwWithMessage(
|
|
168
|
+
`${number->Float.toString} is not a finite number, so it cannot be stored in the \`${column}\` column. Store a finite number, or keep it out of the entity.`,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
// Visits every node on the way into JSON text. A bigint would make
|
|
174
|
+
// `JSON.stringify` throw and fail the whole batch, so it travels as its digits.
|
|
175
|
+
%%private(
|
|
176
|
+
let jsonSafe = (~column) =>
|
|
177
|
+
(_, value: JSON.t) =>
|
|
178
|
+
switch value->typeof {
|
|
179
|
+
| #bigint =>
|
|
180
|
+
value->(Utils.magic: JSON.t => unknown)->stringOf->(Utils.magic: string => JSON.t)
|
|
181
|
+
// A Uint8Array inside a list column travels as its byte values, which the
|
|
182
|
+
// sink reads back into raw bytes.
|
|
183
|
+
| #object =>
|
|
184
|
+
switch value->(Utils.magic: JSON.t => unknown)->Utils.Bytes.asUint8Array {
|
|
185
|
+
| Some(bytes) =>
|
|
186
|
+
bytes
|
|
187
|
+
->(Utils.magic: Uint8Array.t => Array.arrayLike<int>)
|
|
188
|
+
->Array.fromArrayLike
|
|
189
|
+
->(Utils.magic: array<int> => JSON.t)
|
|
190
|
+
| None => value
|
|
191
|
+
}
|
|
192
|
+
| #number =>
|
|
193
|
+
let _ = value->(Utils.magic: JSON.t => float)->finiteOrThrow(~column)
|
|
194
|
+
value
|
|
195
|
+
| _ => value
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
// The replacer closes over nothing but the column's name, so it is built once
|
|
200
|
+
// with the column rather than once per column per batch.
|
|
201
|
+
type column = {name: string, kind: kind, isNullable: bool, replacer: JSON.replacer}
|
|
202
|
+
type table = {handle: int, name: string, columns: array<column>}
|
|
203
|
+
|
|
204
|
+
let makeTable = (~name, {handle, names, kinds, nullable}: registeredTable) => {
|
|
205
|
+
handle,
|
|
206
|
+
name,
|
|
207
|
+
columns: names->Array.mapWithIndex((name, index) => {
|
|
208
|
+
name,
|
|
209
|
+
kind: kinds->Array.getUnsafe(index)->kindOfOrdinal,
|
|
210
|
+
isNullable: nullable->Array.getUnsafe(index),
|
|
211
|
+
replacer: Replacer(jsonSafe(~column=name)),
|
|
212
|
+
}),
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let toText = (value: unknown, ~replacer) =>
|
|
216
|
+
switch value->typeof {
|
|
217
|
+
| #string => value->asString
|
|
218
|
+
| #bigint => value->stringOf
|
|
219
|
+
| _ => value->(Utils.magic: unknown => JSON.t)->JSON.stringify(~replacer)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
type builder = {
|
|
223
|
+
name: string,
|
|
224
|
+
kind: kind,
|
|
225
|
+
isNullable: bool,
|
|
226
|
+
floats: Float64Array.t,
|
|
227
|
+
unsigned: BigUint64Array.t,
|
|
228
|
+
signed: BigInt64Array.t,
|
|
229
|
+
texts: array<string>,
|
|
230
|
+
bytes: array<Uint8Array.t>,
|
|
231
|
+
replacer: JSON.replacer,
|
|
232
|
+
mutable nulls: option<Uint8Array.t>,
|
|
233
|
+
rows: int,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
%%private(let noFloats = Float64Array.fromLength(0))
|
|
237
|
+
%%private(let noUnsigned = BigUint64Array.fromLength(0))
|
|
238
|
+
%%private(let noSigned = BigInt64Array.fromLength(0))
|
|
239
|
+
%%private(let noBytes = Uint8Array.fromLength(0))
|
|
240
|
+
|
|
241
|
+
let makeBuilder = ({name, kind, isNullable, replacer}: column, ~rows) => {
|
|
242
|
+
name,
|
|
243
|
+
kind,
|
|
244
|
+
isNullable,
|
|
245
|
+
floats: kind === F64 ? Float64Array.fromLength(rows) : noFloats,
|
|
246
|
+
unsigned: kind === U64 ? BigUint64Array.fromLength(rows) : noUnsigned,
|
|
247
|
+
signed: kind === I64 ? BigInt64Array.fromLength(rows) : noSigned,
|
|
248
|
+
texts: kind === Text ? Array.make(~length=rows, "") : [],
|
|
249
|
+
bytes: kind === Bytes ? Array.make(~length=rows, noBytes) : [],
|
|
250
|
+
replacer,
|
|
251
|
+
nulls: None,
|
|
252
|
+
rows,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let markNull = (builder, ~row) => {
|
|
256
|
+
let nulls = switch builder.nulls {
|
|
257
|
+
| Some(nulls) => nulls
|
|
258
|
+
| None =>
|
|
259
|
+
let nulls = Uint8Array.fromLength(builder.rows)
|
|
260
|
+
builder.nulls = Some(nulls)
|
|
261
|
+
nulls
|
|
262
|
+
}
|
|
263
|
+
nulls->TypedArray.set(row, 1)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// RowBinary carries the raw integer, so a value the column cannot hold is not
|
|
267
|
+
// rejected anywhere downstream — and a typed array reduces it modulo 2^64 on
|
|
268
|
+
// the way in rather than refusing it, which would leave no trace at all.
|
|
269
|
+
%%private(
|
|
270
|
+
let checkedBigInt = (value: unknown, ~builder, ~min, ~max) => {
|
|
271
|
+
let value = value->toBigInt
|
|
272
|
+
if value < min || value > max {
|
|
273
|
+
JsError.throwWithMessage(
|
|
274
|
+
`${value->BigInt.toString} is out of range for the \`${builder.name}\` column`,
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
value
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
%%private(
|
|
282
|
+
let writePresent = (builder, ~row, value: unknown) =>
|
|
283
|
+
switch builder.kind {
|
|
284
|
+
| F64 =>
|
|
285
|
+
builder.floats->TypedArray.set(row, value->toNumber->finiteOrThrow(~column=builder.name))
|
|
286
|
+
| U64 =>
|
|
287
|
+
builder.unsigned->TypedArray.set(
|
|
288
|
+
row,
|
|
289
|
+
value->checkedBigInt(~builder, ~min=0n, ~max=18446744073709551615n),
|
|
290
|
+
)
|
|
291
|
+
| I64 =>
|
|
292
|
+
builder.signed->TypedArray.set(
|
|
293
|
+
row,
|
|
294
|
+
value->checkedBigInt(~builder, ~min=-9223372036854775808n, ~max=9223372036854775807n),
|
|
295
|
+
)
|
|
296
|
+
| Text => builder.texts->Array.setUnsafe(row, value->toText(~replacer=builder.replacer))
|
|
297
|
+
| Bytes => builder.bytes->Array.setUnsafe(row, value->(Utils.magic: unknown => Uint8Array.t))
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
%%private(let isAbsent = (value: unknown) => value === %raw(`undefined`) || value === %raw(`null`))
|
|
302
|
+
|
|
303
|
+
// Writes one value of a row the handler set. `undefined`/`null` marks the row's
|
|
304
|
+
// null bit, which a column that accepts NULL stores as such — and which one that
|
|
305
|
+
// does not has no way to store: RowBinary carries no "absent", so the row would
|
|
306
|
+
// land holding the type's zero, a value the handler never chose and that nothing
|
|
307
|
+
// downstream could tell from one it did.
|
|
308
|
+
let writeValue = (builder, ~row, value: unknown) =>
|
|
309
|
+
if value->isAbsent {
|
|
310
|
+
if builder.isNullable {
|
|
311
|
+
builder->markNull(~row)
|
|
312
|
+
} else {
|
|
313
|
+
JsError.throwWithMessage(
|
|
314
|
+
`No value for the \`${builder.name}\` column, which is not nullable. Set the field before saving the entity, or make it optional in the schema.`,
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
builder->writePresent(~row, value)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let writeDeletedValue = (builder, ~row, value: unknown) =>
|
|
322
|
+
if value->isAbsent {
|
|
323
|
+
builder->markNull(~row)
|
|
324
|
+
} else {
|
|
325
|
+
builder->writePresent(~row, value)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let builderPayload = (builder): columnValuesInput => {
|
|
329
|
+
let base = {nulls: ?builder.nulls}
|
|
330
|
+
switch builder.kind {
|
|
331
|
+
| F64 => {...base, numbers: builder.floats}
|
|
332
|
+
| U64 => {...base, unsigned64: builder.unsigned}
|
|
333
|
+
| I64 => {...base, signed64: builder.signed}
|
|
334
|
+
| Text => {...base, texts: builder.texts}
|
|
335
|
+
| Bytes => {...base, bytes: builder.bytes}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Core from "../Core.res.mjs";
|
|
4
|
+
import * as Table from "../db/Table.res.mjs";
|
|
5
|
+
import * as Utils from "../Utils.res.mjs";
|
|
6
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
7
|
+
import * as EntityHistory from "../db/EntityHistory.res.mjs";
|
|
8
|
+
import * as InternalTable from "../db/InternalTable.res.mjs";
|
|
9
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
10
|
+
|
|
11
|
+
function historySchema() {
|
|
12
|
+
return {
|
|
13
|
+
idColumn: Table.idFieldName,
|
|
14
|
+
checkpointIdColumn: EntityHistory.checkpointIdFieldName,
|
|
15
|
+
changeColumn: EntityHistory.changeFieldName,
|
|
16
|
+
changeVariants: EntityHistory.RowAction.variants.map(variant => variant),
|
|
17
|
+
setVariant: "SET",
|
|
18
|
+
checkpointsTable: InternalTable.Checkpoints.table.tableName,
|
|
19
|
+
checkpointChainIdColumn: "chain_id",
|
|
20
|
+
checkpointBlockNumberColumn: "block_number"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function make(url, username, password, database, chainIdMode, onWarning) {
|
|
25
|
+
return Core.getAddon().ClickHouseSink.new({
|
|
26
|
+
url: url,
|
|
27
|
+
username: username,
|
|
28
|
+
password: password,
|
|
29
|
+
database: database,
|
|
30
|
+
chainIdMode: chainIdMode,
|
|
31
|
+
history: historySchema()
|
|
32
|
+
}, onWarning);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function kindOfOrdinal(ordinal) {
|
|
36
|
+
switch (ordinal) {
|
|
37
|
+
case 0 :
|
|
38
|
+
return 0;
|
|
39
|
+
case 1 :
|
|
40
|
+
return 1;
|
|
41
|
+
case 2 :
|
|
42
|
+
return 2;
|
|
43
|
+
case 3 :
|
|
44
|
+
return 3;
|
|
45
|
+
case 4 :
|
|
46
|
+
return 4;
|
|
47
|
+
default:
|
|
48
|
+
return Stdlib_JsError.throwWithMessage(`Unknown ClickHouse column kind ` + ordinal.toString());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function finiteOrThrow(number, column) {
|
|
53
|
+
if (isFinite(number)) {
|
|
54
|
+
return number;
|
|
55
|
+
} else {
|
|
56
|
+
return Stdlib_JsError.throwWithMessage(number.toString() + ` is not a finite number, so it cannot be stored in the \`` + column + `\` column. Store a finite number, or keep it out of the entity.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function jsonSafe(column) {
|
|
61
|
+
return (param, value) => {
|
|
62
|
+
let match = typeof value;
|
|
63
|
+
if (match === "bigint") {
|
|
64
|
+
return String(value);
|
|
65
|
+
}
|
|
66
|
+
if (match !== "object") {
|
|
67
|
+
if (match === "number") {
|
|
68
|
+
finiteOrThrow(value, column);
|
|
69
|
+
return value;
|
|
70
|
+
} else {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let bytes = Utils.Bytes.asUint8Array(value);
|
|
75
|
+
if (bytes !== undefined) {
|
|
76
|
+
return Array.from(bytes);
|
|
77
|
+
} else {
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function makeTable(name, param) {
|
|
84
|
+
let nullable = param.nullable;
|
|
85
|
+
let kinds = param.kinds;
|
|
86
|
+
return {
|
|
87
|
+
handle: param.handle,
|
|
88
|
+
name: name,
|
|
89
|
+
columns: param.names.map((name, index) => ({
|
|
90
|
+
name: name,
|
|
91
|
+
kind: kindOfOrdinal(kinds[index]),
|
|
92
|
+
isNullable: nullable[index],
|
|
93
|
+
replacer: jsonSafe(name)
|
|
94
|
+
}))
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function toText(value, replacer) {
|
|
99
|
+
let match = typeof value;
|
|
100
|
+
if (match === "bigint") {
|
|
101
|
+
return String(value);
|
|
102
|
+
} else if (match === "string") {
|
|
103
|
+
return value;
|
|
104
|
+
} else {
|
|
105
|
+
return JSON.stringify(value, replacer);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let noFloats = new Float64Array(0);
|
|
110
|
+
|
|
111
|
+
let noUnsigned = new BigUint64Array(0);
|
|
112
|
+
|
|
113
|
+
let noSigned = new BigInt64Array(0);
|
|
114
|
+
|
|
115
|
+
let noBytes = new Uint8Array(0);
|
|
116
|
+
|
|
117
|
+
function makeBuilder(param, rows) {
|
|
118
|
+
let kind = param.kind;
|
|
119
|
+
return {
|
|
120
|
+
name: param.name,
|
|
121
|
+
kind: kind,
|
|
122
|
+
isNullable: param.isNullable,
|
|
123
|
+
floats: kind === 0 ? new Float64Array(rows) : noFloats,
|
|
124
|
+
unsigned: kind === 1 ? new BigUint64Array(rows) : noUnsigned,
|
|
125
|
+
signed: kind === 2 ? new BigInt64Array(rows) : noSigned,
|
|
126
|
+
texts: kind === 3 ? Stdlib_Array.make(rows, "") : [],
|
|
127
|
+
bytes: kind === 4 ? Stdlib_Array.make(rows, noBytes) : [],
|
|
128
|
+
replacer: param.replacer,
|
|
129
|
+
nulls: undefined,
|
|
130
|
+
rows: rows
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function markNull(builder, row) {
|
|
135
|
+
let nulls = builder.nulls;
|
|
136
|
+
let nulls$1;
|
|
137
|
+
if (nulls !== undefined) {
|
|
138
|
+
nulls$1 = nulls;
|
|
139
|
+
} else {
|
|
140
|
+
let nulls$2 = new Uint8Array(builder.rows);
|
|
141
|
+
builder.nulls = nulls$2;
|
|
142
|
+
nulls$1 = nulls$2;
|
|
143
|
+
}
|
|
144
|
+
nulls$1[row] = 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function checkedBigInt(value, builder, min, max) {
|
|
148
|
+
let value$1 = BigInt(value);
|
|
149
|
+
if (value$1 < min || value$1 > max) {
|
|
150
|
+
Stdlib_JsError.throwWithMessage(value$1.toString() + ` is out of range for the \`` + builder.name + `\` column`);
|
|
151
|
+
}
|
|
152
|
+
return value$1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function writePresent(builder, row, value) {
|
|
156
|
+
let match = builder.kind;
|
|
157
|
+
switch (match) {
|
|
158
|
+
case 0 :
|
|
159
|
+
builder.floats[row] = finiteOrThrow(Number(value), builder.name);
|
|
160
|
+
return;
|
|
161
|
+
case 1 :
|
|
162
|
+
builder.unsigned[row] = checkedBigInt(value, builder, 0n, 18446744073709551615n);
|
|
163
|
+
return;
|
|
164
|
+
case 2 :
|
|
165
|
+
builder.signed[row] = checkedBigInt(value, builder, -9223372036854775808n, 9223372036854775807n);
|
|
166
|
+
return;
|
|
167
|
+
case 3 :
|
|
168
|
+
builder.texts[row] = toText(value, builder.replacer);
|
|
169
|
+
return;
|
|
170
|
+
case 4 :
|
|
171
|
+
builder.bytes[row] = value;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isAbsent(value) {
|
|
177
|
+
if (value === undefined) {
|
|
178
|
+
return true;
|
|
179
|
+
} else {
|
|
180
|
+
return value === null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function writeValue(builder, row, value) {
|
|
185
|
+
if (isAbsent(value)) {
|
|
186
|
+
if (builder.isNullable) {
|
|
187
|
+
return markNull(builder, row);
|
|
188
|
+
} else {
|
|
189
|
+
return Stdlib_JsError.throwWithMessage(`No value for the \`` + builder.name + `\` column, which is not nullable. Set the field before saving the entity, or make it optional in the schema.`);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
return writePresent(builder, row, value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function writeDeletedValue(builder, row, value) {
|
|
197
|
+
if (isAbsent(value)) {
|
|
198
|
+
return markNull(builder, row);
|
|
199
|
+
} else {
|
|
200
|
+
return writePresent(builder, row, value);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function builderPayload(builder) {
|
|
205
|
+
let base_nulls = builder.nulls;
|
|
206
|
+
let base = {
|
|
207
|
+
nulls: base_nulls
|
|
208
|
+
};
|
|
209
|
+
let match = builder.kind;
|
|
210
|
+
switch (match) {
|
|
211
|
+
case 0 :
|
|
212
|
+
let newrecord = {...base};
|
|
213
|
+
newrecord.numbers = builder.floats;
|
|
214
|
+
return newrecord;
|
|
215
|
+
case 1 :
|
|
216
|
+
let newrecord$1 = {...base};
|
|
217
|
+
newrecord$1.unsigned64 = builder.unsigned;
|
|
218
|
+
return newrecord$1;
|
|
219
|
+
case 2 :
|
|
220
|
+
let newrecord$2 = {...base};
|
|
221
|
+
newrecord$2.signed64 = builder.signed;
|
|
222
|
+
return newrecord$2;
|
|
223
|
+
case 3 :
|
|
224
|
+
let newrecord$3 = {...base};
|
|
225
|
+
newrecord$3.texts = builder.texts;
|
|
226
|
+
return newrecord$3;
|
|
227
|
+
case 4 :
|
|
228
|
+
let newrecord$4 = {...base};
|
|
229
|
+
newrecord$4.bytes = builder.bytes;
|
|
230
|
+
return newrecord$4;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export {
|
|
235
|
+
historySchema,
|
|
236
|
+
make,
|
|
237
|
+
kindOfOrdinal,
|
|
238
|
+
makeTable,
|
|
239
|
+
toText,
|
|
240
|
+
makeBuilder,
|
|
241
|
+
markNull,
|
|
242
|
+
writeValue,
|
|
243
|
+
writeDeletedValue,
|
|
244
|
+
builderPayload,
|
|
245
|
+
}
|
|
246
|
+
/* noFloats Not a pure module */
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
type t
|
|
2
2
|
|
|
3
3
|
module Fetch = {
|
|
4
|
-
type args = {
|
|
4
|
+
type args = {
|
|
5
|
+
body?: unknown,
|
|
6
|
+
headers?: dict<string>,
|
|
7
|
+
method?: string,
|
|
8
|
+
path?: string,
|
|
9
|
+
signal?: unknown,
|
|
10
|
+
}
|
|
5
11
|
type t = (string, ~args: args) => promise<unknown>
|
|
6
12
|
// NOTE: don't try make the type t. Rescript 11 will curry the args which breaks
|
|
7
13
|
// keet the type inline. This is a workaround for now.
|
|
@@ -14,7 +20,7 @@ external create: (~url: string, ~options: options=?) => t = "EventSource"
|
|
|
14
20
|
|
|
15
21
|
@set external onopen: (t, unit => unit) => unit = "onopen"
|
|
16
22
|
|
|
17
|
-
type errorEvent = {
|
|
23
|
+
type errorEvent = {code: option<int>, message: option<string>}
|
|
18
24
|
@set external onerror: (t, errorEvent => unit) => unit = "onerror"
|
|
19
25
|
|
|
20
26
|
type event = {data: string}
|
package/src/bindings/NodeJs.res
CHANGED
|
@@ -72,6 +72,12 @@ module Buffer = {
|
|
|
72
72
|
@val @scope("Buffer") external alloc: int => t = "alloc"
|
|
73
73
|
@get external length: t => int = "length"
|
|
74
74
|
@send external toBase64: (t, @as("base64") _) => string = "toString"
|
|
75
|
+
@val @scope("Buffer") external fromUint8Array: Uint8Array.t => t = "from"
|
|
76
|
+
// A view over the bytes rather than a copy.
|
|
77
|
+
@val @scope("Buffer")
|
|
78
|
+
external fromArrayBuffer: (ArrayBuffer.t, ~byteOffset: int, ~length: int) => t = "from"
|
|
79
|
+
@val @scope("Buffer") external fromHex: (string, @as("hex") _) => t = "from"
|
|
80
|
+
@send external toHex: (t, @as("hex") _) => string = "toString"
|
|
75
81
|
let empty = alloc(0)
|
|
76
82
|
}
|
|
77
83
|
|
package/src/bindings/Vitest.res
CHANGED
|
@@ -169,3 +169,26 @@ let messageOfThrown: (unit => 'a) => option<string> = %raw(`function (fn) {
|
|
|
169
169
|
// happens to equal the placeholder.
|
|
170
170
|
let toThrowErrorEqual = (t: testContext, fn: unit => 'a, ~message=?, expected: string) =>
|
|
171
171
|
t.expect(fn->messageOfThrown, ~message?).toEqual(Some(expected))
|
|
172
|
+
|
|
173
|
+
// ============================================================================
|
|
174
|
+
// Fake timers
|
|
175
|
+
// ============================================================================
|
|
176
|
+
|
|
177
|
+
module Vi = {
|
|
178
|
+
@module("vitest") @scope("vi")
|
|
179
|
+
external useFakeTimers: unit => unit = "useFakeTimers"
|
|
180
|
+
|
|
181
|
+
@module("vitest") @scope("vi")
|
|
182
|
+
external useRealTimers: unit => unit = "useRealTimers"
|
|
183
|
+
|
|
184
|
+
// Runs every timer that comes due within the window, awaiting the
|
|
185
|
+
// microtasks each one queues before moving on.
|
|
186
|
+
@module("vitest") @scope("vi")
|
|
187
|
+
external advanceTimersByTimeAsync: int => promise<unit> = "advanceTimersByTimeAsync"
|
|
188
|
+
|
|
189
|
+
@module("vitest") @scope("vi")
|
|
190
|
+
external getTimerCount: unit => int = "getTimerCount"
|
|
191
|
+
|
|
192
|
+
@module("vitest") @scope("vi")
|
|
193
|
+
external clearAllTimers: unit => unit = "clearAllTimers"
|
|
194
|
+
}
|
|
@@ -7,21 +7,20 @@ type t
|
|
|
7
7
|
|
|
8
8
|
@new external create: string => t = "WebSocket"
|
|
9
9
|
|
|
10
|
-
@unboxed
|
|
11
|
-
type readyState =
|
|
12
|
-
| @as(0) Connecting
|
|
13
|
-
| @as(1) Open
|
|
14
|
-
| @as(2) Closing
|
|
15
|
-
| @as(3) Closed
|
|
16
|
-
|
|
17
|
-
@get external readyState: t => readyState = "readyState"
|
|
18
|
-
|
|
19
10
|
@set external onopen: (t, unit => unit) => unit = "onopen"
|
|
20
11
|
@set external onerror: (t, JsExn.t => unit) => unit = "onerror"
|
|
21
12
|
@set external onclose: (t, unit => unit) => unit = "onclose"
|
|
22
13
|
|
|
23
|
-
|
|
14
|
+
// Node delivers a string for a text frame and a Blob for a binary one, so what
|
|
15
|
+
// arrives is only text when the peer sent text. Typed as a string it would reach
|
|
16
|
+
// a JSON parse — and, once that threw, a log line — as an object, past every
|
|
17
|
+
// length cap written for a string.
|
|
18
|
+
type messageEvent = {data: unknown}
|
|
24
19
|
@set external onmessage: (t, messageEvent => unit) => unit = "onmessage"
|
|
25
20
|
|
|
21
|
+
// Nothing in here may be a runtime value. `create` compiles to `new WebSocket`
|
|
22
|
+
// against the global, and a value would make consumers import this module under
|
|
23
|
+
// that same name, shadowing the global they are trying to construct.
|
|
24
|
+
|
|
26
25
|
@send external send: (t, string) => unit = "send"
|
|
27
26
|
@send external close: t => unit = "close"
|