envio 3.6.1 → 3.7.0-subgraph

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.
Files changed (114) hide show
  1. package/index.d.ts +197 -9
  2. package/package.json +6 -6
  3. package/src/Batch.res +64 -25
  4. package/src/Batch.res.mjs +69 -46
  5. package/src/ChainFetching.res +13 -17
  6. package/src/ChainFetching.res.mjs +9 -14
  7. package/src/ChainState.res +160 -119
  8. package/src/ChainState.res.mjs +75 -46
  9. package/src/ChainState.resi +15 -12
  10. package/src/Config.res +18 -2
  11. package/src/Config.res.mjs +9 -6
  12. package/src/Core.res +20 -0
  13. package/src/Core.res.mjs +11 -0
  14. package/src/Ecosystem.res +0 -1
  15. package/src/Envio.res +16 -3
  16. package/src/EventConfigBuilder.res +155 -40
  17. package/src/EventConfigBuilder.res.mjs +76 -24
  18. package/src/EventProcessing.res +10 -14
  19. package/src/EventProcessing.res.mjs +12 -10
  20. package/src/EventUtils.res +3 -47
  21. package/src/FetchState.res +124 -104
  22. package/src/FetchState.res.mjs +116 -100
  23. package/src/HandlerLoader.res +20 -8
  24. package/src/HandlerLoader.res.mjs +11 -2
  25. package/src/HandlerRegister.res +108 -2
  26. package/src/HandlerRegister.res.mjs +59 -10
  27. package/src/HandlerRegister.resi +4 -0
  28. package/src/InMemoryStore.res +1 -6
  29. package/src/InMemoryStore.res.mjs +1 -3
  30. package/src/Internal.res +84 -24
  31. package/src/Internal.res.mjs +33 -2
  32. package/src/LazyLoader.res +12 -11
  33. package/src/LazyLoader.res.mjs +13 -7
  34. package/src/Main.res +22 -9
  35. package/src/Main.res.mjs +18 -5
  36. package/src/MemoryStorage.res +756 -0
  37. package/src/MemoryStorage.res.mjs +623 -0
  38. package/src/Persistence.res +4 -2
  39. package/src/PgStorage.res +24 -8
  40. package/src/PgStorage.res.mjs +15 -12
  41. package/src/ReorgDetection.res +0 -222
  42. package/src/ReorgDetection.res.mjs +0 -163
  43. package/src/Rollback.res +14 -15
  44. package/src/Rollback.res.mjs +4 -5
  45. package/src/SimulateItems.res +2 -2
  46. package/src/UserContext.res +358 -51
  47. package/src/UserContext.res.mjs +271 -35
  48. package/src/Utils.res +3 -0
  49. package/src/bindings/ClickHouse.res +16 -11
  50. package/src/bindings/ClickHouse.res.mjs +10 -10
  51. package/src/bindings/Vitest.res +1 -1
  52. package/src/sources/BlockStore.res +115 -5
  53. package/src/sources/BlockStore.res.mjs +25 -0
  54. package/src/sources/Evm.res +0 -1
  55. package/src/sources/Evm.res.mjs +0 -1
  56. package/src/sources/EvmHyperSyncSource.res +19 -84
  57. package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
  58. package/src/sources/Fuel.res +24 -4
  59. package/src/sources/Fuel.res.mjs +23 -3
  60. package/src/sources/FuelHyperSync.res +8 -3
  61. package/src/sources/FuelHyperSync.res.mjs +5 -4
  62. package/src/sources/FuelHyperSync.resi +3 -1
  63. package/src/sources/FuelHyperSyncClient.res +7 -10
  64. package/src/sources/FuelHyperSyncSource.res +18 -45
  65. package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
  66. package/src/sources/HyperSync.res +49 -229
  67. package/src/sources/HyperSync.res.mjs +74 -191
  68. package/src/sources/HyperSync.resi +11 -35
  69. package/src/sources/HyperSyncClient.res +9 -79
  70. package/src/sources/HyperSyncClient.res.mjs +2 -6
  71. package/src/sources/RequestStat.res +5 -0
  72. package/src/sources/RequestStat.res.mjs +2 -0
  73. package/src/sources/RpcSource.res +149 -43
  74. package/src/sources/RpcSource.res.mjs +104 -41
  75. package/src/sources/SimulateSource.res +8 -5
  76. package/src/sources/SimulateSource.res.mjs +6 -6
  77. package/src/sources/Source.res +89 -16
  78. package/src/sources/Source.res.mjs +50 -1
  79. package/src/sources/SourceManager.res +255 -50
  80. package/src/sources/SourceManager.res.mjs +149 -55
  81. package/src/sources/SourceManager.resi +2 -6
  82. package/src/sources/Svm.res +0 -7
  83. package/src/sources/Svm.res.mjs +0 -8
  84. package/src/sources/SvmHyperSyncClient.res +21 -16
  85. package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
  86. package/src/sources/SvmHyperSyncSource.res +25 -117
  87. package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
  88. package/src/subgraph/blocks.ts +176 -0
  89. package/src/subgraph/calls.ts +217 -0
  90. package/src/subgraph/conformance.ts +99 -0
  91. package/src/subgraph/division.ts +110 -0
  92. package/src/subgraph/errors.ts +90 -0
  93. package/src/subgraph/graph-ts-types/VERSION +2 -0
  94. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  95. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  96. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  97. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  98. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  99. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  100. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  101. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  102. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  103. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  104. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  105. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  106. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  107. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  108. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  109. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  110. package/src/subgraph/graph-ts.ts +1871 -0
  111. package/src/subgraph/hosts.ts +148 -0
  112. package/src/subgraph/runtime.ts +845 -0
  113. package/src/subgraph/scope.ts +63 -0
  114. package/svm.schema.json +3 -2
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
+ import * as ChainId from "./ChainId.res.mjs";
4
5
  import * as Logging from "./Logging.res.mjs";
