@lossless.org/nosqldb 10.11.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 (55) hide show
  1. package/.smartconfig.json +117 -0
  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/dist_ts_debugserver/bundled.js +6 -2
  31. package/package.json +28 -15
  32. package/readme.md +470 -730
  33. package/third-party-notices.md +40 -8
  34. package/third_party_licenses/inventory.json +6 -6
  35. package/ts/00_commitinfo_data.ts +8 -0
  36. package/ts/index.ts +85 -0
  37. package/ts/ts_local/classes.localnosqldb.ts +631 -0
  38. package/ts/ts_local/index.ts +19 -0
  39. package/ts/ts_local/plugins.ts +1 -0
  40. package/ts/ts_nosqldb/classes.storagerehearsal.ts +109 -0
  41. package/ts/ts_nosqldb/committed-writes.ts +38 -0
  42. package/ts/ts_nosqldb/file-storage-options.ts +33 -0
  43. package/ts/ts_nosqldb/file-storage-startup.ts +10 -0
  44. package/ts/ts_nosqldb/index.ts +132 -0
  45. package/ts/ts_nosqldb/offline-inspection.ts +375 -0
  46. package/ts/ts_nosqldb/plugins.ts +12 -0
  47. package/ts/ts_nosqldb/resource-fencing.ts +368 -0
  48. package/ts/ts_nosqldb/rust-db-bridge.ts +1309 -0
  49. package/ts/ts_nosqldb/server/classes.nosqldbserver.ts +874 -0
  50. package/ts/ts_nosqldb/server/index.ts +4 -0
  51. package/ts/ts_nosqldb/service-types.ts +499 -0
  52. package/ts/ts_nosqldb/stopped-storage.ts +116 -0
  53. package/ts/ts_nosqldb/storage-rehearsal.ts +215 -0
  54. package/ts/ts_nosqldb/storage-root-alignment.ts +180 -0
  55. package/ts/ts_nosqldb/storage-root-relocation.ts +279 -0
