@lossless.org/nosqldb 10.0.0 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_rust/rustdb_linux_amd64 +0 -0
- package/dist_rust/rustdb_linux_amd64.tsrust-build.json +4 -4
- package/dist_rust/rustdb_linux_amd64_musl +0 -0
- package/dist_rust/rustdb_linux_amd64_musl.tsrust-build.json +4 -4
- package/dist_rust/rustdb_linux_arm64 +0 -0
- package/dist_rust/rustdb_linux_arm64.tsrust-build.json +4 -4
- package/dist_rust/rustdb_linux_arm64_musl +0 -0
- package/dist_rust/rustdb_linux_arm64_musl.tsrust-build.json +4 -4
- package/dist_rust/rustdb_macos_amd64 +0 -0
- package/dist_rust/rustdb_macos_amd64.tsrust-build.json +4 -4
- package/dist_rust/rustdb_macos_arm64 +0 -0
- package/dist_rust/rustdb_macos_arm64.tsrust-build.json +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/ts_local/classes.localnosqldb.d.ts +15 -5
- package/dist_ts/ts_local/classes.localnosqldb.js +119 -11
- package/dist_ts/ts_nosqldb/file-storage-startup.d.ts +1 -1
- package/dist_ts/ts_nosqldb/file-storage-startup.js +4 -2
- package/dist_ts/ts_nosqldb/index.d.ts +3 -0
- package/dist_ts/ts_nosqldb/index.js +2 -1
- package/dist_ts/ts_nosqldb/rust-db-bridge.d.ts +10 -0
- package/dist_ts/ts_nosqldb/rust-db-bridge.js +37 -5
- package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.d.ts +1 -1
- package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.js +2 -1
- package/dist_ts/ts_nosqldb/service-types.d.ts +4 -0
- package/dist_ts/ts_nosqldb/service-types.js +1 -1
- package/dist_ts/ts_nosqldb/stopped-storage.d.ts +69 -0
- package/dist_ts/ts_nosqldb/stopped-storage.js +44 -0
- package/dist_ts/ts_nosqldb/storage-root-alignment.d.ts +58 -0
- package/dist_ts/ts_nosqldb/storage-root-alignment.js +129 -0
- package/dist_ts/ts_nosqldb/storage-root-relocation.js +12 -10
- package/package.json +1 -1
- package/readme.md +162 -16
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/ts_local/classes.localnosqldb.ts +123 -15
- package/ts/ts_nosqldb/file-storage-startup.ts +3 -2
- package/ts/ts_nosqldb/index.ts +13 -0
- package/ts/ts_nosqldb/rust-db-bridge.ts +52 -4
- package/ts/ts_nosqldb/server/classes.nosqldbserver.ts +2 -1
- package/ts/ts_nosqldb/service-types.ts +4 -0
- package/ts/ts_nosqldb/stopped-storage.ts +108 -0
- package/ts/ts_nosqldb/storage-root-alignment.ts +176 -0
- package/ts/ts_nosqldb/storage-root-relocation.ts +9 -7
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeStoppedStorageRootAlignmentInput, normalizeStoppedStorageRootAlignmentResult,
|
|
3
|
+
normalizeStoppedStorageRootAlignmentAbortResult,
|
|
4
|
+
type ILocalNoSqlDbStoppedStorageRootAlignmentInput, type TLocalNoSqlDbStoppedStorageRootAlignmentResult,
|
|
5
|
+
type ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult,
|
|
6
|
+
} from '../ts_nosqldb/storage-root-alignment.js';
|
|
7
|
+
import {
|
|
8
|
+
NoSqlDbStoppedOperationError,
|
|
9
|
+
normalizeStoppedStorageRootInspectionInput,
|
|
10
|
+
validateNoSqlDbStoppedOperationOptions,
|
|
11
|
+
isStoppedOperationUnknownMethod,
|
|
12
|
+
type INoSqlDbStoppedOperationOptions,
|
|
13
|
+
type ILocalNoSqlDbStoppedStorageRootInspectionInput,
|
|
14
|
+
type TLocalNoSqlDbStoppedStorageRootInspection,
|
|
15
|
+
} from '../ts_nosqldb/stopped-storage.js';
|
|
1
16
|
import * as crypto from 'node:crypto';
|
|
2
17
|
import * as fs from 'node:fs/promises';
|
|
3
18
|
import * as net from 'node:net';
|
|
@@ -17,6 +32,8 @@ import {
|
|
|
17
32
|
import {
|
|
18
33
|
RustDbBridge,
|
|
19
34
|
rustDbBridgeStorageRootRelocation,
|
|
35
|
+
rustDbBridgeStorageRootAlignment,
|
|
36
|
+
rustDbBridgeStorageRootAlignmentAbort,
|
|
20
37
|
} from '../ts_nosqldb/rust-db-bridge.js';
|
|
21
38
|
import { validateNoSqlDbStorageRehearsalStartup } from '../ts_nosqldb/storage-rehearsal.js';
|
|
22
39
|
import {
|
|
@@ -55,7 +72,7 @@ export interface ILocalNoSqlDbOptions {
|
|
|
55
72
|
storageRehearsalId?: string;
|
|
56
73
|
/** Exact engine executable. Invalid explicit paths reject without chmod or discovery fallback. Omit for normal discovery. */
|
|
57
74
|
binaryPath?: string;
|
|
58
|
-
/** Default: current-format-only.
|
|
75
|
+
/** Default: current-format-only. existing-only refuses missing, empty or uninitialized storage. */
|
|
59
76
|
fileStorageStartup?: TNoSqlDbFileStorageStartup;
|
|
60
77
|
/** Required: where to store data */
|
|
61
78
|
folderPath: string;
|
|
@@ -106,12 +123,12 @@ export class LocalNoSqlDb {
|
|
|
106
123
|
};
|
|
107
124
|
}
|
|
108
125
|
|
|
109
|
-
private static createOfflineInspectionBridge(): RustDbBridge {
|
|
110
|
-
return new RustDbBridge();
|
|
126
|
+
private static createOfflineInspectionBridge(binaryPath?: string): RustDbBridge {
|
|
127
|
+
return new RustDbBridge({ binaryPath });
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
private static createStorageRootRelocationBridge(): RustDbBridge {
|
|
114
|
-
return new RustDbBridge();
|
|
130
|
+
private static createStorageRootRelocationBridge(binaryPath?: string): RustDbBridge {
|
|
131
|
+
return new RustDbBridge({ binaryPath });
|
|
115
132
|
}
|
|
116
133
|
|
|
117
134
|
private static async withStorageRootRelocationGate<TResult>(
|
|
@@ -148,12 +165,12 @@ export class LocalNoSqlDb {
|
|
|
148
165
|
*/
|
|
149
166
|
public static async relocateStoppedStorageRoot(
|
|
150
167
|
inputArg: ILocalNoSqlDbStoppedStorageRootRelocationInput,
|
|
151
|
-
optionsArg?:
|
|
168
|
+
optionsArg?: INoSqlDbStoppedOperationOptions,
|
|
152
169
|
): Promise<ILocalNoSqlDbStoppedStorageRootRelocationReceipt> {
|
|
153
170
|
let input: ILocalNoSqlDbStoppedStorageRootRelocationInput;
|
|
154
171
|
try {
|
|
155
172
|
input = normalizeLocalNoSqlDbStoppedStorageRootRelocationInput(inputArg);
|
|
156
|
-
|
|
173
|
+
validateNoSqlDbStoppedOperationOptions(optionsArg);
|
|
157
174
|
} catch (error) {
|
|
158
175
|
if (error instanceof LocalNoSqlDbStorageRootRelocationError) throw error;
|
|
159
176
|
throw new LocalNoSqlDbStorageRootRelocationError('INVALID_REQUEST');
|
|
@@ -188,7 +205,7 @@ export class LocalNoSqlDb {
|
|
|
188
205
|
let receipt: ILocalNoSqlDbStoppedStorageRootRelocationReceipt | undefined;
|
|
189
206
|
let operationError: LocalNoSqlDbStorageRootRelocationError | undefined;
|
|
190
207
|
try {
|
|
191
|
-
bridge = LocalNoSqlDb.createStorageRootRelocationBridge();
|
|
208
|
+
bridge = LocalNoSqlDb.createStorageRootRelocationBridge(optionsArg?.binaryPath);
|
|
192
209
|
LocalNoSqlDb.storageRootRelocationBridge = bridge;
|
|
193
210
|
const spawnTimeout = LocalNoSqlDb.remainingManagementTimeout(deadline);
|
|
194
211
|
const spawned = await bridge.spawn({
|
|
@@ -205,7 +222,9 @@ export class LocalNoSqlDb {
|
|
|
205
222
|
});
|
|
206
223
|
receipt = normalizeLocalNoSqlDbStoppedStorageRootRelocationReceipt(result, input);
|
|
207
224
|
} catch (error) {
|
|
208
|
-
if (error instanceof
|
|
225
|
+
if (error instanceof NoSqlDbStoppedOperationError || isStoppedOperationUnknownMethod(error, 'relocateStoppedStorageRoot')) {
|
|
226
|
+
operationError = new LocalNoSqlDbStorageRootRelocationError(error instanceof NoSqlDbStoppedOperationError ? error.code : 'UNSUPPORTED');
|
|
227
|
+
} else if (error instanceof LocalNoSqlDbStorageRootRelocationError) {
|
|
209
228
|
operationError = error;
|
|
210
229
|
} else {
|
|
211
230
|
const requestWasDispatched = error instanceof Error
|
|
@@ -247,21 +266,110 @@ export class LocalNoSqlDb {
|
|
|
247
266
|
});
|
|
248
267
|
}
|
|
249
268
|
|
|
269
|
+
private static createStorageRootAlignmentBridge(binaryPath?: string): RustDbBridge {
|
|
270
|
+
return new RustDbBridge({ binaryPath });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Adopt an authority-free stopped copy as an independent provider. Retain its external receipt. */
|
|
274
|
+
public static alignStoppedStorageRoot(input: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
275
|
+
options?: INoSqlDbStoppedOperationOptions): Promise<TLocalNoSqlDbStoppedStorageRootAlignmentResult> {
|
|
276
|
+
return LocalNoSqlDb.runStorageRootAlignment(input, options, false, normalizeStoppedStorageRootAlignmentResult);
|
|
277
|
+
}
|
|
278
|
+
/** Abort only the exact incomplete profile append; a complete append must be finished. */
|
|
279
|
+
public static abortStoppedStorageRootAlignment(input: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
280
|
+
options?: INoSqlDbStoppedOperationOptions): Promise<ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult> {
|
|
281
|
+
return LocalNoSqlDb.runStorageRootAlignment(input, options, true, normalizeStoppedStorageRootAlignmentAbortResult);
|
|
282
|
+
}
|
|
283
|
+
private static async runStorageRootAlignment<TResult>(inputArg: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
284
|
+
options: INoSqlDbStoppedOperationOptions | undefined, abort: boolean,
|
|
285
|
+
normalize: (value: unknown, input: ILocalNoSqlDbStoppedStorageRootAlignmentInput) => TResult): Promise<TResult> {
|
|
286
|
+
let input: ILocalNoSqlDbStoppedStorageRootAlignmentInput;
|
|
287
|
+
try {
|
|
288
|
+
input = normalizeStoppedStorageRootAlignmentInput(inputArg);
|
|
289
|
+
validateNoSqlDbStoppedOperationOptions(options);
|
|
290
|
+
if (abort && input.dryRun) throw new Error('abort cannot be a dry run');
|
|
291
|
+
} catch { throw new NoSqlDbStoppedOperationError('INVALID_REQUEST'); }
|
|
292
|
+
if (options?.signal?.aborted) throw new NoSqlDbStoppedOperationError('CANCELLED');
|
|
293
|
+
if (!['linux', 'darwin'].includes(process.platform)) throw new NoSqlDbStoppedOperationError('UNSUPPORTED');
|
|
294
|
+
const deadline = Date.now() + (options?.timeoutMs ?? 3_600_000);
|
|
295
|
+
const remaining = () => {
|
|
296
|
+
if (options?.signal?.aborted) throw new NoSqlDbStoppedOperationError('CANCELLED');
|
|
297
|
+
const timeoutMs = deadline - Date.now();
|
|
298
|
+
if (timeoutMs <= 0) throw new NoSqlDbStoppedOperationError('CANCELLED');
|
|
299
|
+
return { signal: options?.signal, timeoutMs };
|
|
300
|
+
};
|
|
301
|
+
// Share the mutation gate and retained ownership with relocation, including
|
|
302
|
+
// retries after an earlier sidecar could not confirm termination.
|
|
303
|
+
return LocalNoSqlDb.withStorageRootRelocationGate(async () => {
|
|
304
|
+
const retained = LocalNoSqlDb.storageRootRelocationBridge;
|
|
305
|
+
if (retained) {
|
|
306
|
+
try { await retained.terminate({ gracePeriodMs: 0 }); }
|
|
307
|
+
catch { throw new NoSqlDbStoppedOperationError('RECOVERY_REQUIRED'); }
|
|
308
|
+
if (!retained.ownershipReleased) throw new NoSqlDbStoppedOperationError('RECOVERY_REQUIRED');
|
|
309
|
+
LocalNoSqlDb.storageRootRelocationBridge = undefined;
|
|
310
|
+
}
|
|
311
|
+
let bridge: RustDbBridge | undefined;
|
|
312
|
+
let result: TResult | undefined;
|
|
313
|
+
let failure: NoSqlDbStoppedOperationError | undefined;
|
|
314
|
+
let dispatched = false;
|
|
315
|
+
const method = abort ? 'abortStoppedStorageRootAlignment' : 'alignStoppedStorageRoot';
|
|
316
|
+
try {
|
|
317
|
+
remaining();
|
|
318
|
+
bridge = LocalNoSqlDb.createStorageRootAlignmentBridge(options?.binaryPath);
|
|
319
|
+
LocalNoSqlDb.storageRootRelocationBridge = bridge;
|
|
320
|
+
if (!await bridge.spawn(remaining())) throw new NoSqlDbStoppedOperationError('IO');
|
|
321
|
+
const commandOptions = remaining();
|
|
322
|
+
dispatched = true;
|
|
323
|
+
const value = abort ? await bridge[rustDbBridgeStorageRootAlignmentAbort](input, commandOptions)
|
|
324
|
+
: await bridge[rustDbBridgeStorageRootAlignment](input, commandOptions);
|
|
325
|
+
result = normalize(value, input);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (error instanceof NoSqlDbStoppedOperationError) failure = error;
|
|
328
|
+
else if (isStoppedOperationUnknownMethod(error, method)) failure = new NoSqlDbStoppedOperationError('UNSUPPORTED');
|
|
329
|
+
else if (error instanceof Error && 'code' in error && error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
|
|
330
|
+
&& 'method' in error && error.method === method && 'responseErrorCode' in error
|
|
331
|
+
&& isLocalNoSqlDbStorageRootRelocationErrorCode(error.responseErrorCode)) {
|
|
332
|
+
failure = new NoSqlDbStoppedOperationError(error.responseErrorCode);
|
|
333
|
+
} else failure = new NoSqlDbStoppedOperationError(dispatched ? 'RECOVERY_REQUIRED' : 'IO');
|
|
334
|
+
}
|
|
335
|
+
if (bridge) {
|
|
336
|
+
try {
|
|
337
|
+
await bridge.terminate({ gracePeriodMs: 0 });
|
|
338
|
+
if (!bridge.ownershipReleased) throw new Error('termination unconfirmed');
|
|
339
|
+
if (LocalNoSqlDb.storageRootRelocationBridge === bridge) LocalNoSqlDb.storageRootRelocationBridge = undefined;
|
|
340
|
+
} catch { failure = new NoSqlDbStoppedOperationError('RECOVERY_REQUIRED'); }
|
|
341
|
+
}
|
|
342
|
+
if (failure) throw failure;
|
|
343
|
+
if (result === undefined) throw new NoSqlDbStoppedOperationError('RECOVERY_REQUIRED');
|
|
344
|
+
return result;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Read-only complete owner audit of a stopped source or physically admissible copy. */
|
|
349
|
+
public static async inspectStoppedStorageRoot(
|
|
350
|
+
inputArg: ILocalNoSqlDbStoppedStorageRootInspectionInput,
|
|
351
|
+
optionsArg?: INoSqlDbStoppedOperationOptions,
|
|
352
|
+
): Promise<TLocalNoSqlDbStoppedStorageRootInspection> {
|
|
353
|
+
return LocalNoSqlDb.runOfflineInspection(inputArg, optionsArg,
|
|
354
|
+
normalizeStoppedStorageRootInspectionInput,
|
|
355
|
+
(bridge, input, options) => bridge.inspectStoppedStorageRoot(input, options));
|
|
356
|
+
}
|
|
357
|
+
|
|
250
358
|
private static async runOfflineInspection<
|
|
251
359
|
TInput extends { folderPath: string },
|
|
252
360
|
TResult,
|
|
253
361
|
>(
|
|
254
362
|
inputArg: TInput,
|
|
255
|
-
optionsArg:
|
|
363
|
+
optionsArg: INoSqlDbStoppedOperationOptions | undefined,
|
|
256
364
|
normalizeInputArg: (inputArg: TInput) => TInput,
|
|
257
365
|
inspectArg: (
|
|
258
366
|
bridgeArg: RustDbBridge,
|
|
259
367
|
inputArg: TInput,
|
|
260
|
-
optionsArg?:
|
|
368
|
+
optionsArg?: INoSqlDbStoppedOperationOptions,
|
|
261
369
|
) => Promise<TResult>,
|
|
262
370
|
): Promise<TResult> {
|
|
263
371
|
const validatedInput = normalizeInputArg(inputArg);
|
|
264
|
-
|
|
372
|
+
validateNoSqlDbStoppedOperationOptions(optionsArg);
|
|
265
373
|
optionsArg?.signal?.throwIfAborted();
|
|
266
374
|
|
|
267
375
|
const input = normalizeInputArg({
|
|
@@ -271,7 +379,7 @@ export class LocalNoSqlDb {
|
|
|
271
379
|
const deadline = optionsArg?.timeoutMs === undefined
|
|
272
380
|
? undefined
|
|
273
381
|
: Date.now() + optionsArg.timeoutMs;
|
|
274
|
-
const bridge = LocalNoSqlDb.createOfflineInspectionBridge();
|
|
382
|
+
const bridge = LocalNoSqlDb.createOfflineInspectionBridge(optionsArg?.binaryPath);
|
|
275
383
|
let operationFailed = false;
|
|
276
384
|
let operationError: unknown;
|
|
277
385
|
let result: TResult | undefined;
|
|
@@ -334,7 +442,7 @@ export class LocalNoSqlDb {
|
|
|
334
442
|
*/
|
|
335
443
|
public static async inspectOfflinePhysicalNamespaces(
|
|
336
444
|
inputArg: ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
|
|
337
|
-
optionsArg?:
|
|
445
|
+
optionsArg?: INoSqlDbStoppedOperationOptions,
|
|
338
446
|
): Promise<ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult> {
|
|
339
447
|
return LocalNoSqlDb.runOfflineInspection(
|
|
340
448
|
inputArg,
|
|
@@ -351,7 +459,7 @@ export class LocalNoSqlDb {
|
|
|
351
459
|
*/
|
|
352
460
|
public static async inspectOfflineStringValue(
|
|
353
461
|
inputArg: ILocalNoSqlDbOfflineStringValueInspection,
|
|
354
|
-
optionsArg?:
|
|
462
|
+
optionsArg?: INoSqlDbStoppedOperationOptions,
|
|
355
463
|
): Promise<TLocalNoSqlDbOfflineStringValueInspectionResult> {
|
|
356
464
|
return LocalNoSqlDb.runOfflineInspection(
|
|
357
465
|
inputArg,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** Native format admission is read-only until the engine owns the file root. */
|
|
2
|
-
export type TNoSqlDbFileStorageStartup = 'current-format-only';
|
|
2
|
+
export type TNoSqlDbFileStorageStartup = 'current-format-only' | 'existing-only';
|
|
3
3
|
|
|
4
4
|
export const normalizeNoSqlDbFileStorageStartup = (
|
|
5
5
|
valueArg: unknown,
|
|
6
6
|
): TNoSqlDbFileStorageStartup => {
|
|
7
7
|
if (valueArg === undefined || valueArg === 'current-format-only') return 'current-format-only';
|
|
8
|
-
|
|
8
|
+
if (valueArg === 'existing-only') return 'existing-only';
|
|
9
|
+
throw new TypeError('NoSQLDB fileStorageStartup must be "current-format-only" or "existing-only"; automatic storage conversion has been removed');
|
|
9
10
|
};
|
package/ts/ts_nosqldb/index.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
// NoSQLDB - Wire protocol compatible embedded database server
|
|
2
|
+
export { NoSqlDbStoppedOperationError } from './stopped-storage.js';
|
|
3
|
+
export type {
|
|
4
|
+
INoSqlDbStoppedOperationOptions, ILocalNoSqlDbStoppedStorageRootInspectionInput,
|
|
5
|
+
TLocalNoSqlDbStoppedStorageRootInspection, ILocalNoSqlDbAuditedStorageRoot,
|
|
6
|
+
ILocalNoSqlDbUnavailableStorageRoot, INoSqlDbPhysicalAdmissionViolation,
|
|
7
|
+
TNoSqlDbStorageRootBindingChange,
|
|
8
|
+
} from './stopped-storage.js';
|
|
2
9
|
export { NoSqlDbStorageRehearsal } from './classes.storagerehearsal.js';
|
|
3
10
|
export { NoSqlDbStorageRehearsalError, noSqlDbStorageRehearsalErrorCodes } from './storage-rehearsal.js';
|
|
4
11
|
export type {
|
|
@@ -102,3 +109,9 @@ export {
|
|
|
102
109
|
} from './storage-root-relocation.js';
|
|
103
110
|
export type { TNoSqlDbEffectivePayloadInputV1 } from './resource-fencing.js';
|
|
104
111
|
export { createNoSqlDbEffectivePayloadSha256V1 } from './resource-fencing.js';
|
|
112
|
+
|
|
113
|
+
export type {
|
|
114
|
+
INoSqlDbStorageRootPins, ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
115
|
+
ILocalNoSqlDbStoppedStorageRootAlignmentReceipt, TLocalNoSqlDbStoppedStorageRootAlignmentResult,
|
|
116
|
+
ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult,
|
|
117
|
+
} from './storage-root-alignment.js';
|
|
@@ -1,5 +1,8 @@
|
|
|
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';
|
|
1
3
|
import * as plugins from './plugins.js';
|
|
2
4
|
import { normalizeNoSqlDbFileStorageOptions } from './file-storage-options.js';
|
|
5
|
+
import type { TNoSqlDbFileStorageStartup } from './file-storage-startup.js';
|
|
3
6
|
import * as path from 'node:path';
|
|
4
7
|
import * as url from 'node:url';
|
|
5
8
|
import { EventEmitter } from 'node:events';
|
|
@@ -192,7 +195,16 @@ type TOfflinePhysicalNamespaceInspectionCommandParams = {
|
|
|
192
195
|
timeoutMs?: number;
|
|
193
196
|
} & ILocalNoSqlDbOfflinePhysicalNamespaceInspection['limits'];
|
|
194
197
|
|
|
198
|
+
export const rustDbBridgeStorageRootAlignment = Symbol('storageRootAlignment');
|
|
199
|
+
export const rustDbBridgeStorageRootAlignmentAbort = Symbol('storageRootAlignmentAbort');
|
|
200
|
+
|
|
195
201
|
type TNoSqlDbCommands = {
|
|
202
|
+
alignStoppedStorageRoot: { params: ILocalNoSqlDbStoppedStorageRootAlignmentInput & { timeoutMs?: number }; result: TLocalNoSqlDbStoppedStorageRootAlignmentResult };
|
|
203
|
+
abortStoppedStorageRootAlignment: { params: ILocalNoSqlDbStoppedStorageRootAlignmentInput & { timeoutMs?: number }; result: ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult };
|
|
204
|
+
inspectStoppedStorageRoot: {
|
|
205
|
+
params: ILocalNoSqlDbStoppedStorageRootInspectionInput & { timeoutMs?: number };
|
|
206
|
+
result: TLocalNoSqlDbStoppedStorageRootInspection;
|
|
207
|
+
};
|
|
196
208
|
createStoppedStorageRehearsal: {
|
|
197
209
|
params: { request: TNormalizedNoSqlDbStorageRehearsalInput; timeoutMs: number };
|
|
198
210
|
result: INoSqlDbStorageRehearsalReceipt;
|
|
@@ -311,6 +323,7 @@ export interface INoSqlDbQueryOptions {
|
|
|
311
323
|
}
|
|
312
324
|
|
|
313
325
|
interface INoSqlDbRustConfig {
|
|
326
|
+
fileStorageStartup?: TNoSqlDbFileStorageStartup;
|
|
314
327
|
storageRehearsalId?: string;
|
|
315
328
|
port?: number;
|
|
316
329
|
host?: string;
|
|
@@ -610,7 +623,7 @@ export class RustDbBridge extends EventEmitter {
|
|
|
610
623
|
{ request, timeoutMs: optionsArg.timeoutMs }, optionsArg) as INoSqlDbStorageRehearsalReceipt;
|
|
611
624
|
} catch (error) {
|
|
612
625
|
await this.awaitManagementOperationTermination(error);
|
|
613
|
-
throw error;
|
|
626
|
+
throw normalizeStoppedOperationUnknownMethod(error, 'createStoppedStorageRehearsal');
|
|
614
627
|
}
|
|
615
628
|
}
|
|
616
629
|
|
|
@@ -628,7 +641,42 @@ export class RustDbBridge extends EventEmitter {
|
|
|
628
641
|
) as ILocalNoSqlDbStoppedStorageRootRelocationReceipt;
|
|
629
642
|
} catch (error) {
|
|
630
643
|
await this.awaitManagementOperationTermination(error);
|
|
631
|
-
throw error;
|
|
644
|
+
throw normalizeStoppedOperationUnknownMethod(error, 'relocateStoppedStorageRoot');
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
public async [rustDbBridgeStorageRootAlignment](inputArg: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
649
|
+
optionsArg?: INoSqlDbManagementOperationOptions): Promise<TLocalNoSqlDbStoppedStorageRootAlignmentResult> {
|
|
650
|
+
return this.stoppedAlignmentCommand('alignStoppedStorageRoot', inputArg, optionsArg) as Promise<TLocalNoSqlDbStoppedStorageRootAlignmentResult>;
|
|
651
|
+
}
|
|
652
|
+
public async [rustDbBridgeStorageRootAlignmentAbort](inputArg: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
653
|
+
optionsArg?: INoSqlDbManagementOperationOptions): Promise<ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult> {
|
|
654
|
+
return this.stoppedAlignmentCommand('abortStoppedStorageRootAlignment', inputArg, optionsArg) as Promise<ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult>;
|
|
655
|
+
}
|
|
656
|
+
private async stoppedAlignmentCommand(method: 'alignStoppedStorageRoot' | 'abortStoppedStorageRootAlignment',
|
|
657
|
+
inputArg: ILocalNoSqlDbStoppedStorageRootAlignmentInput, optionsArg?: INoSqlDbManagementOperationOptions) {
|
|
658
|
+
const input = normalizeStoppedStorageRootAlignmentInput(inputArg);
|
|
659
|
+
validateNoSqlDbManagementOperationOptions(optionsArg);
|
|
660
|
+
try {
|
|
661
|
+
return await this.bridge.sendCommand(method, { ...input, timeoutMs: optionsArg?.timeoutMs }, optionsArg);
|
|
662
|
+
} catch (error) {
|
|
663
|
+
await this.awaitManagementOperationTermination(error);
|
|
664
|
+
throw normalizeStoppedOperationUnknownMethod(error, method);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
public async inspectStoppedStorageRoot(
|
|
669
|
+
inputArg: ILocalNoSqlDbStoppedStorageRootInspectionInput,
|
|
670
|
+
optionsArg?: INoSqlDbManagementOperationOptions,
|
|
671
|
+
): Promise<TLocalNoSqlDbStoppedStorageRootInspection> {
|
|
672
|
+
const input = normalizeStoppedStorageRootInspectionInput(inputArg);
|
|
673
|
+
validateNoSqlDbManagementOperationOptions(optionsArg);
|
|
674
|
+
try {
|
|
675
|
+
return await this.bridge.sendCommand('inspectStoppedStorageRoot',
|
|
676
|
+
{ ...input, timeoutMs: optionsArg?.timeoutMs }, optionsArg) as TLocalNoSqlDbStoppedStorageRootInspection;
|
|
677
|
+
} catch (error) {
|
|
678
|
+
await this.awaitManagementOperationTermination(error);
|
|
679
|
+
throw normalizeStoppedOperationUnknownMethod(error, 'inspectStoppedStorageRoot');
|
|
632
680
|
}
|
|
633
681
|
}
|
|
634
682
|
|
|
@@ -651,7 +699,7 @@ export class RustDbBridge extends EventEmitter {
|
|
|
651
699
|
) as ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult;
|
|
652
700
|
} catch (error) {
|
|
653
701
|
await this.awaitManagementOperationTermination(error);
|
|
654
|
-
throw error;
|
|
702
|
+
throw normalizeStoppedOperationUnknownMethod(error, 'inspectOfflinePhysicalNamespaces');
|
|
655
703
|
}
|
|
656
704
|
}
|
|
657
705
|
|
|
@@ -669,7 +717,7 @@ export class RustDbBridge extends EventEmitter {
|
|
|
669
717
|
) as TLocalNoSqlDbOfflineStringValueInspectionResult;
|
|
670
718
|
} catch (error) {
|
|
671
719
|
await this.awaitManagementOperationTermination(error);
|
|
672
|
-
throw error;
|
|
720
|
+
throw normalizeStoppedOperationUnknownMethod(error, 'inspectOfflineStringValue');
|
|
673
721
|
}
|
|
674
722
|
}
|
|
675
723
|
|
|
@@ -58,7 +58,7 @@ export interface INoSqlDbServerOptions {
|
|
|
58
58
|
binaryPath?: string;
|
|
59
59
|
/** Exact rehearsal operation ID. Requires authenticated current-format-only file storage and an explicit private Unix socket. Never promotes a rehearsal. */
|
|
60
60
|
storageRehearsalId?: string;
|
|
61
|
-
/** Default: current-format-only.
|
|
61
|
+
/** Default: current-format-only. existing-only also requires initialized storage and refuses fresh creation. */
|
|
62
62
|
fileStorageStartup?: TNoSqlDbFileStorageStartup;
|
|
63
63
|
/** Port to listen on (default: 27017, 0 requests an OS-assigned port) - ignored if socketPath is set */
|
|
64
64
|
port?: number;
|
|
@@ -261,6 +261,7 @@ export class NoSqlDbServer {
|
|
|
261
261
|
socketPath: this.options.socketPath,
|
|
262
262
|
storage: this.options.storage ?? 'memory',
|
|
263
263
|
storagePath: this.options.storagePath,
|
|
264
|
+
...(this.options.fileStorageStartup === 'existing-only' ? { fileStorageStartup: 'existing-only' as const } : {}),
|
|
264
265
|
storageRehearsalId: this.options.storageRehearsalId,
|
|
265
266
|
auth: this.options.auth,
|
|
266
267
|
tls: this.options.tls,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export interface INoSqlDbHealth {
|
|
2
|
+
/** Present on engines supporting stopped root alignment. */
|
|
3
|
+
providerRootId?: string;
|
|
4
|
+
lsn?: string | null;
|
|
2
5
|
running: boolean;
|
|
3
6
|
storage?: 'memory' | 'file';
|
|
4
7
|
fileStorageEngine?: 'mvcc';
|
|
@@ -271,6 +274,7 @@ export interface INoSqlDbManagementOperationOptions {
|
|
|
271
274
|
}
|
|
272
275
|
|
|
273
276
|
export interface INoSqlDbDatabaseResourceFenceState {
|
|
277
|
+
providerRootId?: string;
|
|
274
278
|
version: 1;
|
|
275
279
|
databaseName: string;
|
|
276
280
|
resourceIdSha256: string;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { validateNoSqlDbManagementOperationOptions } from './offline-inspection.js';
|
|
3
|
+
import type { INoSqlDbManagementOperationOptions } from './service-types.js';
|
|
4
|
+
import type { TLocalNoSqlDbStorageRootRelocationErrorCode } from './storage-root-relocation.js';
|
|
5
|
+
|
|
6
|
+
export interface INoSqlDbStoppedOperationOptions extends INoSqlDbManagementOperationOptions {
|
|
7
|
+
/** Exact executable; invalid explicit paths never fall back to discovery. */
|
|
8
|
+
binaryPath?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ILocalNoSqlDbStoppedStorageRootInspectionInput { folderPath: string; }
|
|
12
|
+
export type TNoSqlDbStorageRootBindingChange = 'none' | 'path' | 'inode' | 'path+inode';
|
|
13
|
+
export interface INoSqlDbPhysicalAdmissionViolation {
|
|
14
|
+
relativePath: string;
|
|
15
|
+
rule: string;
|
|
16
|
+
observed: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ILocalNoSqlDbAuditedStorageRoot {
|
|
19
|
+
status: 'audited';
|
|
20
|
+
bindingState: 'matching' | 'foreign';
|
|
21
|
+
bindingChange: TNoSqlDbStorageRootBindingChange;
|
|
22
|
+
storagePurpose: 'production';
|
|
23
|
+
profileSchema: 1 | 4;
|
|
24
|
+
providerRootId: string;
|
|
25
|
+
/** Group/timeline describe shared lineage, not independent provider identity. */
|
|
26
|
+
groupId: string;
|
|
27
|
+
timelineId: string;
|
|
28
|
+
/** All counts and positions are canonical unsigned decimal strings. */
|
|
29
|
+
lsn: string;
|
|
30
|
+
records: string;
|
|
31
|
+
recordsSha256: string;
|
|
32
|
+
recordsWithoutFencingProfileSha256: string;
|
|
33
|
+
authUsers: string;
|
|
34
|
+
authority: {
|
|
35
|
+
resourceFences: string;
|
|
36
|
+
receipts: string;
|
|
37
|
+
publicationHolds: string;
|
|
38
|
+
activeAllocations: string;
|
|
39
|
+
retiredAllocations: string;
|
|
40
|
+
allocationReplays: string;
|
|
41
|
+
activeGrants: '0';
|
|
42
|
+
importStages: '0';
|
|
43
|
+
};
|
|
44
|
+
generations: { manifest: string; onDisk: string[] };
|
|
45
|
+
alignmentEligible: boolean;
|
|
46
|
+
reasons: string[];
|
|
47
|
+
physicalAdmissionViolations: [];
|
|
48
|
+
}
|
|
49
|
+
export interface ILocalNoSqlDbUnavailableStorageRoot {
|
|
50
|
+
status: 'unavailable';
|
|
51
|
+
stage: 'admission' | 'audit';
|
|
52
|
+
alignmentEligible: false;
|
|
53
|
+
reasons: string[];
|
|
54
|
+
/** Bounded advisory evidence. No logical identity or pin is trusted here. */
|
|
55
|
+
physicalAdmissionViolations: INoSqlDbPhysicalAdmissionViolation[];
|
|
56
|
+
}
|
|
57
|
+
export type TLocalNoSqlDbStoppedStorageRootInspection =
|
|
58
|
+
ILocalNoSqlDbAuditedStorageRoot | ILocalNoSqlDbUnavailableStorageRoot;
|
|
59
|
+
|
|
60
|
+
export class NoSqlDbStoppedOperationError extends Error {
|
|
61
|
+
public readonly responseErrorCode: TLocalNoSqlDbStorageRootRelocationErrorCode;
|
|
62
|
+
public constructor(public readonly code: TLocalNoSqlDbStorageRootRelocationErrorCode) {
|
|
63
|
+
super(`NoSQLDB stopped operation failed with code ${code}`);
|
|
64
|
+
this.name = 'NoSqlDbStoppedOperationError';
|
|
65
|
+
this.responseErrorCode = code;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const validateNoSqlDbStoppedOperationOptions = (options?: INoSqlDbStoppedOperationOptions): void => {
|
|
70
|
+
validateNoSqlDbManagementOperationOptions(options);
|
|
71
|
+
if (options?.binaryPath !== undefined && (typeof options.binaryPath !== 'string'
|
|
72
|
+
|| !options.binaryPath.length || options.binaryPath.includes('\0'))) {
|
|
73
|
+
throw new NoSqlDbStoppedOperationError('INVALID_REQUEST');
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const normalizeStoppedStorageRootInspectionInput = (
|
|
78
|
+
input: ILocalNoSqlDbStoppedStorageRootInspectionInput,
|
|
79
|
+
): ILocalNoSqlDbStoppedStorageRootInspectionInput => {
|
|
80
|
+
if (!input || Object.getPrototypeOf(input) !== Object.prototype
|
|
81
|
+
|| Reflect.ownKeys(input).length !== 1
|
|
82
|
+
|| !Object.getOwnPropertyDescriptor(input, 'folderPath')?.enumerable
|
|
83
|
+
|| !('value' in Object.getOwnPropertyDescriptor(input, 'folderPath')!)) {
|
|
84
|
+
throw new NoSqlDbStoppedOperationError('INVALID_REQUEST');
|
|
85
|
+
}
|
|
86
|
+
const folderPath = input.folderPath;
|
|
87
|
+
if (typeof folderPath !== 'string' || plugins.buffer.Buffer.byteLength(folderPath) > 4095
|
|
88
|
+
|| !plugins.path.isAbsolute(folderPath) || plugins.path.dirname(folderPath) === folderPath
|
|
89
|
+
|| folderPath.endsWith('/') || folderPath.includes('//') || /\p{Cc}/u.test(folderPath)
|
|
90
|
+
|| folderPath.split('/').some(part => part === '.' || part === '..' || part.startsWith('.smartdb-rehearsal-'))) {
|
|
91
|
+
throw new NoSqlDbStoppedOperationError('INVALID_REQUEST');
|
|
92
|
+
}
|
|
93
|
+
return { folderPath };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** Only a confirmed Rust response for this exact command proves non-dispatch.
|
|
97
|
+
* Transport errors with the same text remain ambiguous. */
|
|
98
|
+
export const isStoppedOperationUnknownMethod = (error: unknown, method: string): boolean =>
|
|
99
|
+
error instanceof Error && 'code' in error
|
|
100
|
+
&& error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
|
|
101
|
+
&& 'requestId' in error && typeof error.requestId === 'string' && error.requestId.length > 0
|
|
102
|
+
&& 'method' in error && error.method === method
|
|
103
|
+
&& (('responseErrorCode' in error && error.responseErrorCode === 'UNKNOWN_METHOD')
|
|
104
|
+
|| ((!('responseErrorCode' in error) || error.responseErrorCode === undefined)
|
|
105
|
+
&& error.message === `Unknown method: ${method}`));
|
|
106
|
+
|
|
107
|
+
export const normalizeStoppedOperationUnknownMethod = (error: unknown, method: string): unknown =>
|
|
108
|
+
isStoppedOperationUnknownMethod(error, method) ? new NoSqlDbStoppedOperationError('UNSUPPORTED') : error;
|