5
6
  import * as Internal from "./Internal.res.mjs";
6
7
  import * as Ecosystem from "./Ecosystem.res.mjs";
@@ -12,8 +13,70 @@ import * as InMemoryStore from "./InMemoryStore.res.mjs";
12
13
  import * as InMemoryTable from "./InMemoryTable.res.mjs";
13
14
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
14
15
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
16
+ import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
15
17
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
16
18
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
19
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
20
+
21
+ let Suspend = /* @__PURE__ */Primitive_exceptions.create("UserContext.Suspend");
22
+
23
+ function isSuspend(exn) {
24
+ return exn.RE_EXN_ID === Suspend;
25
+ }
26
+
27
+ function makeSyncState() {
28
+ return {
29
+ status: "Active",
30
+ pending: undefined,
31
+ memo: undefined
32
+ };
33
+ }
34
+
35
+ function getPending(sync) {
36
+ let pending = sync.pending;
37
+ if (pending !== undefined) {
38
+ return pending;
39
+ }
40
+ let pending$1 = [];
41
+ sync.pending = pending$1;
42
+ return pending$1;
43
+ }
44
+
45
+ function getMemo(sync) {
46
+ let memo = sync.memo;
47
+ if (memo !== undefined) {
48
+ return memo;
49
+ }
50
+ let memo$1 = {};
51
+ sync.memo = memo$1;
52
+ return memo$1;
53
+ }
54
+
55
+ function checkStatusOrThrow(params, access) {
56
+ let exn = params.sync.status;
57
+ if (typeof exn !== "object") {
58
+ if (exn === "Active") {
59
+ return;
60
+ } else {
61
+ return ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(params.item, params.config.ecosystem), undefined, new Error(`Impossible to access ` + access + ` after the handler is resolved. Make sure you didn't miss an await in the handler.`));
62
+ }
63
+ }
64
+ throw exn._0;
65
+ }
66
+
67
+ function scheduleAndSuspend(params, promise) {
68
+ getPending(params.sync).push(promise);
69
+ params.sync.status = {
70
+ TAG: "Aborted",
71
+ _0: {
72
+ RE_EXN_ID: Suspend
73
+ }
74
+ };
75
+ throw {
76
+ RE_EXN_ID: Suspend,
77
+ Error: new Error()
78
+ };
79
+ }
17
80
 
