@aztec/world-state 0.0.1-commit.b6e433891 → 0.0.1-commit.b9865e97

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 (49) hide show
  1. package/dest/instrumentation/instrumentation.d.ts +4 -3
  2. package/dest/instrumentation/instrumentation.d.ts.map +1 -1
  3. package/dest/instrumentation/instrumentation.js +4 -9
  4. package/dest/native/ipc_world_state_instance.d.ts +87 -0
  5. package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
  6. package/dest/native/ipc_world_state_instance.js +660 -0
  7. package/dest/native/merkle_trees_facade.d.ts +8 -4
  8. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  9. package/dest/native/merkle_trees_facade.js +60 -115
  10. package/dest/native/message.d.ts +14 -230
  11. package/dest/native/message.d.ts.map +1 -1
  12. package/dest/native/message.js +0 -42
  13. package/dest/native/native_world_state.d.ts +19 -7
  14. package/dest/native/native_world_state.d.ts.map +1 -1
  15. package/dest/native/native_world_state.js +113 -58
  16. package/dest/native/native_world_state_instance.d.ts +58 -41
  17. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  18. package/dest/native/native_world_state_instance.js +5 -202
  19. package/dest/native/world_state_operation.d.ts +7 -0
  20. package/dest/native/world_state_operation.d.ts.map +1 -0
  21. package/dest/native/world_state_operation.js +5 -0
  22. package/dest/synchronizer/config.d.ts +1 -1
  23. package/dest/synchronizer/config.d.ts.map +1 -1
  24. package/dest/synchronizer/config.js +9 -10
  25. package/dest/synchronizer/factory.d.ts +4 -4
  26. package/dest/synchronizer/factory.d.ts.map +1 -1
  27. package/dest/synchronizer/factory.js +7 -6
  28. package/dest/synchronizer/server_world_state_synchronizer.d.ts +1 -1
  29. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  30. package/dest/synchronizer/server_world_state_synchronizer.js +26 -11
  31. package/dest/testing.d.ts +4 -3
  32. package/dest/testing.d.ts.map +1 -1
  33. package/dest/testing.js +10 -6
  34. package/package.json +14 -10
  35. package/src/instrumentation/instrumentation.ts +6 -18
  36. package/src/native/ipc_world_state_instance.ts +830 -0
  37. package/src/native/merkle_trees_facade.ts +85 -112
  38. package/src/native/message.ts +13 -297
  39. package/src/native/native_world_state.ts +129 -97
  40. package/src/native/native_world_state_instance.ts +96 -280
  41. package/src/native/world_state_operation.ts +33 -0
  42. package/src/synchronizer/config.ts +14 -10
  43. package/src/synchronizer/factory.ts +11 -10
  44. package/src/synchronizer/server_world_state_synchronizer.ts +17 -13
  45. package/src/testing.ts +8 -9
  46. package/dest/native/world_state_ops_queue.d.ts +0 -19
  47. package/dest/native/world_state_ops_queue.d.ts.map +0 -1
  48. package/dest/native/world_state_ops_queue.js +0 -146
  49. package/src/native/world_state_ops_queue.ts +0 -190
@@ -3,7 +3,6 @@ import { BlockNumber } from '@aztec/foundation/branded-types';
3
3
  import { fromEntries, padArrayEnd } from '@aztec/foundation/collection';
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
- import { tryRmDir } from '@aztec/foundation/fs';
7
6
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
8
7
  import type { L2Block } from '@aztec/stdlib/block';
9
8
  import { DatabaseVersionManager } from '@aztec/stdlib/database-version/manager';
@@ -14,21 +13,21 @@ import type {
14
13
  } from '@aztec/stdlib/interfaces/server';
15
14
  import type { SnapshotDataKeys } from '@aztec/stdlib/snapshots';