@@ -0,0 +1,1309 @@
1
+ import { normalizeStoppedStorageRootAlignmentInput, type ILocalNoSqlDbStoppedStorageRootAlignmentInput, type TLocalNoSqlDbStoppedStorageRootAlignmentResult, type ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult } from './storage-root-alignment.js';
2
+ import { normalizeStoppedStorageRootInspectionInput, normalizeStoppedOperationUnknownMethod, type ILocalNoSqlDbStoppedStorageRootInspectionInput, type TLocalNoSqlDbStoppedStorageRootInspection } from './stopped-storage.js';
3
+ import * as plugins from './plugins.js';
4
+ import type { INoSqlDbGetCommittedWritesInput, INoSqlDbCommittedWritesPage } from './committed-writes.js';
5
+ import { normalizeNoSqlDbFileStorageOptions } from './file-storage-options.js';
6
+ import type { TNoSqlDbFileStorageStartup } from './file-storage-startup.js';
7
+ import * as path from 'node:path';
8
+ import * as url from 'node:url';
9
+ import type { ChildProcess } from 'node:child_process';
10
+ import { EventEmitter } from 'node:events';
11
+ import {
12
+ normalizeNoSqlDbStorageRehearsalInput,
13
+ type INoSqlDbStorageRehearsalInput,
14
+ type INoSqlDbStorageRehearsalReceipt,
15
+ type TNormalizedNoSqlDbStorageRehearsalInput,
16
+ } from './storage-rehearsal.js';
17
+ import {
18
+ normalizeNoSqlDbAllocationReadAccessGrantError,
19
+ normalizeNoSqlDbResourceFenceError,
20
+ } from './service-types.js';
21
+ import {
22
+ normalizeLocalNoSqlDbOfflinePhysicalNamespaceInspectionInput,
23
+ normalizeLocalNoSqlDbOfflineInspectionInput,
24
+ validateNoSqlDbManagementOperationOptions,
25
+ } from './offline-inspection.js';
26
+ import {
27
+ normalizeLocalNoSqlDbStoppedStorageRootRelocationInput,
28
+ type ILocalNoSqlDbStoppedStorageRootRelocationInput,
29
+ type ILocalNoSqlDbStoppedStorageRootRelocationReceipt,
30
+ } from './storage-root-relocation.js';
31
+ import type {
32
+ INoSqlDbHealth,
33
+ INoSqlDbDatabaseTenantInput,
34
+ INoSqlDbAttestDatabaseTenantInput,
35
+ INoSqlDbAttestDatabaseTenantResult,
36
+ INoSqlDbEnsureDatabaseTenantInput,
37
+ INoSqlDbEnsureDatabaseTenantResult,
38
+ INoSqlDbAllocateDatabaseTenantInput,
39
+ INoSqlDbAllocateDatabaseTenantResult,
40
+ INoSqlDbDeleteDatabaseTenantInput,
41
+ INoSqlDbRotateDatabaseTenantPasswordInput,
42
+ INoSqlDbDatabaseTenantDescriptor,
43
+ INoSqlDbDeleteDatabaseTenantResult,
44
+ INoSqlDbIssueDatabaseAllocationReadAccessGrantInput,
45
+ INoSqlDbIssueDatabaseAllocationReadAccessGrantResult,
46
+ INoSqlDbRevokeDatabaseAllocationReadAccessGrantInput,
47
+ INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult,
48
+ INoSqlDbDatabaseExport,
49
+ INoSqlDbExportDatabaseInput,
50
+ INoSqlDbExportHeldDatabaseInput,
51
+ INoSqlDbDatabaseContentDigest,
52
+ INoSqlDbGetDatabaseContentDigestInput,
53
+ INoSqlDbImportDatabaseInput,
54
+ INoSqlDbImportDatabaseResult,
55
+ INoSqlDbCommitDatabasePublicationInput,
56
+ INoSqlDbManagementOperationOptions,
57
+ INoSqlDbDatabaseResourceFenceState,
58
+ ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
59
+ ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult,
60
+ ILocalNoSqlDbOfflineStringValueInspection,
61
+ TLocalNoSqlDbOfflineStringValueInspectionResult,
62
+ TNoSqlDbCommitDatabasePublicationResult,
63
+ } from './service-types.js';
64
+
65
+ export type {
66
+ INoSqlDbHealth,
67
+ INoSqlDbDatabaseTenantInput,
68
+ INoSqlDbAttestDatabaseTenantInput,
69
+ INoSqlDbAttestDatabaseTenantResult,
70
+ INoSqlDbEnsureDatabaseTenantInput,
71
+ INoSqlDbEnsureDatabaseTenantResult,
72
+ INoSqlDbAllocateDatabaseTenantInput,
73
+ INoSqlDbAllocateDatabaseTenantResult,
74
+ INoSqlDbDeleteDatabaseTenantInput,
75
+ INoSqlDbRotateDatabaseTenantPasswordInput,
76
+ INoSqlDbDatabaseTenantDescriptor,
77
+ INoSqlDbDeleteDatabaseTenantResult,
78
+ INoSqlDbDatabaseAllocationReadAccessGrantIdentity,
79
+ INoSqlDbIssueDatabaseAllocationReadAccessGrantInput,
80
+ INoSqlDbIssueDatabaseAllocationReadAccessGrantResult,
81
+ INoSqlDbRevokeDatabaseAllocationReadAccessGrantInput,
82
+ INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult,
83
+ INoSqlDbDatabaseExportCollection,
84
+ INoSqlDbDatabaseExport,
85
+ INoSqlDbImportDatabaseInput,
86
+ INoSqlDbImportDatabaseResult,
87
+ INoSqlDbHeldPublicationReceipt,
88
+ INoSqlDbCommitDatabasePublicationInput,
89
+ INoSqlDbManagementOperationOptions,
90
+ INoSqlDbDatabaseResourceFenceState,
91
+ ILocalNoSqlDbOfflineInspectionLimits,
92
+ ILocalNoSqlDbOfflinePhysicalNamespaceInspectionLimits,
93
+ ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
94
+ ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult,
95
+ ILocalNoSqlDbOfflineStringValueInspection,
96
+ TLocalNoSqlDbOfflineStringValueInspectionResult,
97
+ TNoSqlDbCommitDatabasePublicationResult,
98
+ } from './service-types.js';
99
+
100
+ /**
101
+ * A single oplog entry returned from the Rust engine.
102
+ */
103
+ export interface IOpLogEntry {
104
+ seq: number;
105
+ timestampMs: number;
106
+ op: 'insert' | 'update' | 'delete';
107
+ db: string;
108
+ collection: string;
109
+ documentId: string;
110
+ document: Record<string, any> | null;
111
+ previousDocument: Record<string, any> | null;
112
+ }
113
+
114
+ /**
115
+ * Aggregate oplog statistics.
116
+ */
117
+ export interface IOpLogStats {
118
+ currentSeq: number;
119
+ totalEntries: number;
120
+ oldestSeq: number;
121
+ /** Approximate retained bytes: the in-memory oplog entries and the table holding them. */
122
+ approxBytes: number;
123
+ entriesByOp: {
124
+ insert: number;
125
+ update: number;
126
+ delete: number;
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Result of a getOpLog query.
132
+ */
133
+ export interface IOpLogResult {
134
+ entries: IOpLogEntry[];
135
+ currentSeq: number;
136
+ totalEntries: number;
137
+ }
138
+
139
+ /**
140
+ * Result of a revertToSeq command.
141
+ */
142
+ export interface IRevertResult {
143
+ dryRun: boolean;
144
+ reverted: number;
145
+ targetSeq?: number;
146
+ entries?: IOpLogEntry[];
147
+ errors?: string[];
148
+ }
149
+
150
+ /**
151
+ * A collection info entry.
152
+ */
153
+ export interface ICollectionInfo {
154
+ db: string;
155
+ name: string;
156
+ count: number;
157
+ }
158
+
159
+ /**
160
+ * Result of a getDocuments query.
161
+ */
162
+ export interface IDocumentsResult {
163
+ documents: Record<string, any>[];
164
+ total: number;
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
+
253
+ export class NoSqlDbManagementOperationTerminationError extends AggregateError {
254
+ constructor(operationErrorArg: unknown, terminationErrorArg: unknown) {
255
+ super(
256
+ [operationErrorArg, terminationErrorArg],
257
+ 'NoSQLDB management operation failed and Rust engine termination was incomplete',
258
+ { cause: operationErrorArg },
259
+ );
260
+ this.name = 'NoSqlDbManagementOperationTerminationError';
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Server metrics.
266
+ */
267
+ export interface INoSqlDbMetrics {
268
+ databases: number;
269
+ collections: number;
270
+ oplogEnabled: boolean;
271
+ /** Null when the oplog is disabled. */
272
+ oplogEntries: number | null;
273
+ /** Null when the oplog is disabled. */
274
+ oplogCurrentSeq: number | null;
275
+ sessions: number;
276
+ activeTransactions: number;
277
+ authEnabled: boolean;
278
+ authUsers: number;
279
+ uptimeSeconds: number;
280
+ }
281
+
282
+ /**
283
+ * Type-safe command definitions for the RustDb IPC protocol.
284
+ */
285
+ type TOfflinePhysicalNamespaceInspectionCommandParams = {
286
+ folderPath: string;
287
+ timeoutMs?: number;
288
+ } & ILocalNoSqlDbOfflinePhysicalNamespaceInspection['limits'];
289
+
290
+ export const rustDbBridgeStorageRootAlignment = Symbol('storageRootAlignment');
291
+ export const rustDbBridgeStorageRootAlignmentAbort = Symbol('storageRootAlignmentAbort');
292
+
293
+ type TNoSqlDbCommands = {
294
+ alignStoppedStorageRoot: { params: ILocalNoSqlDbStoppedStorageRootAlignmentInput & { timeoutMs?: number }; result: TLocalNoSqlDbStoppedStorageRootAlignmentResult };
295
+ abortStoppedStorageRootAlignment: { params: ILocalNoSqlDbStoppedStorageRootAlignmentInput & { timeoutMs?: number }; result: ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult };
296
+ inspectStoppedStorageRoot: {
297
+ params: ILocalNoSqlDbStoppedStorageRootInspectionInput & { timeoutMs?: number };
298
+ result: TLocalNoSqlDbStoppedStorageRootInspection;
299
+ };
300
+ createStoppedStorageRehearsal: {
301
+ params: { request: TNormalizedNoSqlDbStorageRehearsalInput; timeoutMs: number };
302
+ result: INoSqlDbStorageRehearsalReceipt;
303
+ };
304
+ relocateStoppedStorageRoot: {
305
+ params: ILocalNoSqlDbStoppedStorageRootRelocationInput & { timeoutMs?: number };
306
+ result: ILocalNoSqlDbStoppedStorageRootRelocationReceipt;
307
+ };
308
+ inspectOfflinePhysicalNamespaces: {
309
+ params: TOfflinePhysicalNamespaceInspectionCommandParams;
310
+ result: ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult;
311
+ };
312
+ inspectOfflineStringValue: {
313
+ params: ILocalNoSqlDbOfflineStringValueInspection & { timeoutMs?: number };
314
+ result: TLocalNoSqlDbOfflineStringValueInspectionResult;
315
+ };
316
+ start: { params: { config: INoSqlDbRustConfig; timeoutMs?: number }; result: { connectionUri: string } };
317
+ stop: { params: Record<string, never>; result: void };
318
+ getStatus: { params: Record<string, never>; result: { running: boolean } };
319
+ getHealth: { params: Record<string, never>; result: INoSqlDbHealth };
320
+ getMetrics: { params: Record<string, never>; result: INoSqlDbMetrics };
321
+ createDatabaseTenant: {
322
+ params: INoSqlDbDatabaseTenantInput;
323
+ result: INoSqlDbDatabaseTenantDescriptor;
324
+ };
325
+ allocateDatabaseTenant: {
326
+ params: INoSqlDbAllocateDatabaseTenantInput;
327
+ result: INoSqlDbAllocateDatabaseTenantResult;
328
+ };
329
+ ensureDatabaseTenant: {
330
+ params: INoSqlDbEnsureDatabaseTenantInput;
331
+ result: INoSqlDbEnsureDatabaseTenantResult;
332
+ };
333
+ attestDatabaseTenant: {
334
+ params: INoSqlDbAttestDatabaseTenantInput;
335
+ result: INoSqlDbAttestDatabaseTenantResult;
336
+ };
337
+ deleteDatabaseTenant: {
338
+ params: INoSqlDbDeleteDatabaseTenantInput;
339
+ result: INoSqlDbDeleteDatabaseTenantResult;
340
+ };
341
+ issueDatabaseAllocationReadAccessGrant: {
342
+ params: INoSqlDbIssueDatabaseAllocationReadAccessGrantInput;
343
+ result: INoSqlDbIssueDatabaseAllocationReadAccessGrantResult;
344
+ };
345
+ revokeDatabaseAllocationReadAccessGrant: {
346
+ params: INoSqlDbRevokeDatabaseAllocationReadAccessGrantInput;
347
+ result: INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult;
348
+ };
349
+ rotateDatabaseTenantPassword: {
350
+ params: INoSqlDbRotateDatabaseTenantPasswordInput;
351
+ result: INoSqlDbDatabaseTenantDescriptor;
352
+ };
353
+ listDatabaseTenants: {
354
+ params: Record<string, never>;
355
+ result: { tenants: INoSqlDbDatabaseTenantDescriptor[] };
356
+ };
357
+ getDatabaseTenantDescriptor: {
358
+ params: { databaseName: string; username: string };
359
+ result: INoSqlDbDatabaseTenantDescriptor;
360
+ };
361
+ getDatabaseResourceFenceState: {
362
+ params: { databaseName: string };
363
+ result: INoSqlDbDatabaseResourceFenceState | null;
364
+ };
365
+ exportDatabase: {
366
+ params: INoSqlDbExportDatabaseInput;
367
+ result: INoSqlDbDatabaseExport;
368
+ };
369
+ exportHeldDatabase: {
370
+ params: INoSqlDbExportHeldDatabaseInput;
371
+ result: INoSqlDbDatabaseExport;
372
+ };
373
+ getDatabaseContentDigest: {
374
+ params: INoSqlDbGetDatabaseContentDigestInput;
375
+ result: INoSqlDbDatabaseContentDigest;
376
+ };
377
+ importDatabase: {
378
+ params: INoSqlDbImportDatabaseInput & { timeoutMs?: number };
379
+ result: INoSqlDbImportDatabaseResult;
380
+ };
381
+ commitDatabasePublication: {
382
+ params: INoSqlDbCommitDatabasePublicationInput;
383
+ result: TNoSqlDbCommitDatabasePublicationResult;
384
+ };
385
+ getOpLog: {
386
+ params: { sinceSeq?: number; limit?: number; db?: string; collection?: string };
387
+ result: IOpLogResult;
388
+ };
389
+ getCommittedWrites: {
390
+ params: INoSqlDbGetCommittedWritesInput;
391
+ result: INoSqlDbCommittedWritesPage;
392
+ };
393
+ getOpLogStats: { params: Record<string, never>; result: IOpLogStats };
394
+ revertToSeq: {
395
+ params: { seq: number; dryRun?: boolean };
396
+ result: IRevertResult;
397
+ };
398
+ getCollections: {
399
+ params: { db?: string };
400
+ result: { collections: ICollectionInfo[] };
401
+ };
402
+ getDocuments: {
403
+ params: { db: string; collection: string; limit?: number; skip?: number };
404
+ result: IDocumentsResult;
405
+ };
406
+ };
407
+
408
+ export const rustDbBridgeStorageRootRelocation = Symbol(
409
+ 'nosqldb.internal.storage-root-relocation',
410
+ );
411
+ export const rustDbBridgeStorageRehearsal = Symbol('nosqldb.internal.storage-rehearsal');
412
+
413
+ /**
414
+ * Configuration sent to the Rust binary on start.
415
+ */
416
+ export interface INoSqlDbQueryOptions {
417
+ /** Target encoded bytes per in-memory sort run (default: 4 MiB). */
418
+ sortBufferBytes?: number;
419
+ /** Shared temporary sort-file budget, including merge output (default: 4 GiB). */
420
+ maxSortSpillBytes?: number;
421
+ /** Concurrent sort builders (default: 4). */
422
+ maxConcurrentSorts?: number;
423
+ }
424
+
425
+ /**
426
+ * Memory budgets the server's connections share for received requests, and
427
+ * the rate a frame larger than the 8 KiB receive buffer must arrive at while
428
+ * others need the encoded budget. An unset budget is sized from the process's
429
+ * cgroup memory limit, or physical memory. Each set budget must be 64 MiB to
430
+ * 1 TiB.
431
+ */
432
+ export interface INoSqlDbInboundOptions {
433
+ /** Encoded bytes of received frames until they are decoded (default: a sixteenth of the memory limit, 64–256 MiB). */
434
+ encodedBytes?: number;
435
+ /** Memory of decoded requests; a request that needs more is refused (default: a quarter of the memory limit, 64 MiB–1 GiB). */
436
+ decodedBytes?: number;
437
+ /** Milliseconds a large frame has, once its encoded budget is held, before its rate counts (default: 10000; 1 to 300000). */
438
+ frameGraceMs?: number;
439
+ /** Slowest average rate a large frame may arrive at after its grace while another connection waits for the encoded budget or more than seven eighths of it are held; the slowest frame below it then closes its connection (default: 1048576; at least 1). */
440
+ minFrameBytesPerSecond?: number;
441
+ }
442
+
443
+ interface INoSqlDbRustConfig {
444
+ fileStorageStartup?: TNoSqlDbFileStorageStartup;
445
+ storageRehearsalId?: string;
446
+ port?: number;
447
+ host?: string;
448
+ socketPath?: string;
449
+ storage: 'memory' | 'file';
450
+ storagePath?: string;
451
+ query?: INoSqlDbQueryOptions;
452
+ inbound?: INoSqlDbInboundOptions;
453
+ storageBudgetBytes?: number;
454
+ auth?: {
455
+ enabled?: boolean;
456
+ users?: Array<{
457
+ username: string;
458
+ password: string;
459
+ database?: string;
460
+ roles?: string[];
461
+ }>;
462
+ scramIterations?: number;
463
+ };
464
+ tls?: {
465
+ enabled?: boolean;
466
+ certPath?: string;
467
+ keyPath?: string;
468
+ caPath?: string;
469
+ requireClientCert?: boolean;
470
+ };
471
+ oplog?: {
472
+ enabled?: boolean;
473
+ maxEntries?: number;
474
+ maxBytes?: number;
475
+ };
476
+ }
477
+
478
+ /**
479
+ * Get the package root directory using import.meta.url.
480
+ * This file is at ts/ts_nosqldb/, so package root is 2 levels up.
481
+ */
482
+ function getPackageRoot(): string {
483
+ const thisDir = path.dirname(url.fileURLToPath(import.meta.url));
484
+ return path.resolve(thisDir, '..', '..');
485
+ }
486
+
487
+ /**
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.
490
+ */
491
+ function getTsrustPlatformSuffix(): string | null {
492
+ const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' };
493
+ const osMap: Record<string, string> = { linux: 'linux', darwin: 'macos' };
494
+ const os = osMap[process.platform];
495
+ const arch = archMap[process.arch];
496
+ if (os && arch) {
497
+ // Static musl runs on both Alpine and glibc hosts without probing host libc.
498
+ return `${os}_${arch}${os === 'linux' ? '_musl' : ''}`;
499
+ }
500
+ return null;
501
+ }
502
+
503
+ /**
504
+ * Build local search paths for the Rust binary, including dist_rust/ candidates
505
+ * (built by rsrepo) and local development build paths.
506
+ */
507
+ function buildLocalPaths(): string[] {
508
+ const packageRoot = getPackageRoot();
509
+ const suffix = getTsrustPlatformSuffix();
510
+ const paths: string[] = [];
511
+
512
+ // dist_rust/ candidates (rsrepo cross-compiled output)
513
+ if (suffix) {
514
+ paths.push(path.join(packageRoot, 'dist_rust', `rustdb_${suffix}`));
515
+ }
516
+ paths.push(path.join(packageRoot, 'dist_rust', 'rustdb'));
517
+
518
+ // Local dev build paths
519
+ paths.push(path.resolve(process.cwd(), 'rust', 'target', 'release', 'rustdb'));
520
+ paths.push(path.resolve(process.cwd(), 'rust', 'target', 'debug', 'rustdb'));
521
+
522
+ return paths;
523
+ }
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`;
543
+ }
544
+
545
+ export interface IRustDbBridgeOptions {
546
+ /** Exact engine executable, validated by Smartrust without discovery fallback or permission changes. */
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;
558
+ }
559
+
560
+ /**
561
+ * Bridge between TypeScript NoSqlDbServer and the Rust binary.
562
+ * Wraps @push.rocks/smartrust's RustBridge with type-safe command definitions.
563
+ */
564
+ export class RustDbBridge extends EventEmitter {
565
+ private bridge: plugins.smartrust.RustBridge<TNoSqlDbCommands>;
566
+
567
+ /**
568
+ * Operating-system process id of the spawned Rust engine while it runs as a
569
+ * child process of this Node.js process. Undefined before spawn, after
570
+ * termination, or when the bridge is connected to an external process.
571
+ */
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 {
578
+ const transport = (this.bridge as unknown as {
579
+ transport?: { childProcess?: ChildProcess | null };
580
+ }).transport;
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;
638
+ }
639
+ private terminationConfirmed = true;
640
+ private terminationPromise: Promise<void> | undefined;
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
+ }
663
+
664
+ constructor(options: IRustDbBridgeOptions = {}) {
665
+ super();
666
+ this.binaryPath = options.binaryPath;
667
+
668
+ this.bridge = new plugins.smartrust.RustBridge<TNoSqlDbCommands>({
669
+ binaryName: 'rustdb',
670
+ binaryPath: options.binaryPath,
671
+ envVarName: 'NOSQLDB_RUST_BINARY',
672
+ platformPackagePrefix: '@lossless.org/nosqldb',
673
+ localPaths: buildLocalPaths(),
674
+ maxPayloadSize: 100 * 1024 * 1024, // database exports/imports can be larger than command replies
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));
683
+ });
684
+
685
+ // Forward events from the inner bridge
686
+ this.bridge.on('exit', (code: number | null, signal: string | null) => {
687
+ this.terminationConfirmed = true;
688
+ this.emit('exit', code, signal);
689
+ });
690
+ }
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
+
717
+ /**
718
+ * Spawn the Rust binary in management mode.
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.
726
+ */
727
+ public async spawn(optionsArg?: INoSqlDbManagementOperationOptions): Promise<boolean> {
728
+ this.spawnRefusal = undefined;
729
+ validateNoSqlDbManagementOperationOptions(optionsArg);
730
+ if (optionsArg?.signal?.aborted) {
731
+ throw new plugins.smartrust.RustBridgeRequestError(
732
+ 'NoSQLDB Rust sidecar spawn was aborted before it started',
733
+ {
734
+ code: 'ERR_RUST_BRIDGE_REQUEST_ABORTED',
735
+ method: 'spawn',
736
+ cause: optionsArg.signal.reason,
737
+ },
738
+ );
739
+ }
740
+
741
+ this.terminationConfirmed = false;
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;
748
+ const spawnPromise = this.bridge.spawn();
749
+ let timeout: ReturnType<typeof setTimeout> | undefined;
750
+ let abortHandler: (() => void) | undefined;
751
+ const cancellationPromise = new Promise<never>((_resolveArg, rejectArg) => {
752
+ if (optionsArg?.timeoutMs !== undefined) {
753
+ timeout = setTimeout(() => {
754
+ rejectArg(new plugins.smartrust.RustBridgeRequestError(
755
+ `NoSQLDB Rust sidecar spawn timed out after ${optionsArg.timeoutMs}ms`,
756
+ {
757
+ code: 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT',
758
+ method: 'spawn',
759
+ },
760
+ ));
761
+ }, optionsArg.timeoutMs);
762
+ }
763
+ if (optionsArg?.signal) {
764
+ abortHandler = () => {
765
+ rejectArg(new plugins.smartrust.RustBridgeRequestError(
766
+ 'NoSQLDB Rust sidecar spawn was aborted',
767
+ {
768
+ code: 'ERR_RUST_BRIDGE_REQUEST_ABORTED',
769
+ method: 'spawn',
770
+ cause: optionsArg.signal!.reason,
771
+ },
772
+ ));
773
+ };
774
+ optionsArg.signal.addEventListener('abort', abortHandler, { once: true });
775
+ }
776
+ });
777
+
778
+ try {
779
+ const spawned = await Promise.race([spawnPromise, cancellationPromise]);
780
+ if (spawned) this.watchEngineExit();
781
+ if (!spawned) {
782
+ this.spawnRefusal = describeRefusedSpawn(this.binaryPath, Math.round(performance.now() - spawnStartedAt));
783
+ await this.terminate({ gracePeriodMs: 0 });
784
+ }
785
+ return spawned;
786
+ } catch (error) {
787
+ try {
788
+ await this.terminate({ gracePeriodMs: 0 });
789
+ } catch (terminationError) {
790
+ throw new NoSqlDbManagementOperationTerminationError(error, terminationError);
791
+ }
792
+ throw error;
793
+ } finally {
794
+ if (timeout) clearTimeout(timeout);
795
+ if (abortHandler && optionsArg?.signal) {
796
+ optionsArg.signal.removeEventListener('abort', abortHandler);
797
+ }
798
+ }
799
+ }
800
+
801
+ /**
802
+ * Kill the Rust process and clean up.
803
+ */
804
+ public kill(): void {
805
+ this.bridge.kill();
806
+ }
807
+
808
+ /**
809
+ * Confirm that the Rust process has exited and all bridge resources are released.
810
+ */
811
+ public terminate(
812
+ optionsArg?: plugins.smartrust.IRustBridgeTerminateOptions,
813
+ ): Promise<void> {
814
+ if (this.terminationPromise) {
815
+ return this.terminationPromise;
816
+ }
817
+
818
+ const terminationPromise = this.bridge.terminate(optionsArg).then(
819
+ () => {
820
+ this.terminationConfirmed = true;
821
+ this.terminationFailure = undefined;
822
+ },
823
+ (errorArg: unknown) => {
824
+ this.terminationFailure = errorArg;
825
+ throw errorArg;
826
+ },
827
+ );
828
+ this.terminationPromise = terminationPromise;
829
+ void terminationPromise.then(
830
+ () => {
831
+ if (this.terminationPromise === terminationPromise) {
832
+ this.terminationPromise = undefined;
833
+ }
834
+ },
835
+ () => {
836
+ if (this.terminationPromise === terminationPromise) {
837
+ this.terminationPromise = undefined;
838
+ }
839
+ },
840
+ );
841
+ return terminationPromise;
842
+ }
843
+
844
+ /**
845
+ * Whether the bridge is currently running.
846
+ */
847
+ public get running(): boolean {
848
+ return this.bridge.running;
849
+ }
850
+
851
+ public get ownershipReleased(): boolean {
852
+ return this.terminationConfirmed;
853
+ }
854
+
855
+ private async awaitManagementOperationTermination(errorArg: unknown) {
856
+ const cancellationRequiresTermination =
857
+ errorArg instanceof plugins.smartrust.RustBridgeRequestError
858
+ && [
859
+ 'ERR_RUST_BRIDGE_REQUEST_ABORTED',
860
+ 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT',
861
+ ].includes(errorArg.code);
862
+ let terminationPromise = this.terminationPromise;
863
+ if (cancellationRequiresTermination && !terminationPromise) {
864
+ terminationPromise = this.terminate({ gracePeriodMs: 0 });
865
+ }
866
+
867
+ if (!terminationPromise) {
868
+ if (!this.terminationConfirmed && this.terminationFailure !== undefined) {
869
+ throw new NoSqlDbManagementOperationTerminationError(
870
+ errorArg,
871
+ this.terminationFailure,
872
+ );
873
+ }
874
+ return;
875
+ }
876
+
877
+ try {
878
+ await terminationPromise;
879
+ } catch (terminationError) {
880
+ throw new NoSqlDbManagementOperationTerminationError(
881
+ errorArg,
882
+ terminationError,
883
+ );
884
+ }
885
+ }
886
+
887
+ // --- Convenience methods for each management command ---
888
+
889
+ public async [rustDbBridgeStorageRehearsal](inputArg: INoSqlDbStorageRehearsalInput,
890
+ optionsArg: INoSqlDbManagementOperationOptions & { timeoutMs: number }): Promise<INoSqlDbStorageRehearsalReceipt> {
891
+ const request = normalizeNoSqlDbStorageRehearsalInput(inputArg);
892
+ validateNoSqlDbManagementOperationOptions(optionsArg);
893
+ try {
894
+ return await this.send('createStoppedStorageRehearsal',
895
+ { request, timeoutMs: optionsArg.timeoutMs }, optionsArg) as INoSqlDbStorageRehearsalReceipt;
896
+ } catch (error) {
897
+ await this.awaitManagementOperationTermination(error);
898
+ throw normalizeStoppedOperationUnknownMethod(error, 'createStoppedStorageRehearsal');
899
+ }
900
+ }
901
+
902
+ public async [rustDbBridgeStorageRootRelocation](
903
+ inputArg: ILocalNoSqlDbStoppedStorageRootRelocationInput,
904
+ optionsArg?: INoSqlDbManagementOperationOptions,
905
+ ): Promise<ILocalNoSqlDbStoppedStorageRootRelocationReceipt> {
906
+ const input = normalizeLocalNoSqlDbStoppedStorageRootRelocationInput(inputArg);
907
+ validateNoSqlDbManagementOperationOptions(optionsArg);
908
+ try {
909
+ return await this.send(
910
+ 'relocateStoppedStorageRoot',
911
+ { ...input, timeoutMs: optionsArg?.timeoutMs },
912
+ optionsArg,
913
+ ) as ILocalNoSqlDbStoppedStorageRootRelocationReceipt;
914
+ } catch (error) {
915
+ await this.awaitManagementOperationTermination(error);
916
+ throw normalizeStoppedOperationUnknownMethod(error, 'relocateStoppedStorageRoot');
917
+ }
918
+ }
919
+
920
+ public async [rustDbBridgeStorageRootAlignment](inputArg: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
921
+ optionsArg?: INoSqlDbManagementOperationOptions): Promise<TLocalNoSqlDbStoppedStorageRootAlignmentResult> {
922
+ return this.stoppedAlignmentCommand('alignStoppedStorageRoot', inputArg, optionsArg) as Promise<TLocalNoSqlDbStoppedStorageRootAlignmentResult>;
923
+ }
924
+ public async [rustDbBridgeStorageRootAlignmentAbort](inputArg: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
925
+ optionsArg?: INoSqlDbManagementOperationOptions): Promise<ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult> {
926
+ return this.stoppedAlignmentCommand('abortStoppedStorageRootAlignment', inputArg, optionsArg) as Promise<ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult>;
927
+ }
928
+ private async stoppedAlignmentCommand(method: 'alignStoppedStorageRoot' | 'abortStoppedStorageRootAlignment',
929
+ inputArg: ILocalNoSqlDbStoppedStorageRootAlignmentInput, optionsArg?: INoSqlDbManagementOperationOptions) {
930
+ const input = normalizeStoppedStorageRootAlignmentInput(inputArg);
931
+ validateNoSqlDbManagementOperationOptions(optionsArg);
932
+ try {
933
+ return await this.send(method, { ...input, timeoutMs: optionsArg?.timeoutMs }, optionsArg);
934
+ } catch (error) {
935
+ await this.awaitManagementOperationTermination(error);
936
+ throw normalizeStoppedOperationUnknownMethod(error, method);
937
+ }
938
+ }
939
+
940
+ public async inspectStoppedStorageRoot(
941
+ inputArg: ILocalNoSqlDbStoppedStorageRootInspectionInput,
942
+ optionsArg?: INoSqlDbManagementOperationOptions,
943
+ ): Promise<TLocalNoSqlDbStoppedStorageRootInspection> {
944
+ const input = normalizeStoppedStorageRootInspectionInput(inputArg);
945
+ validateNoSqlDbManagementOperationOptions(optionsArg);
946
+ try {
947
+ return await this.send('inspectStoppedStorageRoot',
948
+ { ...input, timeoutMs: optionsArg?.timeoutMs }, optionsArg) as TLocalNoSqlDbStoppedStorageRootInspection;
949
+ } catch (error) {
950
+ await this.awaitManagementOperationTermination(error);
951
+ throw normalizeStoppedOperationUnknownMethod(error, 'inspectStoppedStorageRoot');
952
+ }
953
+ }
954
+
955
+ public async inspectOfflinePhysicalNamespaces(
956
+ inputArg: ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
957
+ optionsArg?: INoSqlDbManagementOperationOptions,
958
+ ): Promise<ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult> {
959
+ const input = normalizeLocalNoSqlDbOfflinePhysicalNamespaceInspectionInput(inputArg);
960
+ validateNoSqlDbManagementOperationOptions(optionsArg);
961
+ const params: TOfflinePhysicalNamespaceInspectionCommandParams = {
962
+ folderPath: input.folderPath,
963
+ ...input.limits,
964
+ timeoutMs: optionsArg?.timeoutMs,
965
+ };
966
+ try {
967
+ return await this.send(
968
+ 'inspectOfflinePhysicalNamespaces',
969
+ params,
970
+ optionsArg,
971
+ ) as ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult;
972
+ } catch (error) {
973
+ await this.awaitManagementOperationTermination(error);
974
+ throw normalizeStoppedOperationUnknownMethod(error, 'inspectOfflinePhysicalNamespaces');
975
+ }
976
+ }
977
+
978
+ public async inspectOfflineStringValue(
979
+ inputArg: ILocalNoSqlDbOfflineStringValueInspection,
980
+ optionsArg?: INoSqlDbManagementOperationOptions,
981
+ ): Promise<TLocalNoSqlDbOfflineStringValueInspectionResult> {
982
+ const input = normalizeLocalNoSqlDbOfflineInspectionInput(inputArg);
983
+ validateNoSqlDbManagementOperationOptions(optionsArg);
984
+ try {
985
+ return await this.send(
986
+ 'inspectOfflineStringValue',
987
+ { ...input, timeoutMs: optionsArg?.timeoutMs },
988
+ optionsArg,
989
+ ) as TLocalNoSqlDbOfflineStringValueInspectionResult;
990
+ } catch (error) {
991
+ await this.awaitManagementOperationTermination(error);
992
+ throw normalizeStoppedOperationUnknownMethod(error, 'inspectOfflineStringValue');
993
+ }
994
+ }
995
+
996
+ public async startDb(
997
+ config: INoSqlDbRustConfig,
998
+ optionsArg?: INoSqlDbManagementOperationOptions,
999
+ ): Promise<{ connectionUri: string }> {
1000
+ normalizeNoSqlDbFileStorageOptions(config);
1001
+ validateNoSqlDbManagementOperationOptions(optionsArg);
1002
+ try {
1003
+ return await this.send(
1004
+ 'start',
1005
+ { config, timeoutMs: optionsArg?.timeoutMs },
1006
+ optionsArg,
1007
+ ) as { connectionUri: string };
1008
+ } catch (error) {
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
+ }
1014
+ throw error;
1015
+ }
1016
+ }
1017
+
1018
+ public async stopDb(): Promise<void> {
1019
+ await this.send('stop', {} as Record<string, never>);
1020
+ }
1021
+
1022
+ public async getStatus(): Promise<{ running: boolean }> {
1023
+ return await this.send('getStatus', {} as Record<string, never>) as { running: boolean };
1024
+ }
1025
+
1026
+ public async getMetrics(): Promise<INoSqlDbMetrics> {
1027
+ return this.send('getMetrics', {} as Record<string, never>) as Promise<INoSqlDbMetrics>;
1028
+ }
1029
+
1030
+ public async getHealth(
1031
+ optionsArg?: INoSqlDbManagementOperationOptions,
1032
+ ): Promise<INoSqlDbHealth> {
1033
+ try {
1034
+ return await this.send(
1035
+ 'getHealth',
1036
+ {} as Record<string, never>,
1037
+ optionsArg,
1038
+ ) as INoSqlDbHealth;
1039
+ } catch (error) {
1040
+ await this.awaitManagementOperationTermination(error);
1041
+ throw error;
1042
+ }
1043
+ }
1044
+
1045
+ public async createDatabaseTenant(
1046
+ params: INoSqlDbDatabaseTenantInput,
1047
+ ): Promise<INoSqlDbDatabaseTenantDescriptor> {
1048
+ try {
1049
+ return await this.send('createDatabaseTenant', params) as INoSqlDbDatabaseTenantDescriptor;
1050
+ } catch (error) {
1051
+ throw normalizeNoSqlDbResourceFenceError(error);
1052
+ }
1053
+ }
1054
+
1055
+ public async ensureDatabaseTenant(
1056
+ params: INoSqlDbEnsureDatabaseTenantInput,
1057
+ optionsArg?: INoSqlDbManagementOperationOptions,
1058
+ ): Promise<INoSqlDbEnsureDatabaseTenantResult> {
1059
+ try {
1060
+ return await this.send(
1061
+ 'ensureDatabaseTenant',
1062
+ params,
1063
+ optionsArg,
1064
+ ) as INoSqlDbEnsureDatabaseTenantResult;
1065
+ } catch (error) {
1066
+ await this.awaitManagementOperationTermination(error);
1067
+ throw normalizeNoSqlDbResourceFenceError(error);
1068
+ }
1069
+ }
1070
+
1071
+ public async attestDatabaseTenant(
1072
+ params: INoSqlDbAttestDatabaseTenantInput,
1073
+ optionsArg?: INoSqlDbManagementOperationOptions,
1074
+ ): Promise<INoSqlDbAttestDatabaseTenantResult> {
1075
+ try {
1076
+ return await this.send(
1077
+ 'attestDatabaseTenant',
1078
+ params,
1079
+ optionsArg,
1080
+ ) as INoSqlDbAttestDatabaseTenantResult;
1081
+ } catch (error) {
1082
+ await this.awaitManagementOperationTermination(error);
1083
+ throw normalizeNoSqlDbResourceFenceError(error);
1084
+ }
1085
+ }
1086
+
1087
+ public async allocateDatabaseTenant(
1088
+ params: INoSqlDbAllocateDatabaseTenantInput,
1089
+ optionsArg?: INoSqlDbManagementOperationOptions,
1090
+ ): Promise<INoSqlDbAllocateDatabaseTenantResult> {
1091
+ try {
1092
+ return await this.send(
1093
+ 'allocateDatabaseTenant',
1094
+ params,
1095
+ optionsArg,
1096
+ ) as INoSqlDbAllocateDatabaseTenantResult;
1097
+ } catch (error) {
1098
+ await this.awaitManagementOperationTermination(error);
1099
+ throw normalizeNoSqlDbResourceFenceError(error);
1100
+ }
1101
+ }
1102
+
1103
+ public async deleteDatabaseTenant(
1104
+ params: INoSqlDbDeleteDatabaseTenantInput,
1105
+ optionsArg?: INoSqlDbManagementOperationOptions,
1106
+ ): Promise<INoSqlDbDeleteDatabaseTenantResult> {
1107
+ try {
1108
+ return await this.send(
1109
+ 'deleteDatabaseTenant',
1110
+ params,
1111
+ optionsArg,
1112
+ ) as INoSqlDbDeleteDatabaseTenantResult;
1113
+ } catch (error) {
1114
+ await this.awaitManagementOperationTermination(error);
1115
+ throw normalizeNoSqlDbResourceFenceError(error);
1116
+ }
1117
+ }
1118
+
1119
+ public async issueDatabaseAllocationReadAccessGrant(
1120
+ params: INoSqlDbIssueDatabaseAllocationReadAccessGrantInput,
1121
+ optionsArg?: INoSqlDbManagementOperationOptions,
1122
+ ): Promise<INoSqlDbIssueDatabaseAllocationReadAccessGrantResult> {
1123
+ try {
1124
+ return await this.send(
1125
+ 'issueDatabaseAllocationReadAccessGrant',
1126
+ params,
1127
+ optionsArg,
1128
+ ) as INoSqlDbIssueDatabaseAllocationReadAccessGrantResult;
1129
+ } catch (error) {
1130
+ await this.awaitManagementOperationTermination(error);
1131
+ throw normalizeNoSqlDbAllocationReadAccessGrantError(
1132
+ normalizeNoSqlDbResourceFenceError(error),
1133
+ error instanceof plugins.smartrust.RustBridgeRequestError
1134
+ ? error.responseErrorCode
1135
+ : undefined,
1136
+ );
1137
+ }
1138
+ }
1139
+
1140
+ public async revokeDatabaseAllocationReadAccessGrant(
1141
+ params: INoSqlDbRevokeDatabaseAllocationReadAccessGrantInput,
1142
+ optionsArg?: INoSqlDbManagementOperationOptions,
1143
+ ): Promise<INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult> {
1144
+ try {
1145
+ return await this.send(
1146
+ 'revokeDatabaseAllocationReadAccessGrant',
1147
+ params,
1148
+ optionsArg,
1149
+ ) as INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult;
1150
+ } catch (error) {
1151
+ await this.awaitManagementOperationTermination(error);
1152
+ throw normalizeNoSqlDbAllocationReadAccessGrantError(
1153
+ normalizeNoSqlDbResourceFenceError(error),
1154
+ error instanceof plugins.smartrust.RustBridgeRequestError
1155
+ ? error.responseErrorCode
1156
+ : undefined,
1157
+ );
1158
+ }
1159
+ }
1160
+
1161
+ public async rotateDatabaseTenantPassword(
1162
+ params: INoSqlDbRotateDatabaseTenantPasswordInput,
1163
+ ): Promise<INoSqlDbDatabaseTenantDescriptor> {
1164
+ return this.send('rotateDatabaseTenantPassword', params) as Promise<INoSqlDbDatabaseTenantDescriptor>;
1165
+ }
1166
+
1167
+ public async listDatabaseTenants(): Promise<INoSqlDbDatabaseTenantDescriptor[]> {
1168
+ const result = await this.send('listDatabaseTenants', {} as Record<string, never>) as { tenants: INoSqlDbDatabaseTenantDescriptor[] };
1169
+ return result.tenants;
1170
+ }
1171
+
1172
+ public async getDatabaseTenantDescriptor(params: {
1173
+ databaseName: string;
1174
+ username: string;
1175
+ }): Promise<INoSqlDbDatabaseTenantDescriptor> {
1176
+ return this.send('getDatabaseTenantDescriptor', params) as Promise<INoSqlDbDatabaseTenantDescriptor>;
1177
+ }
1178
+
1179
+ public async getDatabaseResourceFenceState(
1180
+ params: { databaseName: string },
1181
+ optionsArg?: INoSqlDbManagementOperationOptions,
1182
+ ): Promise<INoSqlDbDatabaseResourceFenceState | null> {
1183
+ try {
1184
+ return await this.send(
1185
+ 'getDatabaseResourceFenceState',
1186
+ params,
1187
+ optionsArg,
1188
+ ) as INoSqlDbDatabaseResourceFenceState | null;
1189
+ } catch (error) {
1190
+ await this.awaitManagementOperationTermination(error);
1191
+ throw normalizeNoSqlDbResourceFenceError(error);
1192
+ }
1193
+ }
1194
+
1195
+ public async exportDatabase(
1196
+ params: INoSqlDbExportDatabaseInput,
1197
+ optionsArg?: INoSqlDbManagementOperationOptions,
1198
+ ): Promise<INoSqlDbDatabaseExport> {
1199
+ try {
1200
+ return await this.send(
1201
+ 'exportDatabase',
1202
+ params,
1203
+ optionsArg,
1204
+ ) as INoSqlDbDatabaseExport;
1205
+ } catch (error) {
1206
+ await this.awaitManagementOperationTermination(error);
1207
+ throw error;
1208
+ }
1209
+ }
1210
+
1211
+ public async exportHeldDatabase(
1212
+ params: INoSqlDbExportHeldDatabaseInput,
1213
+ optionsArg?: INoSqlDbManagementOperationOptions,
1214
+ ): Promise<INoSqlDbDatabaseExport> {
1215
+ try {
1216
+ return await this.send(
1217
+ 'exportHeldDatabase',
1218
+ params,
1219
+ optionsArg,
1220
+ ) as INoSqlDbDatabaseExport;
1221
+ } catch (error) {
1222
+ await this.awaitManagementOperationTermination(error);
1223
+ throw normalizeNoSqlDbResourceFenceError(error);
1224
+ }
1225
+ }
1226
+
1227
+ public async getDatabaseContentDigest(
1228
+ params: INoSqlDbGetDatabaseContentDigestInput,
1229
+ optionsArg?: INoSqlDbManagementOperationOptions,
1230
+ ): Promise<INoSqlDbDatabaseContentDigest> {
1231
+ try {
1232
+ return await this.send(
1233
+ 'getDatabaseContentDigest',
1234
+ params,
1235
+ optionsArg,
1236
+ ) as INoSqlDbDatabaseContentDigest;
1237
+ } catch (error) {
1238
+ await this.awaitManagementOperationTermination(error);
1239
+ throw error;
1240
+ }
1241
+ }
1242
+
1243
+ public async importDatabase(
1244
+ params: INoSqlDbImportDatabaseInput,
1245
+ optionsArg?: INoSqlDbManagementOperationOptions,
1246
+ ): Promise<INoSqlDbImportDatabaseResult> {
1247
+ try {
1248
+ return await this.send(
1249
+ 'importDatabase',
1250
+ { ...params, timeoutMs: optionsArg?.timeoutMs },
1251
+ optionsArg,
1252
+ ) as INoSqlDbImportDatabaseResult;
1253
+ } catch (error) {
1254
+ await this.awaitManagementOperationTermination(error);
1255
+ throw normalizeNoSqlDbResourceFenceError(error);
1256
+ }
1257
+ }
1258
+
1259
+ public async commitDatabasePublication(
1260
+ params: INoSqlDbCommitDatabasePublicationInput,
1261
+ optionsArg?: INoSqlDbManagementOperationOptions,
1262
+ ): Promise<TNoSqlDbCommitDatabasePublicationResult> {
1263
+ try {
1264
+ return await this.send(
1265
+ 'commitDatabasePublication',
1266
+ params,
1267
+ optionsArg,
1268
+ ) as TNoSqlDbCommitDatabasePublicationResult;
1269
+ } catch (error) {
1270
+ await this.awaitManagementOperationTermination(error);
1271
+ throw normalizeNoSqlDbResourceFenceError(error);
1272
+ }
1273
+ }
1274
+
1275
+ public async getOpLog(params: {
1276
+ sinceSeq?: number;
1277
+ limit?: number;
1278
+ db?: string;
1279
+ collection?: string;
1280
+ } = {}): Promise<IOpLogResult> {
1281
+ return this.send('getOpLog', params) as Promise<IOpLogResult>;
1282
+ }
1283
+
1284
+ public async getCommittedWrites(params: INoSqlDbGetCommittedWritesInput): Promise<INoSqlDbCommittedWritesPage> {
1285
+ return this.send('getCommittedWrites', params);
1286
+ }
1287
+
1288
+ public async getOpLogStats(): Promise<IOpLogStats> {
1289
+ return this.send('getOpLogStats', {} as Record<string, never>) as Promise<IOpLogStats>;
1290
+ }
1291
+
1292
+ public async revertToSeq(seq: number, dryRun = false): Promise<IRevertResult> {
1293
+ return this.send('revertToSeq', { seq, dryRun }) as Promise<IRevertResult>;
1294
+ }
1295
+
1296
+ public async getCollections(db?: string): Promise<ICollectionInfo[]> {
1297
+ const result = await this.send('getCollections', db ? { db } : {}) as { collections: ICollectionInfo[] };
1298
+ return result.collections;
1299
+ }
1300
+
1301
+ public async getDocuments(
1302
+ db: string,
1303
+ collection: string,
1304
+ limit = 50,
1305
+ skip = 0,
1306
+ ): Promise<IDocumentsResult> {
1307
+ return this.send('getDocuments', { db, collection, limit, skip }) as Promise<IDocumentsResult>;
1308
+ }
1309
+ }