18
81
  let paramsByThis = new WeakMap();
19
82
 
@@ -47,28 +110,69 @@ var EffectContext = function(params, chainId, effectName, defaultShouldCache, ca
47
110
  EffectContext.prototype = effectContextPrototype;
48
111
  ;
49
112
 
50
- function initEffect(params) {
51
- let handlerChainId = Internal.getItemChainId(params.item);
113
+ function prepareEffectCall(params, effect, input, caller) {
52
114
  let isCrossChain = effect => Stdlib_Option.getOr(effect.crossChain, params.config.defaultCrossChain);
53
- let makeCaller = caller => ((effect, input) => {
54
- let scope = isCrossChain(effect) ? "crossChain" : handlerChainId;
55
- if (caller !== undefined && isCrossChain(caller) && !isCrossChain(effect)) {
56
- Stdlib_JsError.throwWithMessage(`The cross-chain effect "` + caller.name + `" cannot call the chain-scoped effect "` + effect.name + `", because a cross-chain effect isn't tied to a single chain. Make "` + effect.name + `" cross-chain (\`crossChain: true\`), or make "` + caller.name + `" chain-scoped (\`crossChain: false\`).`);
115
+ let scope = isCrossChain(effect) ? "crossChain" : Internal.getItemChainId(params.item);
116
+ if (caller !== undefined && isCrossChain(caller) && !isCrossChain(effect)) {
117
+ Stdlib_JsError.throwWithMessage(`The cross-chain effect "` + caller.name + `" cannot call the chain-scoped effect "` + effect.name + `", because a cross-chain effect isn't tied to a single chain. Make "` + effect.name + `" cross-chain (\`crossChain: true\`), or make "` + caller.name + `" chain-scoped (\`crossChain: false\`).`);
118
+ }
119
+ let tmp;
120
+ tmp = scope === "crossChain" ? undefined : Primitive_option.some(scope);
121
+ let effectContext = new EffectContext(params, tmp, effect.name, effect.defaultShouldCache, (nested, nestedInput) => callEffectAsync(params, nested, nestedInput, effect));
122
+ let effectArgs_cacheKey = Utils.Hash.makeOrThrow(S$RescriptSchema.reverseConvertOrThrow(input, effect.input));
123
+ let effectArgs_checkpointId = params.checkpointId;
124
+ let effectArgs = {
125
+ input: input,
126
+ context: effectContext,
127
+ cacheKey: effectArgs_cacheKey,
128
+ checkpointId: effectArgs_checkpointId
129
+ };
130
+ return [
131
+ scope,
132
+ effectArgs
133
+ ];
134
+ }
135
+
136
+ function callEffectAsync(params, effect, input, caller) {
137
+ let match = prepareEffectCall(params, effect, input, caller);
138
+ return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, match[1], match[0], params.indexerState, params.isPreload, params.item, params.config.ecosystem);
139
+ }
140
+
141
+ function initEffect(params) {
142
+ return (effect, input) => {
143
+ checkStatusOrThrow(params, "context.effect");
144
+ return callEffectAsync(params, effect, input, undefined);
145
+ };
146
+ }
147
+
148
+ function effectMemoKey(effect, scope, cacheKey) {
149
+ if (scope === "crossChain") {
150
+ return effect.name + `.` + cacheKey;
151
+ } else {
152
+ return effect.name + `.` + ChainId.toString(scope) + `.` + cacheKey;
153
+ }
154
+ }
155
+
156
+ function initEffectSync(params) {
157
+ return (effect, input) => {
158
+ checkStatusOrThrow(params, "context.effectSync");
159
+ let match = prepareEffectCall(params, effect, input, undefined);
160
+ let effectArgs = match[1];
161
+ let scope = match[0];
162
+ let memo = getMemo(params.sync);
163
+ let memoKey = effectMemoKey(effect, scope, effectArgs.cacheKey);
164
+ let output = memo[memoKey];
165
+ if (output !== undefined) {
166
+ return Primitive_option.valFromOption(output);
57
167
  }
58
- let tmp;
59
- tmp = scope === "crossChain" ? undefined : Primitive_option.some(scope);
60
- let effectContext = new EffectContext(params, tmp, effect.name, effect.defaultShouldCache, makeCaller(effect));
61
- let effectArgs_cacheKey = Utils.Hash.makeOrThrow(S$RescriptSchema.reverseConvertOrThrow(input, effect.input));
62
- let effectArgs_checkpointId = params.checkpointId;
63
- let effectArgs = {
64
- input: input,
65
- context: effectContext,
66
- cacheKey: effectArgs_cacheKey,
67
- checkpointId: effectArgs_checkpointId
68
- };
69
- return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, scope, params.indexerState, params.isPreload, params.item, params.config.ecosystem);
70
- });
71
- return makeCaller(undefined);
168
+ let inMemTable = InMemoryStore.getEffectInMemTable(params.indexerState, effect, scope);
169
+ if (!InMemoryStore.hasEffectOutput(inMemTable, effectArgs.cacheKey)) {
170
+ return scheduleAndSuspend(params, LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, scope, params.indexerState, params.isPreload, params.item, params.config.ecosystem));
171
+ }
172
+ let output$1 = InMemoryStore.getEffectOutputUnsafe(inMemTable, effectArgs.cacheKey);
173
+ memo[memoKey] = output$1;
174
+ return output$1;
175
+ };
72
176
  }