16
15
  import { MerkleTreeId, NullifierLeaf, type NullifierLeafPreimage, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
17
- import { BlockHeader, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
18
- import { WorldStateRevision } from '@aztec/stdlib/world-state';
16
+ import { BlockHeader, GlobalVariables, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
17
+ import { EMPTY_GENESIS_DATA, type GenesisData, WorldStateRevision } from '@aztec/stdlib/world-state';
19
18
  import { getTelemetryClient } from '@aztec/telemetry-client';
20
19
 
21
20
  import assert from 'assert/strict';
22
- import { mkdtemp, rm } from 'fs/promises';
21
+ import { mkdir, mkdtemp, rm } from 'fs/promises';
23
22
  import { tmpdir } from 'os';
24
23
  import { join } from 'path';
25
24
 
26
25
  import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
27
26
  import type { WorldStateTreeMapSizes } from '../synchronizer/factory.js';
28
27
  import type { MerkleTreeAdminDatabase as MerkleTreeDatabase } from '../world-state-db/merkle_tree_db.js';
28
+ import { IpcWorldState } from './ipc_world_state_instance.js';
29
29
  import { MerkleTreesFacade, MerkleTreesForkFacade, serializeLeaf } from './merkle_trees_facade.js';
30
30
  import {
31
- WorldStateMessageType,
32
31
  type WorldStateStatusFull,
33
32
  type WorldStateStatusSummary,
34
33
  blockStateReference,
@@ -36,7 +35,7 @@ import {
36
35
  sanitizeSummary,
37
36
  treeStateReferenceToSnapshot,
38
37
  } from './message.js';
39
- import { NativeWorldState } from './native_world_state_instance.js';
38
+ import type { NativeWorldStateInstance } from './native_world_state_instance.js';
40
39
 
41
40
  // The current version of the world state database schema
42
41
  // Increment this when making incompatible changes to the database schema
@@ -50,37 +49,49 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
50
49
  private cachedStatusSummary: WorldStateStatusSummary | undefined;
51
50
 
52
51
  protected constructor(
53
- protected instance: NativeWorldState,
52
+ protected instance: NativeWorldStateInstance,
54
53
  protected readonly worldStateInstrumentation: WorldStateInstrumentation,
55
54
  protected readonly log: Logger,
55
+ private readonly genesis: GenesisData = EMPTY_GENESIS_DATA,
56
56
  private readonly cleanup = () => Promise.resolve(),
57
+ /** Factory to recreate a fresh IpcWorldState after clear(). */
58
+ private readonly recreateInstance?: () => Promise<NativeWorldStateInstance>,
57
59
  ) {}
58
60
 
59
61
  static async new(
60
62
  rollupAddress: EthAddress,
61
63
  dataDir: string,
62
64
  wsTreeMapSizes: WorldStateTreeMapSizes,
63
- prefilledPublicData: PublicDataTreeLeaf[] = [],
65
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
64
66
  instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
65
67
  bindings?: LoggerBindings,
66
68
  cleanup = () => Promise.resolve(),
67
69
  ): Promise<NativeWorldStateService> {
70
+ for (const [key, value] of Object.entries(wsTreeMapSizes)) {
71
+ if (value <= 0) {
72
+ throw new Error(`Map size must be a positive number, got ${value} for ${key}`);
73
+ }
74
+ }
75
+
68
76
  const log = createLogger('world-state:database', bindings);
69
77
  const worldStateDirectory = join(dataDir, WORLD_STATE_DIR);
70
- // Create a version manager to handle versioning
78
+
71
79
  const versionManager = new DatabaseVersionManager({
72
80
  schemaVersion: WORLD_STATE_DB_VERSION,
73
81
  rollupAddress,
74
82
  dataDirectory: worldStateDirectory,
75
- onOpen: (dir: string) => {
76
- return Promise.resolve(
77
- new NativeWorldState(dir, wsTreeMapSizes, prefilledPublicData, instrumentation, bindings),
78
- );
79
- },
83
+ onOpen: dir => IpcWorldState.spawn(dir, wsTreeMapSizes, genesis, instrumentation, bindings),
80
84
  });
81
85
 
82
86
  const [instance] = await versionManager.open();
83
- const worldState = new this(instance, instrumentation, log, cleanup);
87
+
88
+ const recreateInstance = async () => {
89
+ await rm(worldStateDirectory, { recursive: true, force: true, maxRetries: 3 });
90
+ await mkdir(worldStateDirectory, { recursive: true });
91
+ return IpcWorldState.spawn(worldStateDirectory, wsTreeMapSizes, genesis, instrumentation, bindings);
92
+ };
93
+
94
+ const worldState = new this(instance, instrumentation, log, genesis, cleanup, recreateInstance);
84
95
  try {
85
96
  await worldState.init();
86
97
  } catch (e) {
@@ -92,15 +103,18 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
92
103
  }
93
104
 
94
105
  static async tmp(
95
- rollupAddress = EthAddress.ZERO,
96
106
  cleanupTmpDir = true,
97
- prefilledPublicData: PublicDataTreeLeaf[] = [],
107
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
98
108
  instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
99
109
  bindings?: LoggerBindings,
100
110
  ): Promise<NativeWorldStateService> {
101
111
  const log = createLogger('world-state:database', bindings);
102
112
  const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
103
- const dbMapSizeKb = 10 * 1024 * 1024;
113
+ // Temporary (test/dev) world states are small and short-lived. Keep the LMDB
114
+ // map sizes in the MB range: with the IPC backend each tmp() spawns a separate
115
+ // aztec-wsdb that maps these per tree, and oversized maps add real cold-start
116
+ // I/O under parallel CI load. 256 MB/tree is ample for tests.
117
+ const dbMapSizeKb = 256 * 1024;
104
118
  const worldStateTreeMapSizes: WorldStateTreeMapSizes = {
105
119
  archiveTreeMapSizeKb: dbMapSizeKb,
106
120
  nullifierTreeMapSizeKb: dbMapSizeKb,
@@ -110,7 +124,8 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
110
124
  };
111
125
  log.debug(`Created temporary world state database at: ${dataDir} with tree map size: ${dbMapSizeKb}`);
112
126
 
113
- // pass a cleanup callback because process.on('beforeExit', cleanup) does not work under Jest
127
+ const instance = await IpcWorldState.spawn(dataDir, worldStateTreeMapSizes, genesis, instrumentation, bindings);
128
+
114
129
  const cleanup = async () => {
115
130
  if (cleanupTmpDir) {
116
131
  await rm(dataDir, { recursive: true, force: true, maxRetries: 3 });
@@ -120,15 +135,43 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
120
135
  }
121
136
  };
122
137
 
123
- return this.new(
124
- rollupAddress,
125
- dataDir,
126
- worldStateTreeMapSizes,
127
- prefilledPublicData,
128
- instrumentation,
129
- bindings,
130
- cleanup,
131
- );
138
+ const recreateInstance = async () => {
139
+ await rm(dataDir, { recursive: true, force: true, maxRetries: 3 });
140
+ await mkdir(dataDir, { recursive: true });
141
+ return IpcWorldState.spawn(dataDir, worldStateTreeMapSizes, genesis, instrumentation, bindings);
142
+ };
143
+
144
+ const worldState = new this(instance, instrumentation, log, genesis, cleanup, recreateInstance);
145
+ try {
146
+ await worldState.init();
147
+ } catch (e) {
148
+ log.error(`Error initializing tmp world state: ${e}`);
149
+ throw e;
150
+ }
151
+ return worldState;
152
+ }
153
+
154
+ static ephemeral(
155
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
156
+ instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
157
+ bindings?: LoggerBindings,
158
+ ): Promise<NativeWorldStateService> {
159
+ return this.tmp(/*cleanupTmpDir=*/ true, genesis, instrumentation, bindings);
160
+ }
161
+
162
+ static async fromIpc(
163
+ wsdbBackend: ConstructorParameters<typeof IpcWorldState>[0],
164
+ instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
165
+ bindings?: LoggerBindings,
166
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
167
+ cleanup = () => Promise.resolve(),
168
+ recreateInstance?: () => Promise<NativeWorldStateInstance>,
169
+ ): Promise<NativeWorldStateService> {
170
+ const log = createLogger('world-state:database', bindings);
171
+ const instance = new IpcWorldState(wsdbBackend, instrumentation, bindings);
172
+ const worldState = new this(instance, instrumentation, log, genesis, cleanup, recreateInstance);
173
+ await worldState.init();
174
+ return worldState;
132
175
  }
133
176
 
134
177
  protected async init() {
@@ -147,16 +190,29 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
147
190
 
148
191
  // the initial header _must_ be the first element in the archive tree
149
192
  // if this assertion fails, check that the hashing done in Header in yarn-project matches the initial header hash done in world_state.cpp
150
- const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [(await this.initialHeader.hash()).toFr()]);
193
+ const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [await this.initialHeader.hash()]);
151
194
  const initialHeaderIndex = indices[0];
152
195
  assert.strictEqual(initialHeaderIndex, 0n, 'Invalid initial archive state');
153
196
  }
154
197
 
155
- public async clear() {
198
+ public async clear(): Promise<void> {
199
+ if (!this.recreateInstance) {
200
+ throw new Error('clear() is not available for externally-managed IPC backends');
201
+ }
202
+ this.log.warn('Clearing world state: shutting down WSDB, deleting data, and recreating');
156
203
  await this.instance.close();
157
204
  this.cachedStatusSummary = undefined;
158
- await tryRmDir(this.instance.getDataDir(), this.log);
159
- this.instance = this.instance.clone();
205
+ this.instance = await this.recreateInstance();
206
+ await this.init();
207
+ this.log.info('World state cleared and reinitialized from genesis');
208
+ }
209
+
210
+ /** Returns the IPC path of the underlying IPC backend, if available. */
211
+ public getIpcPath(): string {
212
+ if (this.instance instanceof IpcWorldState) {
213
+ return this.instance.getIpcPath();
214
+ }
215
+ throw new Error('getIpcPath() is only available with IPC world state');
160
216
  }
161
217
 
162
218
  public getCommitted(): MerkleTreeReadOperations {
@@ -175,17 +231,16 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
175
231
  blockNumber?: BlockNumber,
176
232
  opts: { closeDelayMs?: number } = {},
177
233
  ): Promise<MerkleTreeWriteOperations> {
178
- const resp = await this.instance.call(WorldStateMessageType.CREATE_FORK, {
234
+ const forkId = await this.instance.createFork({
179
235
  latest: blockNumber === undefined,
180
236
  blockNumber: blockNumber ?? BlockNumber.ZERO,
181
- canonical: true,
182
237
  });
183
238
  return new MerkleTreesForkFacade(
184
239
  this.instance,
185
240
  this.initialHeader!,
186
241
  new WorldStateRevision(
187
- /*forkId=*/ resp.forkId,
188
- /* blockNumber=*/ BlockNumber.ZERO,
242
+ /*forkId=*/ forkId,
243
+ /* blockNumber=*/ WorldStateRevision.LATEST,
189
244
  /* includeUncommitted=*/ true,
190
245
  ),
191
246
  opts,
@@ -228,35 +283,41 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
228
283
  });
229
284
 
230
285
  try {
231
- return await this.instance.call(
232
- WorldStateMessageType.SYNC_BLOCK,
233
- {
234
- blockNumber: l2Block.number,
235
- blockHeaderHash: await l2Block.hash(),
236
- paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
237
- paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
238
- paddedNullifiers: paddedNullifiers.map(serializeLeaf),
239
- publicDataWrites: publicDataWrites.map(serializeLeaf),
240
- blockStateRef: blockStateReference(l2Block.header.state),
241
- canonical: true,
242
- },
243
- this.sanitizeAndCacheSummaryFromFull.bind(this),
244
- this.deleteCachedSummary.bind(this),
245
- );
286
+ const status = await this.instance.syncBlock({
287
+ blockNumber: l2Block.number,
288
+ blockHeaderHash: (await l2Block.hash()).toBuffer(),
289
+ paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
290
+ paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
291
+ paddedNullifiers: paddedNullifiers.map(serializeLeaf),
292
+ publicDataWrites: publicDataWrites.map(serializeLeaf),
293
+ blockStateRef: blockStateReference(l2Block.header.state),
294
+ });
295
+ return this.sanitizeAndCacheSummaryFromFull(status);
246
296
  } catch (err) {
297
+ this.deleteCachedSummary();
247
298
  this.worldStateInstrumentation.incCriticalErrors('synch_pending_block');
248
299
  throw err;
249
300
  }
250
301
  }
251
302
 
252
303
  public async close(): Promise<void> {
253
- await this.instance.close();
254
- await this.cleanup();
304
+ try {
305
+ await this.instance.close();
306
+ } finally {
307
+ await this.cleanup();
308
+ }
309
+ }
310
+
311
+ async [Symbol.asyncDispose](): Promise<void> {
312
+ await this.close();
255
313
  }
256
314
 
257
315
  private async buildInitialHeader(): Promise<BlockHeader> {
258
316
  const state = await this.getInitialStateReference();
259
- return BlockHeader.empty({ state });
317
+ return BlockHeader.empty({
318
+ state,
319
+ globalVariables: GlobalVariables.empty({ timestamp: this.genesis.genesisTimestamp }),
320
+ });
260
321
  }
261
322
 
262
323
  private sanitizeAndCacheSummaryFromFull(response: WorldStateStatusFull) {
@@ -271,7 +332,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
271
332
  return sanitized;
272
333
  }
273
334
 
274
- private deleteCachedSummary(_: string) {
335
+ private deleteCachedSummary() {
275
336
  this.cachedStatusSummary = undefined;
276
337
  }
277
338
 
@@ -282,16 +343,9 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
282
343
  */
283
344
  public async setFinalized(toBlockNumber: BlockNumber) {
284
345
  try {
285
- await this.instance.call(
286
- WorldStateMessageType.FINALIZE_BLOCKS,
287
- {
288
- toBlockNumber,
289
- canonical: true,
290
- },
291
- this.sanitizeAndCacheSummary.bind(this),
292
- this.deleteCachedSummary.bind(this),
293
- );
346
+ this.sanitizeAndCacheSummary(await this.instance.finalizeBlocks(toBlockNumber));
294
347
  } catch (err) {
348
+ this.deleteCachedSummary();
295
349
  this.worldStateInstrumentation.incCriticalErrors('finalize_block');
296
350
  throw err;
297
351
  }
@@ -305,16 +359,9 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
305
359
  */
306
360
  public async removeHistoricalBlocks(toBlockNumber: BlockNumber) {
307
361
  try {
308
- return await this.instance.call(
309
- WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
310
- {
311
- toBlockNumber,
312
- canonical: true,
313
- },
314
- this.sanitizeAndCacheSummaryFromFull.bind(this),
315
- this.deleteCachedSummary.bind(this),
316
- );
362
+ return this.sanitizeAndCacheSummaryFromFull(await this.instance.removeHistoricalBlocks(toBlockNumber));
317
363
  } catch (err) {
364
+ this.deleteCachedSummary();
318
365
  this.worldStateInstrumentation.incCriticalErrors('prune_historical_block');
319
366
  throw err;
320
367
  }
@@ -327,16 +374,9 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
327
374
  */
328
375
  public async unwindBlocks(toBlockNumber: BlockNumber) {
329
376
  try {
330
- return await this.instance.call(
331
- WorldStateMessageType.UNWIND_BLOCKS,
332
- {
333
- toBlockNumber,
334
- canonical: true,
335
- },
336
- this.sanitizeAndCacheSummaryFromFull.bind(this),
337
- this.deleteCachedSummary.bind(this),
338
- );
377
+ return this.sanitizeAndCacheSummaryFromFull(await this.instance.unwindBlocks(toBlockNumber));
339
378
  } catch (err) {
379
+ this.deleteCachedSummary();
340
380
  this.worldStateInstrumentation.incCriticalErrors('prune_pending_block');
341
381
  throw err;
342
382
  }
@@ -346,11 +386,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
346
386
  if (this.cachedStatusSummary !== undefined) {
347
387
  return { ...this.cachedStatusSummary };
348
388
  }
349
- return await this.instance.call(
350
- WorldStateMessageType.GET_STATUS,
351
- { canonical: true },
352
- this.sanitizeAndCacheSummary.bind(this),
353
- );
389
+ return this.sanitizeAndCacheSummary(await this.instance.getStatus());
354
390
  }
355
391
 
356
392
  updateLeaf<ID extends IndexedTreeId>(
@@ -362,14 +398,14 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
362
398
  }
363
399
 
364
400
  private async getInitialStateReference(): Promise<StateReference> {
365
- const resp = await this.instance.call(WorldStateMessageType.GET_INITIAL_STATE_REFERENCE, { canonical: true });
401
+ const state = await this.instance.getInitialStateReference();
366
402
 
367
403
  return new StateReference(
368
- treeStateReferenceToSnapshot(resp.state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]),
404
+ treeStateReferenceToSnapshot(state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]),
369
405
  new PartialStateReference(
370
- treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NOTE_HASH_TREE]),
371
- treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NULLIFIER_TREE]),
372
- treeStateReferenceToSnapshot(resp.state[MerkleTreeId.PUBLIC_DATA_TREE]),
406
+ treeStateReferenceToSnapshot(state[MerkleTreeId.NOTE_HASH_TREE]),
407
+ treeStateReferenceToSnapshot(state[MerkleTreeId.NULLIFIER_TREE]),
408
+ treeStateReferenceToSnapshot(state[MerkleTreeId.PUBLIC_DATA_TREE]),
373
409
  ),
374
410
  );
375
411
  }
@@ -378,11 +414,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
378
414
  dstPath: string,
379
415
  compact: boolean = true,
380
416
  ): Promise<Record<Exclude<SnapshotDataKeys, 'archiver'>, string>> {
381
- await this.instance.call(WorldStateMessageType.COPY_STORES, {
382
- dstPath,
383
- compact,
384
- canonical: true,
385
- });
417
+ await this.instance.copyStores(dstPath, compact);
386
418
  return fromEntries(NATIVE_WORLD_STATE_DBS.map(([name, dir]) => [name, join(dstPath, dir, 'data.mdb')] as const));
387
419
  }
388
420
  }