@lossless.org/nosqldb 10.10.0 → 11.0.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.
Files changed (43) hide show
  1. package/.smartconfig.json +21 -19
  2. package/dist_rust/rustdb_linux_amd64 +0 -0
  3. package/dist_rust/rustdb_linux_amd64.tsrust-build.json +5 -5
  4. package/dist_rust/rustdb_linux_amd64_musl +0 -0
  5. package/dist_rust/rustdb_linux_amd64_musl.tsrust-build.json +5 -5
  6. package/dist_rust/rustdb_linux_arm64 +0 -0
  7. package/dist_rust/rustdb_linux_arm64.tsrust-build.json +5 -5
  8. package/dist_rust/rustdb_linux_arm64_musl +0 -0
  9. package/dist_rust/rustdb_linux_arm64_musl.tsrust-build.json +5 -5
  10. package/dist_rust/rustdb_macos_amd64 +0 -0
  11. package/dist_rust/rustdb_macos_amd64.tsrust-build.json +5 -5
  12. package/dist_rust/rustdb_macos_arm64 +0 -0
  13. package/dist_rust/rustdb_macos_arm64.tsrust-build.json +5 -5
  14. package/dist_ts/00_commitinfo_data.js +2 -2
  15. package/dist_ts/ts_local/classes.localnosqldb.d.ts +15 -4
  16. package/dist_ts/ts_local/classes.localnosqldb.js +31 -11
  17. package/dist_ts/ts_nosqldb/index.d.ts +3 -3
  18. package/dist_ts/ts_nosqldb/index.js +5 -4
  19. package/dist_ts/ts_nosqldb/rust-db-bridge.d.ts +125 -2
  20. package/dist_ts/ts_nosqldb/rust-db-bridge.js +261 -46
  21. package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.d.ts +69 -3
  22. package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.js +91 -12
  23. package/dist_ts/ts_nosqldb/server/index.d.ts +2 -2
  24. package/dist_ts/ts_nosqldb/server/index.js +2 -2
  25. package/dist_ts/ts_nosqldb/stopped-storage.d.ts +9 -1
  26. package/dist_ts/ts_nosqldb/stopped-storage.js +1 -1
  27. package/dist_ts/ts_nosqldb/storage-rehearsal.js +1 -1
  28. package/dist_ts/ts_nosqldb/storage-root-alignment.js +7 -3
  29. package/dist_ts/ts_nosqldb/storage-root-relocation.js +2 -2
  30. package/package.json +20 -16
  31. package/readme.md +469 -666
  32. package/third-party-notices.md +40 -8
  33. package/third_party_licenses/inventory.json +6 -6
  34. package/ts/00_commitinfo_data.ts +1 -1
  35. package/ts/ts_local/classes.localnosqldb.ts +35 -12
  36. package/ts/ts_nosqldb/index.ts +7 -2
  37. package/ts/ts_nosqldb/rust-db-bridge.ts +291 -48
  38. package/ts/ts_nosqldb/server/classes.nosqldbserver.ts +151 -15
  39. package/ts/ts_nosqldb/server/index.ts +2 -2
  40. package/ts/ts_nosqldb/stopped-storage.ts +9 -1
  41. package/ts/ts_nosqldb/storage-rehearsal.ts +1 -1
  42. package/ts/ts_nosqldb/storage-root-alignment.ts +6 -2
  43. package/ts/ts_nosqldb/storage-root-relocation.ts +1 -1
@@ -6,6 +6,7 @@ import { normalizeNoSqlDbFileStorageOptions } from './file-storage-options.js';
6
6
  import type { TNoSqlDbFileStorageStartup } from './file-storage-startup.js';
7
7
  import * as path from 'node:path';
8
8
  import * as url from 'node:url';
9
+ import type { ChildProcess } from 'node:child_process';
9
10
  import { EventEmitter } from 'node:events';