73
177
 
74
178
  function entityScope(params) {
@@ -97,24 +201,83 @@ function throwClickHouseReadOnly(entityConfig, op) {
97
201
  return Stdlib_JsError.throwWithMessage(`context.` + entityConfig.name + `.` + op + `() is unavailable: ClickHouse storage is currently write-only. Follow Envio releases to be notified when ClickHouse supports both reads and writes from handlers.`);
98
202
  }
99
203
 
204
+ function getSyncHandler(params, entityId) {
205
+ let inMemTable = InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, entityScope(params));
206
+ if (entityId in inMemTable.latestEntityChangeById) {
207
+ return InMemoryTable.Entity.getUnsafe(inMemTable)(entityId);
208
+ } else {
209
+ return scheduleAndSuspend(params, LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, entityId));
210
+ }
211
+ }
212
+
213
+ function getWhereSyncHandler(params, filter) {
214
+ let entityConfig = params.entityConfig;
215
+ let inMemTable = InMemoryStore.getInMemTable(params.indexerState, entityConfig, entityScope(params));
216
+ let hasIndex = InMemoryTable.Entity.hasIndex(inMemTable);
217
+ let getOnIndex = InMemoryTable.Entity.getUnsafeOnIndex(inMemTable);
218
+ let filters = EntityFilter.parseGetWhereOrThrow(filter, entityConfig.name, entityConfig.table);
219
+ let missing = [];
220
+ let entities = [];
221
+ filters.forEach(filter => {
222
+ let filterKey = EntityFilter.toString(filter);
223
+ if (hasIndex(filterKey)) {
224
+ entities.push(...getOnIndex(filterKey));
225
+ } else {
226
+ missing.push(LoadLayer.loadByFilter(params.loadManager, params.persistence, entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, filter));
227
+ }
228
+ });
229
+ if (Utils.$$Array.notEmpty(missing)) {
230
+ let pending = getPending(params.sync);
231
+ missing.forEach(promise => {
232
+ pending.push(promise);
233
+ });
234
+ params.sync.status = {
235
+ TAG: "Aborted",
236
+ _0: {
237
+ RE_EXN_ID: Suspend
238
+ }
239
+ };
240
+ throw {
241
+ RE_EXN_ID: Suspend,
242
+ Error: new Error()
243
+ };
244
+ }
245
+ return entities;
246
+ }
247
+
248
+ function getInBlockSyncHandler(params, entityId) {
249
+ let inMemTable = InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, entityScope(params));
250
+ let change = inMemTable.latestEntityChangeById[entityId];
251
+ if (change !== undefined && change.checkpointId === params.checkpointId) {
252
+ return InMemoryTable.Entity.mapChangeToEntity(change);
253
+ }
254
+ }
255
+
100
256
  let entityTraps_get = (params, prop) => {
257
+ checkStatusOrThrow(params, `context.` + params.entityConfig.name + `.` + prop);
101
258
  let isClickHouseOnly = !params.entityConfig.storage.postgres;
102
- let set = params.isPreload ? noopSet : entity => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, entityScope(params)), IndexerState.committedCheckpointId(params.indexerState), {
103
- type: "SET",
104
- entityId: entity.id,
105
- entity: entity,
106
- checkpointId: params.checkpointId
107
- });
259
+ let set = params.isPreload ? noopSet : entity => {
260
+ checkStatusOrThrow(params, `context.` + params.entityConfig.name + `.set`);
261
+ InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, entityScope(params)), IndexerState.committedCheckpointId(params.indexerState), {
262
+ type: "SET",
263
+ entityId: entity.id,
264
+ entity: entity,
265
+ checkpointId: params.checkpointId
266
+ });
267
+ };
108
268
  switch (prop) {
109
269
  case "deleteUnsafe" :
110
270
  if (params.isPreload) {
111
271
  return noopDeleteUnsafe;
112
272
  } else {
113
- return entityId => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, entityScope(params)), IndexerState.committedCheckpointId(params.indexerState), {
114
- type: "DELETE",
115
- entityId: entityId,
116
- checkpointId: params.checkpointId
117
- });
273
+ return entityId => {
274
+ checkStatusOrThrow(params, `context.` + params.entityConfig.name + `.deleteUnsafe`);
275
+ InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, entityScope(params)), IndexerState.committedCheckpointId(params.indexerState), {
276
+ type: "DELETE",
277
+ entityId: entityId,
278
+ checkpointId: params.checkpointId
279
+ });
280
+ };
118
281
  }
