envio 3.1.0-rc.1 → 3.1.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/index.d.ts +12 -0
- package/package.json +6 -6
- package/src/Batch.res +7 -1
- package/src/Batch.res.mjs +2 -1
- package/src/ChainManager.res +3 -2
- package/src/ChainManager.res.mjs +3 -3
- package/src/Env.res +6 -0
- package/src/Env.res.mjs +3 -0
- package/src/EventProcessing.res +24 -122
- package/src/EventProcessing.res.mjs +24 -88
- package/src/GlobalState.res +31 -52
- package/src/GlobalState.res.mjs +54 -33
- package/src/GlobalStateManager.res +1 -3
- package/src/InMemoryStore.res +408 -110
- package/src/InMemoryStore.res.mjs +335 -84
- package/src/InMemoryTable.res +49 -30
- package/src/InMemoryTable.res.mjs +31 -39
- package/src/Internal.res +3 -0
- package/src/LoadLayer.res +9 -7
- package/src/LoadLayer.res.mjs +4 -10
- package/src/Main.res +31 -2
- package/src/Main.res.mjs +19 -5
- package/src/Persistence.res +6 -1
- package/src/PgStorage.res +171 -68
- package/src/PgStorage.res.mjs +125 -39
- package/src/RollbackCommit.res +32 -0
- package/src/RollbackCommit.res.mjs +35 -0
- package/src/TestIndexerProxyStorage.res +1 -1
- package/src/TestIndexerProxyStorage.res.mjs +1 -1
- package/src/Throttler.res +22 -15
- package/src/Throttler.res.mjs +19 -14
- package/src/UserContext.res +1 -0
- package/src/UserContext.res.mjs +3 -1
- package/src/bindings/NodeJs.res +1 -0
|
@@ -53,49 +53,41 @@ function make() {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function
|
|
57
|
-
let
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
function snapshotChanges(self, committedCheckpointId, upToCheckpointId) {
|
|
57
|
+
let changes = [];
|
|
58
|
+
let keptPrev = [];
|
|
59
|
+
self.prevEntityChanges.forEach(change => {
|
|
60
|
+
if (change.checkpointId > upToCheckpointId) {
|
|
61
|
+
keptPrev.push(change);
|
|
62
|
+
} else {
|
|
63
|
+
changes.push(change);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
self.prevEntityChanges = keptPrev;
|
|
67
|
+
self.changesCount = self.changesCount - changes.length;
|
|
68
|
+
Utils.Dict.forEach(self.latestEntityChangeById, change => {
|
|
69
|
+
let checkpointId = change.checkpointId;
|
|
70
|
+
if (checkpointId > committedCheckpointId && checkpointId <= upToCheckpointId) {
|
|
71
|
+
changes.push(change);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return changes;
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
function
|
|
74
|
-
let
|
|
75
|
-
let keptCount = {
|
|
76
|
-
contents: 0
|
|
77
|
-
};
|
|
78
|
+
function dropCommittedChanges(self, committedCheckpointId, keepLoadedFromDb) {
|
|
79
|
+
let keysToDelete = [];
|
|
78
80
|
Utils.Dict.forEachWithKey(self.latestEntityChangeById, (change, key) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
let checkpointId = change.checkpointId;
|
|
82
|
+
if (checkpointId <= committedCheckpointId && !(keepLoadedFromDb && checkpointId === Internal.loadedFromDbCheckpointId)) {
|
|
83
|
+
keysToDelete.push(key);
|
|
82
84
|
return;
|
|
83
85
|
}
|
|
84
86
|
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
indicesByEntityId: {},
|
|
90
|
-
fieldNameIndices: {}
|
|
91
|
-
};
|
|
92
|
-
return {
|
|
93
|
-
latestEntityChangeById: latestEntityChangeById,
|
|
94
|
-
changesCount: keptCount.contents,
|
|
95
|
-
prevEntityChanges: init.prevEntityChanges,
|
|
96
|
-
indicesByEntityId: init.indicesByEntityId,
|
|
97
|
-
fieldNameIndices: init.fieldNameIndices
|
|
98
|
-
};
|
|
87
|
+
keysToDelete.forEach(key => Utils.Dict.deleteInPlace(self.latestEntityChangeById, key));
|
|
88
|
+
self.changesCount = self.changesCount - keysToDelete.length;
|
|
89
|
+
self.indicesByEntityId = {};
|
|
90
|
+
self.fieldNameIndices = {};
|
|
99
91
|
}
|
|
100
92
|
|
|
101
93
|
function updateIndices(self, entity) {
|
|
@@ -259,8 +251,8 @@ let Entity = {
|
|
|
259
251
|
getOrCreateEntityIndices: getOrCreateEntityIndices,
|
|
260
252
|
makeIndicesSerializedToValue: makeIndicesSerializedToValue,
|
|
261
253
|
make: make,
|
|
262
|
-
|
|
263
|
-
|
|
254
|
+
snapshotChanges: snapshotChanges,
|
|
255
|
+
dropCommittedChanges: dropCommittedChanges,
|
|
264
256
|
updateIndices: updateIndices,
|
|
265
257
|
deleteEntityFromIndices: deleteEntityFromIndices,
|
|
266
258
|
set: set,
|
package/src/Internal.res
CHANGED
|
@@ -537,6 +537,9 @@ type effectArgs = {
|
|
|
537
537
|
input: effectInput,
|
|
538
538
|
context: effectContext,
|
|
539
539
|
cacheKey: string,
|
|
540
|
+
// The processing checkpoint that referenced this effect; stamped on the
|
|
541
|
+
// in-memory cache entry so it's evicted once the checkpoint commits.
|
|
542
|
+
checkpointId: bigint,
|
|
540
543
|
}
|
|
541
544
|
type effectCacheItem = {id: string, output: effectOutput}
|
|
542
545
|
type effectCacheStorageMeta = {
|
package/src/LoadLayer.res
CHANGED
|
@@ -89,10 +89,12 @@ let callEffect = (
|
|
|
89
89
|
|
|
90
90
|
effect.handler(arg)
|
|
91
91
|
->Promise.thenResolve(output => {
|
|
92
|
-
inMemTable
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
inMemTable->InMemoryStore.setEffectOutput(
|
|
93
|
+
~checkpointId=arg.checkpointId,
|
|
94
|
+
~cacheKey=arg.cacheKey,
|
|
95
|
+
~output,
|
|
96
|
+
~shouldCache=arg.context.cache,
|
|
97
|
+
)
|
|
96
98
|
})
|
|
97
99
|
->Utils.Promise.catchResolve(exn => {
|
|
98
100
|
onError(~inputKey=arg.cacheKey, ~exn)
|
|
@@ -283,7 +285,7 @@ let loadEffect = (
|
|
|
283
285
|
try {
|
|
284
286
|
let output = dbEntity.output->S.parseOrThrow(outputSchema)
|
|
285
287
|
idsFromCache->Utils.Set.add(dbEntity.id)->ignore
|
|
286
|
-
inMemTable
|
|
288
|
+
inMemTable->InMemoryStore.initEffectOutputFromDb(~cacheKey=dbEntity.id, ~output)
|
|
287
289
|
} catch {
|
|
288
290
|
| S.Raised(error) =>
|
|
289
291
|
inMemTable.invalidationsCount = inMemTable.invalidationsCount + 1
|
|
@@ -334,8 +336,8 @@ let loadEffect = (
|
|
|
334
336
|
~load,
|
|
335
337
|
~shouldGroup,
|
|
336
338
|
~hasher=args => args.cacheKey,
|
|
337
|
-
~getUnsafeInMemory=hash => inMemTable
|
|
338
|
-
~hasInMemory=hash => inMemTable
|
|
339
|
+
~getUnsafeInMemory=hash => inMemTable->InMemoryStore.getEffectOutput(hash)->Option.getUnsafe,
|
|
340
|
+
~hasInMemory=hash => inMemTable->InMemoryStore.getEffectOutput(hash)->Option.isSome,
|
|
339
341
|
~input=effectArgs,
|
|
340
342
|
)
|
|
341
343
|
}
|
package/src/LoadLayer.res.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import * as TableIndices from "./TableIndices.res.mjs";
|
|
|
12
12
|
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
13
13
|
import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
14
14
|
import * as InMemoryTable from "./InMemoryTable.res.mjs";
|
|
15
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
15
16
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
16
17
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
17
18
|
|
|
@@ -55,13 +56,7 @@ function callEffect(effect, arg, inMemTable, timerRef, onError) {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
effect.prevCallStartTimerRef = timerRef;
|
|
58
|
-
return effect.handler(arg).then(output => {
|
|
59
|
-
inMemTable.dict[arg.cacheKey] = output;
|
|
60
|
-
if (arg.context.cache) {
|
|
61
|
-
inMemTable.idsToStore.push(arg.cacheKey);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
}).catch(exn => onError(arg.cacheKey, exn)).finally(() => {
|
|
59
|
+
return effect.handler(arg).then(output => InMemoryStore.setEffectOutput(inMemTable, arg.checkpointId, arg.cacheKey, output, arg.context.cache)).catch(exn => onError(arg.cacheKey, exn)).finally(() => {
|
|
65
60
|
effect.activeCallsCount = effect.activeCallsCount - 1 | 0;
|
|
66
61
|
Prometheus.SafeGauge.handleInt(Prometheus.EffectCalls.activeCallsCount, effectName, effect.activeCallsCount);
|
|
67
62
|
let newTimer = Hrtime.makeTimer();
|
|
@@ -159,8 +154,7 @@ function loadEffect(loadManager, persistence, effect, effectArgs, inMemoryStore,
|
|
|
159
154
|
try {
|
|
160
155
|
let output = S$RescriptSchema.parseOrThrow(dbEntity.output, outputSchema);
|
|
161
156
|
idsFromCache.add(dbEntity.id);
|
|
162
|
-
inMemTable
|
|
163
|
-
return;
|
|
157
|
+
return InMemoryStore.initEffectOutputFromDb(inMemTable, dbEntity.id, output);
|
|
164
158
|
} catch (raw_error) {
|
|
165
159
|
let error = Primitive_exceptions.internalToException(raw_error);
|
|
166
160
|
if (error.RE_EXN_ID === S$RescriptSchema.Raised) {
|
|
@@ -193,7 +187,7 @@ function loadEffect(loadManager, persistence, effect, effectArgs, inMemoryStore,
|
|
|
193
187
|
return await executeWithRateLimit(effect, argsToCall, inMemTable, onError, false);
|
|
194
188
|
}
|
|
195
189
|
};
|
|
196
|
-
return LoadManager.call(loadManager, effectArgs, key, load, args => args.cacheKey, shouldGroup, hash =>
|
|
190
|
+
return LoadManager.call(loadManager, effectArgs, key, load, args => args.cacheKey, shouldGroup, hash => Stdlib_Option.isSome(InMemoryStore.getEffectOutput(inMemTable, hash)), hash => InMemoryStore.getEffectOutput(inMemTable, hash));
|
|
197
191
|
}
|
|
198
192
|
|
|
199
193
|
function loadByField(loadManager, persistence, operator, entityConfig, inMemoryStore, fieldName, fieldValueSchema, shouldGroup, item, fieldValue) {
|
package/src/Main.res
CHANGED
|
@@ -312,6 +312,13 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
312
312
|
)
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
let onRollbackCommitFn = (callback: 'a) => {
|
|
316
|
+
HandlerRegister.throwIfFinishedRegistration(
|
|
317
|
+
~methodName="~internalAndWillBeRemovedSoon_onRollbackCommit",
|
|
318
|
+
)
|
|
319
|
+
let _ = RollbackCommit.register(callback->(Utils.magic: 'a => RollbackCommit.callback))
|
|
320
|
+
}
|
|
321
|
+
|
|
315
322
|
// Two-stage parse: first the ecosystem-specific outer schema unwraps the
|
|
316
323
|
// wrapper (`block.number` / `block.height` / `slot`) and surfaces the
|
|
317
324
|
// inner chunk as raw `unknown`; then the shared `blockRangeSchema`
|
|
@@ -453,8 +460,16 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
453
460
|
"onEvent",
|
|
454
461
|
"contractRegister",
|
|
455
462
|
"onBlock",
|
|
463
|
+
"~internalAndWillBeRemovedSoon_onRollbackCommit",
|
|
464
|
+
]
|
|
465
|
+
| Svm => [
|
|
466
|
+
"name",
|
|
467
|
+
"description",
|
|
468
|
+
"chainIds",
|
|
469
|
+
"chains",
|
|
470
|
+
"onSlot",
|
|
471
|
+
"~internalAndWillBeRemovedSoon_onRollbackCommit",
|
|
456
472
|
]
|
|
457
|
-
| Svm => ["name", "description", "chainIds", "chains", "onSlot"]
|
|
458
473
|
}
|
|
459
474
|
keysMemo := Some(keys)
|
|
460
475
|
keys
|
|
@@ -477,6 +492,7 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
477
492
|
| "onEvent" => onEventFn->Utils.magic
|
|
478
493
|
| "contractRegister" => contractRegisterFn->Utils.magic
|
|
479
494
|
| "onBlock" | "onSlot" => onBlockFn->Utils.magic
|
|
495
|
+
| "~internalAndWillBeRemovedSoon_onRollbackCommit" => onRollbackCommitFn->Utils.magic
|
|
480
496
|
| _ =>
|
|
481
497
|
JsError.throwWithMessage(
|
|
482
498
|
`Field \`${prop}\` does not exist on \`indexer\`. Available fields: ${getKeys()->Array.join(
|
|
@@ -686,6 +702,12 @@ let start = async (
|
|
|
686
702
|
| Some(patchConfig) => patchConfig(config, registrations)
|
|
687
703
|
| None => config
|
|
688
704
|
}
|
|
705
|
+
// The single fatal-error handler, shared by the store, ErrorExit, and the
|
|
706
|
+
// manager's catch.
|
|
707
|
+
let onError = (errHandler: ErrorHandling.t) => {
|
|
708
|
+
errHandler->ErrorHandling.log
|
|
709
|
+
NodeJs.process->NodeJs.exitWithCode(Failure)
|
|
710
|
+
}
|
|
689
711
|
let ctx = {
|
|
690
712
|
Ctx.registrations,
|
|
691
713
|
config,
|
|
@@ -693,6 +715,9 @@ let start = async (
|
|
|
693
715
|
inMemoryStore: InMemoryStore.make(
|
|
694
716
|
~entities=persistence.allEntities,
|
|
695
717
|
~committedCheckpointId=(persistence->Persistence.getInitializedState).checkpointId,
|
|
718
|
+
~persistence,
|
|
719
|
+
~config,
|
|
720
|
+
~onError=exn => onError(exn->ErrorHandling.make(~msg="Failed writing batch to the database")),
|
|
696
721
|
),
|
|
697
722
|
}
|
|
698
723
|
|
|
@@ -763,8 +788,12 @@ let start = async (
|
|
|
763
788
|
~isDevelopmentMode,
|
|
764
789
|
~shouldUseTui,
|
|
765
790
|
~exitAfterFirstEventBlock,
|
|
791
|
+
~onError,
|
|
766
792
|
)
|
|
767
|
-
let gsManager =
|
|
793
|
+
let gsManager =
|
|
794
|
+
globalState->GlobalStateManager.make(~onError=exn =>
|
|
795
|
+
onError(exn->ErrorHandling.make(~msg="Indexer has failed with an unexpected error"))
|
|
796
|
+
)
|
|
768
797
|
if shouldUseTui {
|
|
769
798
|
let _rerender = Tui.start(~getState=() => gsManager->GlobalStateManager.getState)
|
|
770
799
|
}
|
package/src/Main.res.mjs
CHANGED
|
@@ -18,12 +18,14 @@ import * as PromClient from "prom-client";
|
|
|
18
18
|
import Yargs from "yargs/yargs";
|
|
19
19
|
import * as ChainFetcher from "./ChainFetcher.res.mjs";
|
|
20
20
|
import * as ChainManager from "./ChainManager.res.mjs";
|
|
21
|
+
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
21
22
|
import * as HandlerLoader from "./HandlerLoader.res.mjs";
|
|
22
23
|
import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
23
24
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
24
25
|
import * as SourceManager from "./sources/SourceManager.res.mjs";
|
|
25
26
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
26
27
|
import * as Helpers from "yargs/helpers";
|
|
28
|
+
import * as RollbackCommit from "./RollbackCommit.res.mjs";
|
|
27
29
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
28
30
|
import * as HandlerRegister from "./HandlerRegister.res.mjs";
|
|
29
31
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
@@ -254,6 +256,10 @@ function getGlobalIndexer() {
|
|
|
254
256
|
let match = parseIdentityConfig(identityConfig);
|
|
255
257
|
HandlerRegister.setContractRegister(match[0], match[1], handler, match[2], undefined);
|
|
256
258
|
};
|
|
259
|
+
let onRollbackCommitFn = callback => {
|
|
260
|
+
HandlerRegister.throwIfFinishedRegistration("~internalAndWillBeRemovedSoon_onRollbackCommit");
|
|
261
|
+
RollbackCommit.register(callback);
|
|
262
|
+
};
|
|
257
263
|
let extractRange = (filter, name, ecosystem) => {
|
|
258
264
|
try {
|
|
259
265
|
let inner = S$RescriptSchema.parseOrThrow(filter, ecosystem.onBlockFilterSchema);
|
|
@@ -340,7 +346,8 @@ function getGlobalIndexer() {
|
|
|
340
346
|
"description",
|
|
341
347
|
"chainIds",
|
|
342
348
|
"chains",
|
|
343
|
-
"onSlot"
|
|
349
|
+
"onSlot",
|
|
350
|
+
"~internalAndWillBeRemovedSoon_onRollbackCommit"
|
|
344
351
|
];
|
|
345
352
|
break;
|
|
346
353
|
}
|
|
@@ -352,7 +359,8 @@ function getGlobalIndexer() {
|
|
|
352
359
|
"chains",
|
|
353
360
|
"onEvent",
|
|
354
361
|
"contractRegister",
|
|
355
|
-
"onBlock"
|
|
362
|
+
"onBlock",
|
|
363
|
+
"~internalAndWillBeRemovedSoon_onRollbackCommit"
|
|
356
364
|
];
|
|
357
365
|
}
|
|
358
366
|
keysMemo.contents = keys;
|
|
@@ -375,6 +383,8 @@ function getGlobalIndexer() {
|
|
|
375
383
|
case "onBlock" :
|
|
376
384
|
case "onSlot" :
|
|
377
385
|
return onBlockFn;
|
|
386
|
+
case "~internalAndWillBeRemovedSoon_onRollbackCommit" :
|
|
387
|
+
return onRollbackCommitFn;
|
|
378
388
|
default:
|
|
379
389
|
return Stdlib_JsError.throwWithMessage(`Field \`` + prop + `\` does not exist on \`indexer\`. Available fields: ` + getKeys().join(", ") + `.`);
|
|
380
390
|
}
|
|
@@ -523,7 +533,11 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
523
533
|
allEnums: config.allEnums
|
|
524
534
|
}) : config;
|
|
525
535
|
let config$2 = patchConfig !== undefined ? patchConfig(config$1, registrations) : config$1;
|
|
526
|
-
let
|
|
536
|
+
let onError = errHandler => {
|
|
537
|
+
ErrorHandling.log(errHandler);
|
|
538
|
+
Process.exit(1);
|
|
539
|
+
};
|
|
540
|
+
let ctx_inMemoryStore = InMemoryStore.make(persistence$1.allEntities, Persistence.getInitializedState(persistence$1).checkpointId, persistence$1, config$2, exn => onError(ErrorHandling.make(exn, undefined, "Failed writing batch to the database")));
|
|
527
541
|
let ctx = {
|
|
528
542
|
registrations: registrations,
|
|
529
543
|
config: config$2,
|
|
@@ -572,8 +586,8 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
572
586
|
}, ctx, isDevelopmentMode);
|
|
573
587
|
}
|
|
574
588
|
let chainManager = ChainManager.makeFromDbState(Persistence.getInitializedState(persistence$1), config$2, registrations, undefined);
|
|
575
|
-
let globalState = GlobalState.make(ctx, chainManager, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock);
|
|
576
|
-
let gsManager = GlobalStateManager.make(globalState, undefined);
|
|
589
|
+
let globalState = GlobalState.make(ctx, chainManager, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError);
|
|
590
|
+
let gsManager = GlobalStateManager.make(globalState, exn => onError(ErrorHandling.make(exn, undefined, "Indexer has failed with an unexpected error")));
|
|
577
591
|
if (shouldUseTui) {
|
|
578
592
|
Tui.start(() => GlobalStateManager.getState(gsManager));
|
|
579
593
|
}
|
package/src/Persistence.res
CHANGED
|
@@ -51,6 +51,9 @@ type updatedEffectCache = {
|
|
|
51
51
|
type rollback = {
|
|
52
52
|
targetCheckpointId: Internal.checkpointId,
|
|
53
53
|
diffCheckpointId: Internal.checkpointId,
|
|
54
|
+
// Last valid block per chain affected by the rollback. Read by
|
|
55
|
+
// `RollbackCommit.fire` once the diff is durably written.
|
|
56
|
+
progressBlockNumberByChainId: dict<int>,
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
type updatedEntity = {
|
|
@@ -126,13 +129,15 @@ type storage = {
|
|
|
126
129
|
// Write batch to storage
|
|
127
130
|
writeBatch: (
|
|
128
131
|
~batch: Batch.t,
|
|
129
|
-
~rawEvents: array<InternalTable.RawEvents.t>,
|
|
130
132
|
~rollback: option<rollback>,
|
|
131
133
|
~isInReorgThreshold: bool,
|
|
132
134
|
~config: Config.t,
|
|
133
135
|
~allEntities: array<Internal.entityConfig>,
|
|
134
136
|
~updatedEffectsCache: array<updatedEffectCache>,
|
|
135
137
|
~updatedEntities: array<updatedEntity>,
|
|
138
|
+
// Chain metadata stale since the last write, persisted in the same
|
|
139
|
+
// transaction so it never races the batch write.
|
|
140
|
+
~chainMetaData: option<dict<InternalTable.Chains.metaFields>>,
|
|
136
141
|
) => promise<unit>,
|
|
137
142
|
// Release any long-lived resources (e.g. the postgres connection pool) so
|
|
138
143
|
// short-lived CLI commands like `db-migrate setup` can exit cleanly.
|