10
11
  import {
11
12
  normalizeNoSqlDbStorageRehearsalInput,
@@ -163,6 +164,92 @@ export interface IDocumentsResult {
163
164
  total: number;
164
165
  }
165
166
 
167
+ /**
168
+ * Bounds of the engine standard-error tail that start failures, unexpected
169
+ * engine exits and failed management calls carry: the engine's last 64 lines.
170
+ * A longer line keeps its first 1,024 characters, followed by
171
+ * ` [<n> more characters]`.
172
+ */
173
+ export const noSqlDbEngineStderrTailLimits = Object.freeze({ lines: 64, lineCharacters: 1024 });
174
+
175
+ /**
176
+ * Attach the engine's last standard-error lines to an error as its own field
177
+ * `engineStderrTail`, leaving its message and cause unchanged. Values that
178
+ * cannot carry a field (primitives, frozen objects) are left as they are.
179
+ */
180
+ export function attachNoSqlDbEngineStderrTail(errorArg: unknown, tailArg: readonly string[]): void {
181
+ if (errorArg === null || (typeof errorArg !== 'object' && typeof errorArg !== 'function')) return;
182
+ if (!Object.isExtensible(errorArg) && !Object.prototype.hasOwnProperty.call(errorArg, 'engineStderrTail')) return;
183
+ try {
184
+ Object.defineProperty(errorArg, 'engineStderrTail', {
185
+ value: Object.freeze([...tailArg]),
186
+ enumerable: true,
187
+ configurable: true,
188
+ writable: false,
189
+ });
190
+ } catch {
191
+ // A non-configurable field of the same name stays as the caller made it.
192
+ }
193
+ }
194
+
195
+ /**
196
+ * The engine process exited with a non-zero status or by a signal while its
197
+ * server was running and no stop was in progress. `engineStderrTail` holds
198
+ * its last standard-error lines.
199
+ */
200
+ export class NoSqlDbEngineExitError extends Error {
201
+ public readonly engineStderrTail: readonly string[];
202
+
203
+ constructor(
204
+ public readonly exitCode: number | null,
205
+ public readonly signal: string | null,
206
+ engineStderrTailArg: readonly string[],
207
+ ) {
208
+ super(`nosqldb Rust process exited unexpectedly (code=${exitCode}, signal=${signal})`);
209
+ this.name = 'NoSqlDbEngineExitError';
210
+ this.engineStderrTail = Object.freeze([...engineStderrTailArg]);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * A start that the engine refused because the storage root, or a feature it
216
+ * carries, comes from a later NoSQLDB release than this one: the engine's
217
+ * management reply carried the code `UNKNOWN_ROOT`. The engine wrote nothing
218
+ * into the root (storage-root-names-and-start.md, "Roots of later releases";
219
+ * requirements A300 and A319). `code` holds the code, and `cause` the
220
+ * management error that carried it.
221
+ */
222
+ export class NoSqlDbUnknownRootError extends Error {
223
+ public readonly code = 'UNKNOWN_ROOT' as const;
224
+ public readonly responseErrorCode = 'UNKNOWN_ROOT' as const;
225
+
226
+ constructor(options?: { cause?: unknown }) {
227
+ super('NoSQLDB refuses this storage root: the root, or a feature it carries, comes from a later NoSQLDB release than this one', options);
228
+ this.name = 'NoSqlDbUnknownRootError';
229
+ }
230
+ }
231
+
232
+ /** The bounded record of one engine process's last standard-error lines. */
233
+ class EngineStderrTail {
234
+ private lines: string[] = [];
235
+
236
+ public add(lineArg: string): void {
237
+ const limit = noSqlDbEngineStderrTailLimits.lineCharacters;
238
+ this.lines.push(lineArg.length > limit
239
+ ? `${lineArg.slice(0, limit)} [${lineArg.length - limit} more characters]`
240
+ : lineArg);
241
+ if (this.lines.length > noSqlDbEngineStderrTailLimits.lines) this.lines.shift();
242
+ }
243
+
244
+ public clear(): void {
245
+ this.lines = [];
246
+ }
247
+
248
+ public snapshot(): string[] {
249
+ return [...this.lines];
250
+ }
251
+ }
252
+
166
253
  export class NoSqlDbManagementOperationTerminationError extends AggregateError {
167
254
  constructor(operationErrorArg: unknown, terminationErrorArg: unknown) {
168
255
  super(
@@ -363,6 +450,7 @@ interface INoSqlDbRustConfig {
363
450
  storagePath?: string;
364
451
  query?: INoSqlDbQueryOptions;
365
452
  inbound?: INoSqlDbInboundOptions;
453
+ storageBudgetBytes?: number;
366
454
  auth?: {
367
455
  enabled?: boolean;
368
456
  users?: Array<{
@@ -397,8 +485,8 @@ function getPackageRoot(): string {
397
485
  }
398
486
 
399
487
  /**
400
- * Map Node.js process.platform/process.arch to tsrust's friendly name suffix.
401
- * tsrust names cross-compiled binaries as: rustdb_linux_amd64, rustdb_linux_arm64, etc.
488
+ * Map Node.js process.platform/process.arch to rsrepo's friendly name suffix.
489
+ * rsrepo names cross-compiled binaries as: rustdb_linux_amd64, rustdb_linux_arm64, etc.
402
490
  */
403
491
  function getTsrustPlatformSuffix(): string | null {
404
492
  const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' };
@@ -414,14 +502,14 @@ function getTsrustPlatformSuffix(): string | null {
414
502
 
415
503
  /**
416
504
  * Build local search paths for the Rust binary, including dist_rust/ candidates
417
- * (built by tsrust) and local development build paths.
505
+ * (built by rsrepo) and local development build paths.
418
506
  */
419
507
  function buildLocalPaths(): string[] {
420
508
  const packageRoot = getPackageRoot();
421
509
  const suffix = getTsrustPlatformSuffix();
422
510
  const paths: string[] = [];
423
511
 
424
- // dist_rust/ candidates (tsrust cross-compiled output)
512
+ // dist_rust/ candidates (rsrepo cross-compiled output)
425
513
  if (suffix) {
426
514
  paths.push(path.join(packageRoot, 'dist_rust', `rustdb_${suffix}`));
427
515
  }
@@ -433,18 +521,40 @@ function buildLocalPaths(): string[] {
433
521
 
434
522
  return paths;
435
523
  }
436
- export function getRustDbAllocatorEnv(
437
- environment: NodeJS.ProcessEnv = process.env,
438
- ): Record<string, string> {
439
- return {
440
- MIMALLOC_ARENA_EAGER_COMMIT: environment.MIMALLOC_ARENA_EAGER_COMMIT ?? '0',
441
- MIMALLOC_ALLOW_LARGE_OS_PAGES: environment.MIMALLOC_ALLOW_LARGE_OS_PAGES ?? '0',
442
- };
524
+
525
+ const binaryHelp = 'Set NOSQLDB_RUST_BINARY env var, install the platform package, or build locally with `rsrepo`.';
526
+
527
+ /**
528
+ * What a spawn() that returned false after `waitedMs` means, stating only what
529
+ * is known. Smartrust refuses a `binaryPath` that does not pass its validation
530
+ * before spawn() can return false, so with one the engine there passed
531
+ * Smartrust's validation and did not report ready within the bridge's limit,
532
+ * and its path is given as the caller gave it.
533
+ * Without one, whatever NOSQLDB_RUST_BINARY holds (one of the places that
534
+ * Smartrust's search looks), either the search found no engine or the engine
535
+ * it found did not report ready, and nothing tells which.
536
+ */
537
+ export function describeRefusedSpawn(binaryPath: string | undefined, waitedMs: number): string {
538
+ if (binaryPath !== undefined) {
539
+ return `the nosqldb engine at ${binaryPath} did not report ready within the bridge's limit: it waited ${waitedMs} ms`;
540
+ }
541
+ return `the nosqldb engine did not start: spawn() returned false after ${waitedMs} ms, which means either that no nosqldb Rust binary `
542
+ + `was found (${binaryHelp}) or that the engine found did not report ready within the bridge's limit`;
443
543
  }
444
544
 
445
545
  export interface IRustDbBridgeOptions {
446
546
  /** Exact engine executable, validated by Smartrust without discovery fallback or permission changes. */
447
547
  binaryPath?: string;
548
+ /**
549
+ * Receives every line the engine writes to its standard error, in order and
550
+ * without its line break. Without it, no engine line reaches the host
551
+ * process's standard error. Either way the bridge keeps the last lines for
552
+ * errors (`noSqlDbEngineStderrTailLimits`). Each call runs in its own
553
+ * microtask after the bridge has recorded the line, so an exception it
554
+ * throws cannot disturb the bridge; it is not caught and reaches the process
555
+ * as an uncaught exception.
556
+ */
557
+ engineLog?: (line: string) => void;
448
558
  }
449
559
 
450
560
  /**
@@ -460,17 +570,100 @@ export class RustDbBridge extends EventEmitter {
460
570
  * termination, or when the bridge is connected to an external process.
461
571
  */
462
572
  public get processId(): number | undefined {
573
+ return this.childProcess?.pid;
574
+ }
575
+
576
+ /** The engine child process of the latest spawn, while the bridge holds it. */
577
+ private get childProcess(): ChildProcess | undefined {
463
578
  const transport = (this.bridge as unknown as {
464
- transport?: { childProcess?: { pid?: number } | null };
579
+ transport?: { childProcess?: ChildProcess | null };
465
580
  }).transport;
466
- return transport?.childProcess?.pid;
581
+ return transport?.childProcess ?? undefined;
582
+ }
583
+
584
+ /** How the engine process of the latest spawn exited, once it has. */
585
+ private engineExit: { code: number | null; signal: string | null } | null = null;
586
+ private engineExitPromise: Promise<void> | undefined;
587
+
588
+ /** Records the exit of the engine process that a spawn has just started. */
589
+ private watchEngineExit(): void {
590
+ const child = this.childProcess;
591
+ if (!child) return;
592
+ this.engineExitPromise = new Promise((resolveArg) => {
593
+ const record = (code: number | null, signal: string | null) => {
594
+ this.engineExit = { code, signal };
595
+ resolveArg();
596
+ };
597
+ if (child.exitCode !== null || child.signalCode !== null) record(child.exitCode, child.signalCode);
598
+ else child.once('exit', record);
599
+ });
600
+ }
601
+
602
+ /**
603
+ * The exit code and signal of the engine process of the latest spawn, as its
604
+ * process exit reported them, each null where it reported none; or null
605
+ * while that process has not exited, or no spawn started one.
606
+ */
607
+ public get lastEngineExit(): { code: number | null; signal: string | null } | null {
608
+ return this.engineExit;
609
+ }
610
+
611
+ /** Whether the engine process of the latest spawn exists and has not exited. */
612
+ public get engineProcessAlive(): boolean {
613
+ return this.engineExitPromise !== undefined && this.engineExit === null;
614
+ }
615
+
616
+ /** Waits up to `timeoutMs` for the engine process to exit by itself; resolves whether it has. */
617
+ public async waitForEngineExit(timeoutMs: number): Promise<boolean> {
618
+ if (!this.engineExitPromise) return false;
619
+ let timer: ReturnType<typeof setTimeout> | undefined;
620
+ await Promise.race([
621
+ this.engineExitPromise,
622
+ new Promise<void>((resolveArg) => { timer = setTimeout(resolveArg, timeoutMs); }),
623
+ ]);
624
+ if (timer) clearTimeout(timer);
625
+ return this.engineExit !== null;
626
+ }
627
+
628
+ /**
629
+ * Closes the engine's management input, the end of the management protocol
630
+ * after an acknowledged `stop`, on which the engine exits by itself. This is
631
+ * neither a signal nor a kill. Returns whether there was an input to close.
632
+ */
633
+ public endManagementInput(): boolean {
634
+ const input = this.childProcess?.stdin;
635
+ if (!input || input.writableEnded) return false;
636
+ input.end();
637
+ return true;
467
638
  }
468
639
  private terminationConfirmed = true;
469
640
  private terminationPromise: Promise<void> | undefined;
470
641
  private terminationFailure: unknown;
642
+ private readonly stderrTail = new EngineStderrTail();
643
+ /** The `binaryPath` this bridge was given, if any, as given: the bridge's only configured path. */
644
+ private readonly binaryPath: string | undefined;
645
+ private spawnRefusal: string | undefined;
646
+
647
+ /**
648
+ * Why Smartrust's spawn() returned false, as far as the bridge knows. Each
649
+ * spawn() clears it as it starts, and sets it when Smartrust's spawn()
650
+ * returns false before this spawn()'s own `timeoutMs` or `signal` cuts it
651
+ * short, before the engine is ended. That spawn() then returns false, or
652
+ * throws if ending the engine fails. After any other spawn() it is
653
+ * undefined, including one cut short by its own `timeoutMs` or `signal`.
654
+ * Smartrust's spawn() returns false both when it finds no engine and when
655
+ * the engine it starts does not report ready within its limit; a
656
+ * `binaryPath` tells the two apart, as Smartrust refuses one that does not
657
+ * pass its validation with RustBinaryLocatorError before spawn() can return
658
+ * false.
659
+ */
660
+ public get spawnFailureMessage(): string | undefined {
661
+ return this.spawnRefusal;
662
+ }
471
663
 
472
664
  constructor(options: IRustDbBridgeOptions = {}) {
473
665
  super();
666
+ this.binaryPath = options.binaryPath;
474
667
 
475
668
  this.bridge = new plugins.smartrust.RustBridge<TNoSqlDbCommands>({
476
669
  binaryName: 'rustdb',
@@ -479,7 +672,14 @@ export class RustDbBridge extends EventEmitter {
479
672
  platformPackagePrefix: '@lossless.org/nosqldb',
480
673
  localPaths: buildLocalPaths(),
481
674
  maxPayloadSize: 100 * 1024 * 1024, // database exports/imports can be larger than command replies
482
- env: getRustDbAllocatorEnv(),
675
+ });
676
+
677
+ // Smartrust delivers the engine's standard error one line at a time.
678
+ const engineLog = options.engineLog;
679
+ this.bridge.on('stderr', (line: string) => {
680
+ this.stderrTail.add(line);
681
+ // Microtasks run in order, so the lines keep their order.
682
+ if (engineLog) queueMicrotask(() => engineLog(line));
483
683
  });
484
684
 
485
685
  // Forward events from the inner bridge
@@ -489,11 +689,43 @@ export class RustDbBridge extends EventEmitter {
489
689
  });
490
690
  }
491
691
 
692
+ /**
693
+ * The last standard-error lines of the running or most recently spawned
694
+ * engine process, within `noSqlDbEngineStderrTailLimits`.
695
+ */
696
+ public get engineStderrTail(): string[] {
697
+ return this.stderrTail.snapshot();
698
+ }
699
+
700
+ /**
701
+ * Send one management command. A failure while no engine runs (it exited
702
+ * or never started) carries the engine's last standard-error lines.
703
+ */
704
+ private async send<TMethod extends keyof TNoSqlDbCommands & string>(
705
+ methodArg: TMethod,
706
+ paramsArg: TNoSqlDbCommands[TMethod]['params'],
707
+ optionsArg?: INoSqlDbManagementOperationOptions,
708
+ ): Promise<TNoSqlDbCommands[TMethod]['result']> {
709
+ try {
710
+ return await this.bridge.sendCommand(methodArg, paramsArg, optionsArg);
711
+ } catch (error) {
712
+ if (!this.bridge.running) attachNoSqlDbEngineStderrTail(error, this.stderrTail.snapshot());
713
+ throw error;
714
+ }
715
+ }
716
+
492
717
  /**
493
718
  * Spawn the Rust binary in management mode.
494
- * Returns true if the binary was found and spawned successfully.
719
+ * Returns true once the engine reported ready. Returns false when
720
+ * Smartrust's spawn() returned false: no engine was found, or the one found
721
+ * did not report ready within the bridge's limit. spawnFailureMessage then
722
+ * says what that means, as far as the bridge knows, and still says it if
723
+ * ending that engine fails and this throws instead. After any other
724
+ * outcome, including a throw on this spawn()'s own `timeoutMs` or `signal`,
725
+ * spawnFailureMessage is undefined.
495
726
  */
496
727
  public async spawn(optionsArg?: INoSqlDbManagementOperationOptions): Promise<boolean> {
728
+ this.spawnRefusal = undefined;
497
729
  validateNoSqlDbManagementOperationOptions(optionsArg);
498
730
  if (optionsArg?.signal?.aborted) {
499
731
  throw new plugins.smartrust.RustBridgeRequestError(
@@ -508,6 +740,11 @@ export class RustDbBridge extends EventEmitter {
508
740
 
509
741
  this.terminationConfirmed = false;
510
742
  this.terminationFailure = undefined;
743
+ this.stderrTail.clear();
744
+ const spawnStartedAt = performance.now();
745
+ // The previous engine's exit belongs to it, also when this spawn fails.
746
+ this.engineExit = null;
747
+ this.engineExitPromise = undefined;
511
748
  const spawnPromise = this.bridge.spawn();
512
749
  let timeout: ReturnType<typeof setTimeout> | undefined;
513
750
  let abortHandler: (() => void) | undefined;
@@ -540,7 +777,9 @@ export class RustDbBridge extends EventEmitter {
540
777
 
541
778
  try {
542
779
  const spawned = await Promise.race([spawnPromise, cancellationPromise]);
780
+ if (spawned) this.watchEngineExit();
543
781
  if (!spawned) {
782
+ this.spawnRefusal = describeRefusedSpawn(this.binaryPath, Math.round(performance.now() - spawnStartedAt));
544
783
  await this.terminate({ gracePeriodMs: 0 });
545
784
  }
546
785
  return spawned;
@@ -652,7 +891,7 @@ export class RustDbBridge extends EventEmitter {
652
891
  const request = normalizeNoSqlDbStorageRehearsalInput(inputArg);
653
892
  validateNoSqlDbManagementOperationOptions(optionsArg);
654
893
  try {
655
- return await this.bridge.sendCommand('createStoppedStorageRehearsal',
894
+ return await this.send('createStoppedStorageRehearsal',
656
895
  { request, timeoutMs: optionsArg.timeoutMs }, optionsArg) as INoSqlDbStorageRehearsalReceipt;
657
896
  } catch (error) {
658
897
  await this.awaitManagementOperationTermination(error);
@@ -667,7 +906,7 @@ export class RustDbBridge extends EventEmitter {
667
906
  const input = normalizeLocalNoSqlDbStoppedStorageRootRelocationInput(inputArg);
668
907
  validateNoSqlDbManagementOperationOptions(optionsArg);
669
908
  try {
670
- return await this.bridge.sendCommand(
909
+ return await this.send(
671
910
  'relocateStoppedStorageRoot',
672
911
  { ...input, timeoutMs: optionsArg?.timeoutMs },
673
912
  optionsArg,
@@ -691,7 +930,7 @@ export class RustDbBridge extends EventEmitter {
691
930
  const input = normalizeStoppedStorageRootAlignmentInput(inputArg);
692
931
  validateNoSqlDbManagementOperationOptions(optionsArg);
693
932
  try {
694
- return await this.bridge.sendCommand(method, { ...input, timeoutMs: optionsArg?.timeoutMs }, optionsArg);
933
+ return await this.send(method, { ...input, timeoutMs: optionsArg?.timeoutMs }, optionsArg);
695
934
  } catch (error) {
696
935
  await this.awaitManagementOperationTermination(error);
697
936
  throw normalizeStoppedOperationUnknownMethod(error, method);
@@ -705,7 +944,7 @@ export class RustDbBridge extends EventEmitter {
705
944
  const input = normalizeStoppedStorageRootInspectionInput(inputArg);
706
945
  validateNoSqlDbManagementOperationOptions(optionsArg);
707
946
  try {
708
- return await this.bridge.sendCommand('inspectStoppedStorageRoot',
947
+ return await this.send('inspectStoppedStorageRoot',
709
948
  { ...input, timeoutMs: optionsArg?.timeoutMs }, optionsArg) as TLocalNoSqlDbStoppedStorageRootInspection;
710
949
  } catch (error) {
711
950
  await this.awaitManagementOperationTermination(error);
@@ -725,7 +964,7 @@ export class RustDbBridge extends EventEmitter {
725
964
  timeoutMs: optionsArg?.timeoutMs,
726
965
  };
727
966
  try {
728
- return await this.bridge.sendCommand(
967
+ return await this.send(
729
968
  'inspectOfflinePhysicalNamespaces',
730
969
  params,
731
970
  optionsArg,
@@ -743,7 +982,7 @@ export class RustDbBridge extends EventEmitter {
743
982
  const input = normalizeLocalNoSqlDbOfflineInspectionInput(inputArg);
744
983
  validateNoSqlDbManagementOperationOptions(optionsArg);
745
984
  try {
746
- return await this.bridge.sendCommand(
985
+ return await this.send(
747
986
  'inspectOfflineStringValue',
748
987
  { ...input, timeoutMs: optionsArg?.timeoutMs },
749
988
  optionsArg,
@@ -761,34 +1000,38 @@ export class RustDbBridge extends EventEmitter {
761
1000
  normalizeNoSqlDbFileStorageOptions(config);
762
1001
  validateNoSqlDbManagementOperationOptions(optionsArg);
763
1002
  try {
764
- return await this.bridge.sendCommand(
1003
+ return await this.send(
765
1004
  'start',
766
1005
  { config, timeoutMs: optionsArg?.timeoutMs },
767
1006
  optionsArg,
768
1007
  ) as { connectionUri: string };
769
1008
  } catch (error) {
770
1009
  await this.awaitManagementOperationTermination(error);
1010
+ // The unknown-root refusal is told apart by its code, never by its message.
1011
+ if (error instanceof Error && 'responseErrorCode' in error && error.responseErrorCode === 'UNKNOWN_ROOT') {
1012
+ throw new NoSqlDbUnknownRootError({ cause: error });
1013
+ }
771
1014
  throw error;
772
1015
  }
773
1016
  }
774
1017
 
775
1018
  public async stopDb(): Promise<void> {
776
- await this.bridge.sendCommand('stop', {} as Record<string, never>);
1019
+ await this.send('stop', {} as Record<string, never>);
777
1020
  }
778
1021
 
779
1022
  public async getStatus(): Promise<{ running: boolean }> {
780
- return await this.bridge.sendCommand('getStatus', {} as Record<string, never>) as { running: boolean };
1023
+ return await this.send('getStatus', {} as Record<string, never>) as { running: boolean };
781
1024
  }
782
1025
 
783
1026
  public async getMetrics(): Promise<INoSqlDbMetrics> {
784
- return this.bridge.sendCommand('getMetrics', {} as Record<string, never>) as Promise<INoSqlDbMetrics>;
1027
+ return this.send('getMetrics', {} as Record<string, never>) as Promise<INoSqlDbMetrics>;
785
1028
  }
786
1029
 
787
1030
  public async getHealth(
788
1031
  optionsArg?: INoSqlDbManagementOperationOptions,
789
1032
  ): Promise<INoSqlDbHealth> {
790
1033
  try {
791
- return await this.bridge.sendCommand(
1034
+ return await this.send(
792
1035
  'getHealth',
793
1036
  {} as Record<string, never>,
794
1037
  optionsArg,
@@ -803,7 +1046,7 @@ export class RustDbBridge extends EventEmitter {
803
1046
  params: INoSqlDbDatabaseTenantInput,
804
1047
  ): Promise<INoSqlDbDatabaseTenantDescriptor> {
805
1048
  try {
806
- return await this.bridge.sendCommand('createDatabaseTenant', params) as INoSqlDbDatabaseTenantDescriptor;
1049
+ return await this.send('createDatabaseTenant', params) as INoSqlDbDatabaseTenantDescriptor;
807
1050
  } catch (error) {
808
1051
  throw normalizeNoSqlDbResourceFenceError(error);
809
1052
  }
@@ -814,7 +1057,7 @@ export class RustDbBridge extends EventEmitter {
814
1057
  optionsArg?: INoSqlDbManagementOperationOptions,
815
1058
  ): Promise<INoSqlDbEnsureDatabaseTenantResult> {
816
1059
  try {
817
- return await this.bridge.sendCommand(
1060
+ return await this.send(
818
1061
  'ensureDatabaseTenant',
819
1062
  params,
820
1063
  optionsArg,
@@ -830,7 +1073,7 @@ export class RustDbBridge extends EventEmitter {
830
1073
  optionsArg?: INoSqlDbManagementOperationOptions,
831
1074
  ): Promise<INoSqlDbAttestDatabaseTenantResult> {
832
1075
  try {
833
- return await this.bridge.sendCommand(
1076
+ return await this.send(
834
1077
  'attestDatabaseTenant',
835
1078
  params,
836
1079
  optionsArg,
@@ -846,7 +1089,7 @@ export class RustDbBridge extends EventEmitter {
846
1089
  optionsArg?: INoSqlDbManagementOperationOptions,
847
1090
  ): Promise<INoSqlDbAllocateDatabaseTenantResult> {
848
1091
  try {
849
- return await this.bridge.sendCommand(
1092
+ return await this.send(
850
1093
  'allocateDatabaseTenant',
851
1094
  params,
852
1095
  optionsArg,
@@ -862,7 +1105,7 @@ export class RustDbBridge extends EventEmitter {
862
1105
  optionsArg?: INoSqlDbManagementOperationOptions,
863
1106
  ): Promise<INoSqlDbDeleteDatabaseTenantResult> {
864
1107
  try {
865
- return await this.bridge.sendCommand(
1108
+ return await this.send(
866
1109
  'deleteDatabaseTenant',
867
1110
  params,
868
1111
  optionsArg,
@@ -878,7 +1121,7 @@ export class RustDbBridge extends EventEmitter {
878
1121
  optionsArg?: INoSqlDbManagementOperationOptions,
879
1122
  ): Promise<INoSqlDbIssueDatabaseAllocationReadAccessGrantResult> {
880
1123
  try {
881
- return await this.bridge.sendCommand(
1124
+ return await this.send(
882
1125
  'issueDatabaseAllocationReadAccessGrant',
883
1126
  params,
884
1127
  optionsArg,
@@ -899,7 +1142,7 @@ export class RustDbBridge extends EventEmitter {
899
1142
  optionsArg?: INoSqlDbManagementOperationOptions,
900
1143
  ): Promise<INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult> {
901
1144
  try {
902
- return await this.bridge.sendCommand(
1145
+ return await this.send(
903
1146
  'revokeDatabaseAllocationReadAccessGrant',
904
1147
  params,
905
1148
  optionsArg,
@@ -918,11 +1161,11 @@ export class RustDbBridge extends EventEmitter {
918
1161
  public async rotateDatabaseTenantPassword(
919
1162
  params: INoSqlDbRotateDatabaseTenantPasswordInput,
920
1163
  ): Promise<INoSqlDbDatabaseTenantDescriptor> {
921
- return this.bridge.sendCommand('rotateDatabaseTenantPassword', params) as Promise<INoSqlDbDatabaseTenantDescriptor>;
1164
+ return this.send('rotateDatabaseTenantPassword', params) as Promise<INoSqlDbDatabaseTenantDescriptor>;
922
1165
  }
923
1166
 
924
1167
  public async listDatabaseTenants(): Promise<INoSqlDbDatabaseTenantDescriptor[]> {
925
- const result = await this.bridge.sendCommand('listDatabaseTenants', {} as Record<string, never>) as { tenants: INoSqlDbDatabaseTenantDescriptor[] };
1168
+ const result = await this.send('listDatabaseTenants', {} as Record<string, never>) as { tenants: INoSqlDbDatabaseTenantDescriptor[] };
926
1169
  return result.tenants;
927
1170
  }
928
1171
 
@@ -930,7 +1173,7 @@ export class RustDbBridge extends EventEmitter {
930
1173
  databaseName: string;
931
1174
  username: string;
932
1175
  }): Promise<INoSqlDbDatabaseTenantDescriptor> {
933
- return this.bridge.sendCommand('getDatabaseTenantDescriptor', params) as Promise<INoSqlDbDatabaseTenantDescriptor>;
1176
+ return this.send('getDatabaseTenantDescriptor', params) as Promise<INoSqlDbDatabaseTenantDescriptor>;
934
1177
  }
935
1178
 
936
1179
  public async getDatabaseResourceFenceState(
@@ -938,7 +1181,7 @@ export class RustDbBridge extends EventEmitter {
938
1181
  optionsArg?: INoSqlDbManagementOperationOptions,
939
1182
  ): Promise<INoSqlDbDatabaseResourceFenceState | null> {
940
1183
  try {
941
- return await this.bridge.sendCommand(
1184
+ return await this.send(
942
1185
  'getDatabaseResourceFenceState',
943
1186
  params,
944
1187
  optionsArg,
@@ -954,7 +1197,7 @@ export class RustDbBridge extends EventEmitter {
954
1197
  optionsArg?: INoSqlDbManagementOperationOptions,
955
1198
  ): Promise<INoSqlDbDatabaseExport> {
956
1199
  try {
957
- return await this.bridge.sendCommand(
1200
+ return await this.send(
958
1201
  'exportDatabase',
959
1202
  params,
960
1203
  optionsArg,
@@ -970,7 +1213,7 @@ export class RustDbBridge extends EventEmitter {
970
1213
  optionsArg?: INoSqlDbManagementOperationOptions,
971
1214
  ): Promise<INoSqlDbDatabaseExport> {
972
1215
  try {
973
- return await this.bridge.sendCommand(
1216
+ return await this.send(
974
1217
  'exportHeldDatabase',
975
1218
  params,
976
1219
  optionsArg,
@@ -986,7 +1229,7 @@ export class RustDbBridge extends EventEmitter {
986
1229
  optionsArg?: INoSqlDbManagementOperationOptions,
987
1230
  ): Promise<INoSqlDbDatabaseContentDigest> {
988
1231
  try {
989
- return await this.bridge.sendCommand(
1232
+ return await this.send(
990
1233
  'getDatabaseContentDigest',
991
1234
  params,
992
1235
  optionsArg,
@@ -1002,7 +1245,7 @@ export class RustDbBridge extends EventEmitter {
1002
1245
  optionsArg?: INoSqlDbManagementOperationOptions,
1003
1246
  ): Promise<INoSqlDbImportDatabaseResult> {
1004
1247
  try {
1005
- return await this.bridge.sendCommand(
1248
+ return await this.send(
1006
1249
  'importDatabase',
1007
1250
  { ...params, timeoutMs: optionsArg?.timeoutMs },
1008
1251
  optionsArg,
@@ -1018,7 +1261,7 @@ export class RustDbBridge extends EventEmitter {
1018
1261
  optionsArg?: INoSqlDbManagementOperationOptions,
1019
1262
  ): Promise<TNoSqlDbCommitDatabasePublicationResult> {
1020
1263
  try {
1021
- return await this.bridge.sendCommand(
1264
+ return await this.send(
1022
1265
  'commitDatabasePublication',
1023
1266
  params,
1024
1267
  optionsArg,
@@ -1035,23 +1278,23 @@ export class RustDbBridge extends EventEmitter {
1035
1278
  db?: string;
1036
1279
  collection?: string;
1037
1280
  } = {}): Promise<IOpLogResult> {
1038
- return this.bridge.sendCommand('getOpLog', params) as Promise<IOpLogResult>;
1281
+ return this.send('getOpLog', params) as Promise<IOpLogResult>;
1039
1282
  }
1040
1283
 
1041
1284
  public async getCommittedWrites(params: INoSqlDbGetCommittedWritesInput): Promise<INoSqlDbCommittedWritesPage> {
1042
- return this.bridge.sendCommand('getCommittedWrites', params);
1285
+ return this.send('getCommittedWrites', params);
1043
1286
  }
1044
1287
 
1045
1288
  public async getOpLogStats(): Promise<IOpLogStats> {
1046
- return this.bridge.sendCommand('getOpLogStats', {} as Record<string, never>) as Promise<IOpLogStats>;
1289
+ return this.send('getOpLogStats', {} as Record<string, never>) as Promise<IOpLogStats>;
1047
1290
  }
1048
1291
 
1049
1292
  public async revertToSeq(seq: number, dryRun = false): Promise<IRevertResult> {
1050
- return this.bridge.sendCommand('revertToSeq', { seq, dryRun }) as Promise<IRevertResult>;
1293
+ return this.send('revertToSeq', { seq, dryRun }) as Promise<IRevertResult>;
1051
1294
  }
1052
1295
 
1053
1296
  public async getCollections(db?: string): Promise<ICollectionInfo[]> {
1054
- const result = await this.bridge.sendCommand('getCollections', db ? { db } : {}) as { collections: ICollectionInfo[] };
1297
+ const result = await this.send('getCollections', db ? { db } : {}) as { collections: ICollectionInfo[] };
1055
1298
  return result.collections;
1056
1299
  }
1057
1300
 
@@ -1061,6 +1304,6 @@ export class RustDbBridge extends EventEmitter {
1061
1304
  limit = 50,
1062
1305
  skip = 0,
1063
1306
  ): Promise<IDocumentsResult> {
1064
- return this.bridge.sendCommand('getDocuments', { db, collection, limit, skip }) as Promise<IDocumentsResult>;
1307
+ return this.send('getDocuments', { db, collection, limit, skip }) as Promise<IDocumentsResult>;
1065
1308
  }
1066
1309
  }