119
282
  case "get" :
120
283
  if (isClickHouseOnly) {
@@ -122,6 +285,8 @@ let entityTraps_get = (params, prop) => {
122
285
  } else {
123
286
  return entityId => LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, entityId);
124
287
  }
288
+ case "getInBlockSync" :
289
+ return entityId => getInBlockSyncHandler(params, entityId);
125
290
  case "getOrCreate" :
126
291
  if (isClickHouseOnly) {
127
292
  return _entity => throwClickHouseReadOnly(params.entityConfig, "getOrCreate");
@@ -147,12 +312,16 @@ let entityTraps_get = (params, prop) => {
147
312
  }
148
313
  });
149
314
  }
315
+ case "getSync" :
316
+ return entityId => getSyncHandler(params, entityId);
150
317
  case "getWhere" :
151
318
  if (isClickHouseOnly) {
152
319
  return _filter => throwClickHouseReadOnly(params.entityConfig, "getWhere");
153
320
  } else {
154
321
  return filter => getWhereHandler(params, filter);
155
322
  }
323
+ case "getWhereSync" :
324
+ return filter => getWhereSyncHandler(params, filter);
156
325
  case "set" :
157
326
  return set;
158
327
  default:
@@ -164,16 +333,63 @@ let entityTraps = {
164
333
  get: entityTraps_get
165
334
  };
166
335
 
167
- let handlerTraps_get = (params, prop) => {
168
- if (params.isResolved) {
169
- ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(params.item, params.config.ecosystem), undefined, new Error(`Impossible to access context.` + prop + ` after the handler is resolved. Make sure you didn't miss an await in the handler.`));
336
+ async function runSyncRound(params, fn, round) {
337
+ if (round > 10000) {
338
+ Stdlib_JsError.throwWithMessage(`The handler suspended on a synchronous read too many times: gave up after ` + (10000).toString() + ` rounds. This usually means the code isn't deterministic across reruns.`);
339
+ }
340
+ params.sync.status = "Active";
341
+ params.sync.pending = undefined;
342
+ let suspended;
343
+ try {
344
+ fn();
345
+ suspended = false;
346
+ } catch (raw_exn) {
347
+ let exn = Primitive_exceptions.internalToException(raw_exn);
348
+ if (exn.RE_EXN_ID === Suspend) {
349
+ suspended = true;
350
+ } else {
351
+ let pending = params.sync.pending;
352
+ if (pending !== undefined) {
353
+ params.sync.pending = undefined;
354
+ pending.forEach(promise => {
355
+ Utils.$$Promise.silentCatch(promise);
356
+ });
357
+ }
358
+ throw exn;
359
+ }
170
360
  }
361
+ let pending$1 = params.sync.pending;
362
+ if (pending$1 === undefined) {
363
+ return;
364
+ }
365
+ params.sync.pending = undefined;
366
+ if (suspended) {
367
+ let errors = [];
368
+ await Promise.all(pending$1.map(promise => Stdlib_Promise.$$catch(promise, exn => {
369
+ errors.push(exn);
370
+ return Promise.resolve();
371
+ })));
372
+ let exn$1 = errors[0];
373
+ if (exn$1 !== undefined) {
374
+ throw exn$1;
375
+ }
376
+ return await runSyncRound(params, fn, round + 1 | 0);
377
+ }
378
+ pending$1.forEach(promise => {
379
+ Utils.$$Promise.silentCatch(promise);
380
+ });
381
+ }
382
+
383
+ let handlerTraps_get = (params, prop) => {
384
+ checkStatusOrThrow(params, `context.` + prop);
171
385
  switch (prop) {
172
386
  case "chain" :
173
387
  let chainId = Internal.getItemChainId(params.item);
174
388
  return params.chains[chainId];
175
389
  case "effect" :
176
390
  return initEffect(params);
391
+ case "effectSync" :
392
+ return initEffectSync(params);
177
393
  case "isPreload" :
178
394
  return params.isPreload;
179
395
  case "log" :
@@ -182,6 +398,8 @@ let handlerTraps_get = (params, prop) => {
182
398
  } else {
183
399
  return Ecosystem.getItemUserLogger(params.item, params.config.ecosystem);
184
400
  }
401
+ case "runSync" :
402
+ return fn => runSyncRound(params, fn, 1);
185
403
  default:
186
404
  let entityConfig = params.config.userEntitiesByName[prop];
187
405
  if (entityConfig !== undefined) {
@@ -194,7 +412,7 @@ let handlerTraps_get = (params, prop) => {
194
412
  isPreload: params.isPreload,
195
413
  chains: params.chains,
196
414
  config: params.config,
197
- isResolved: params.isResolved,
415
+ sync: params.sync,
198
416
  entityConfig: entityConfig
199
417
  }, entityTraps);
200
418
  } else {
@@ -211,16 +429,34 @@ function getHandlerContext(params) {
211
429
  return new Proxy(params, handlerTraps);
212
430
  }
213
431
 
432
+ let maxSyncRounds = 10000;
433
+
214
434
  export {
435
+ Suspend,
436
+ isSuspend,
437
+ makeSyncState,
438
+ getPending,
439
+ getMemo,
440
+ checkStatusOrThrow,
441
+ scheduleAndSuspend,
215
442
  paramsByThis,
216
443
  effectContextPrototype,
444
+ prepareEffectCall,
445
+ callEffectAsync,
217
446
  initEffect,
447
+ effectMemoKey,
448
+ initEffectSync,
218
449
  entityScope,
219
450
  getWhereHandler,
220
451
  noopSet,
221
452
  noopDeleteUnsafe,
222
453
  throwClickHouseReadOnly,
454
+ getSyncHandler,
455
+ getWhereSyncHandler,
456
+ getInBlockSyncHandler,
223
457
  entityTraps,
458
+ maxSyncRounds,
459
+ runSyncRound,
224
460
  handlerTraps,
225
461
  getHandlerContext,
226
462
  }
package/src/Utils.res CHANGED
@@ -576,6 +576,9 @@ module Set = {
576
576
  @send
577
577
  external intersection: (t<'value>, t<'value>) => t<'value> = "intersection"
578
578
 
579
+ @send
580
+ external union: (t<'value>, t<'value>) => t<'value> = "union"
581
+
579
582
  let immutableAdd: (t<'a>, 'a) => t<'a> = %raw(`(set, value) => {
580
583
  return new Set([...set, value])
581
584
  }`)
@@ -14,8 +14,13 @@ type execParams = {query: string}
14
14
  @module("@clickhouse/client")
15
15
  external createClient: clientConfig => client = "createClient"
16
16
 
17
+ // `command`, not `exec`: exec hands its response stream to the caller and holds
18
+ // the socket until it is consumed, which nothing here does. The pool is 10
19
+ // sockets wide, so a schema whose DDL needs more than that — initialize issues
20
+ // 2N+3 statements for N entities — stalled every statement past the tenth until
21
+ // the 30s request timeout freed one. command destroys the stream for us.
17
22
  @send
18
- external exec: (client, execParams) => promise<unit> = "exec"
23
+ external command: (client, execParams) => promise<unit> = "command"
19
24
 
20
25
  @send
21
26
  external close: client => promise<unit> = "close"
@@ -628,21 +633,21 @@ let initialize = async (
628
633
  // CLUSTER removes the database from every node — the engine's own log
629
634
  // can't replicate the drop of the database it lives in — and SYNC waits
630
635
  // for the drop to finish before the CREATE below.
631
- await client->exec({
636
+ await client->command({
632
637
  query: `DROP DATABASE IF EXISTS ${database} ON CLUSTER '{cluster}' SYNC`,
633
638
  })
634
639
  } else {
635
- await client->exec({
640
+ await client->command({
636
641
  query: `TRUNCATE DATABASE IF EXISTS ${database}${onClusterClause(~onCluster=ddlOnCluster)}`,
637
642
  })
638
643
  }
639
- await client->exec({
644
+ await client->command({
640
645
  query: `CREATE DATABASE IF NOT EXISTS ${database}${databaseOnClusterClause}${databaseEngineClause}`,
641
646
  })
642
647
 
643
648
  await Promise.all(
644
649
  entities->Array.map(entityConfig =>
645
- client->exec({
650
+ client->command({
646
651
  query: makeCreateHistoryTableQuery(
647
652
  ~entityConfig,
648
653
  ~database,
@@ -653,7 +658,7 @@ let initialize = async (
653
658
  })
654
659
  ),
655
660
  )->Utils.Promise.ignoreValue
656
- await client->exec({
661
+ await client->command({
657
662
  query: makeCreateCheckpointsTableQuery(
658
663
  ~database,
659
664
  ~replicated,
@@ -670,14 +675,14 @@ let initialize = async (
670
675
  // caught up before creating the views. ON CLUSTER must precede the
671
676
  // database name in this command's grammar.
672
677
  if hasReplicatedDatabaseEngine {
673
- await client->exec({
678
+ await client->command({
674
679
  query: `SYSTEM SYNC DATABASE REPLICA ON CLUSTER '{cluster}' ${database}`,
675
680
  })
676
681
  }
677
682
 
678
683
  await Promise.all(
679
684
  entities->Array.map(entityConfig =>
680
- client->exec({
685
+ client->command({
681
686
  query: makeCreateViewQuery(~entityConfig, ~database, ~onCluster=ddlOnCluster),
682
687
  })
683
688
  ),
@@ -697,7 +702,7 @@ let resume = async (client, ~database: string, ~checkpointId: Internal.checkpoin
697
702
  try {
698
703
  // Try to use the database - will throw if it doesn't exist
699
704
  try {
700
- await client->exec({query: `USE ${database}`})
705
+ await client->command({query: `USE ${database}`})
701
706
  } catch {
702
707
  | exn =>
703
708
  Logging.errorWithExn(
@@ -717,14 +722,14 @@ let resume = async (client, ~database: string, ~checkpointId: Internal.checkpoin
717
722
  await Promise.all(
718
723
  tables->Array.map(table => {
719
724
  let tableName = table["name"]
720
- client->exec({
725
+ client->command({
721
726
  query: `ALTER TABLE ${database}.\`${tableName}\` DELETE WHERE \`${EntityHistory.checkpointIdFieldName}\` > ${checkpointId->BigInt.toString}`,
722
727
  })
723
728
  }),
724
729
  )->Utils.Promise.ignoreValue
725
730
 
726
731
  // Delete stale checkpoints
727
- await client->exec({
732
+ await client->command({
728
733
  query: `DELETE FROM ${database}.\`${InternalTable.Checkpoints.table.tableName}\` WHERE \`${Table.idFieldName}\` > ${checkpointId->BigInt.toString}`,
729
734
  })
730
735
  } catch {
@@ -428,31 +428,31 @@ async function initialize(client, database, entities, param, chainIdModeOpt) {
428
428
  }
429
429
  }
430
430
  if (hasReplicatedDatabaseEngine) {
431
- await client.exec({
431
+ await client.command({
432
432
  query: `DROP DATABASE IF EXISTS ` + database + ` ON CLUSTER '{cluster}' SYNC`
433
433
  });
434
434
  } else {
435
- await client.exec({
435
+ await client.command({
436
436
  query: `TRUNCATE DATABASE IF EXISTS ` + database + (
437
437
  ddlOnCluster ? ` ON CLUSTER '{cluster}'` : ""
438
438
  )
439
439
  });
440
440
  }
441
- await client.exec({
441
+ await client.command({
442
442
  query: `CREATE DATABASE IF NOT EXISTS ` + database + databaseOnClusterClause + databaseEngineClause
443
443
  });
444
- await Promise.all(entities.map(entityConfig => client.exec({
444
+ await Promise.all(entities.map(entityConfig => client.command({
445
445
  query: makeCreateHistoryTableQuery(entityConfig, database, replicated, ddlOnCluster, chainIdMode)
446
446
  })));
447
- await client.exec({
447
+ await client.command({
448
448
  query: makeCreateCheckpointsTableQuery(database, replicated, ddlOnCluster, chainIdMode)
449
449
  });
450
450
  if (hasReplicatedDatabaseEngine) {
451
- await client.exec({
451
+ await client.command({
452
452
  query: `SYSTEM SYNC DATABASE REPLICA ON CLUSTER '{cluster}' ` + database
453
453
  });
454
454
  }
455
- await Promise.all(entities.map(entityConfig => client.exec({
455
+ await Promise.all(entities.map(entityConfig => client.command({
456
456
  query: makeCreateViewQuery(entityConfig, database, ddlOnCluster)
457
457
  })));
458
458
  return Logging.trace("ClickHouse storage initialization completed successfully");
@@ -466,7 +466,7 @@ async function initialize(client, database, entities, param, chainIdModeOpt) {
466
466
  async function resume(client, database, checkpointId) {
467
467
  try {
468
468
  try {
469
- await client.exec({
469
+ await client.command({
470
470
  query: `USE ` + database
471
471
  });
472
472
  } catch (raw_exn) {
@@ -480,11 +480,11 @@ async function resume(client, database, checkpointId) {
480
480
  let tables = (await tablesResult.json()).data;
481
481
  await Promise.all(tables.map(table => {
482
482
  let tableName = table.name;
483
- return client.exec({
483
+ return client.command({
484
484
  query: `ALTER TABLE ` + database + `.\`` + tableName + `\` DELETE WHERE \`` + EntityHistory.checkpointIdFieldName + `\` > ` + checkpointId.toString()
485
485
  });
486
486
  }));
487
- return await client.exec({
487
+ return await client.command({
488
488
  query: `DELETE FROM ` + database + `.\`` + InternalTable.Checkpoints.table.tableName + `\` WHERE \`` + Table.idFieldName + `\` > ` + checkpointId.toString()
489
489
  });
490
490
  } catch (raw_exn$1) {
@@ -98,7 +98,7 @@ external afterEach: (unit => unit) => unit = "afterEach"
98
98
  // Async Module
99
99
  // ============================================================================
100
100
 
101
- type options = {retry?: int}
101
+ type options = {retry?: int, timeout?: int}
102
102
 
103
103
  module Async = {
104
104
  @module("vitest")