@lossless.org/nosqldb 8.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.
- package/.smartconfig.json +107 -0
- package/dist_rust/rustdb_linux_amd64 +0 -0
- package/dist_rust/rustdb_linux_amd64.tsrust-build.json +14 -0
- package/dist_rust/rustdb_linux_arm64 +0 -0
- package/dist_rust/rustdb_linux_arm64.tsrust-build.json +14 -0
- package/dist_rust/rustdb_macos_amd64 +0 -0
- package/dist_rust/rustdb_macos_amd64.tsrust-build.json +14 -0
- package/dist_rust/rustdb_macos_arm64 +0 -0
- package/dist_rust/rustdb_macos_arm64.tsrust-build.json +14 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/index.d.ts +8 -0
- package/dist_ts/index.js +10 -0
- package/dist_ts/ts_local/classes.localnosqldb.d.ts +119 -0
- package/dist_ts/ts_local/classes.localnosqldb.js +386 -0
- package/dist_ts/ts_local/index.d.ts +5 -0
- package/dist_ts/ts_local/index.js +3 -0
- package/dist_ts/ts_local/plugins.d.ts +1 -0
- package/dist_ts/ts_local/plugins.js +3 -0
- package/dist_ts/ts_nosqldb/classes.storagerehearsal.d.ts +19 -0
- package/dist_ts/ts_nosqldb/classes.storagerehearsal.js +122 -0
- package/dist_ts/ts_nosqldb/file-storage-options.d.ts +13 -0
- package/dist_ts/ts_nosqldb/file-storage-options.js +24 -0
- package/dist_ts/ts_nosqldb/file-storage-startup.d.ts +3 -0
- package/dist_ts/ts_nosqldb/file-storage-startup.js +6 -0
- package/dist_ts/ts_nosqldb/index.d.ts +14 -0
- package/dist_ts/ts_nosqldb/index.js +11 -0
- package/dist_ts/ts_nosqldb/offline-inspection.d.ts +72 -0
- package/dist_ts/ts_nosqldb/offline-inspection.js +282 -0
- package/dist_ts/ts_nosqldb/plugins.d.ts +7 -0
- package/dist_ts/ts_nosqldb/plugins.js +10 -0
- package/dist_ts/ts_nosqldb/resource-fencing.d.ts +39 -0
- package/dist_ts/ts_nosqldb/resource-fencing.js +271 -0
- package/dist_ts/ts_nosqldb/rust-db-bridge.d.ts +213 -0
- package/dist_ts/ts_nosqldb/rust-db-bridge.js +439 -0
- package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.d.ts +204 -0
- package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.js +456 -0
- package/dist_ts/ts_nosqldb/server/index.d.ts +3 -0
- package/dist_ts/ts_nosqldb/server/index.js +3 -0
- package/dist_ts/ts_nosqldb/service-types.d.ts +341 -0
- package/dist_ts/ts_nosqldb/service-types.js +62 -0
- package/dist_ts/ts_nosqldb/storage-rehearsal.d.ts +103 -0
- package/dist_ts/ts_nosqldb/storage-rehearsal.js +131 -0
- package/dist_ts/ts_nosqldb/storage-root-relocation.d.ts +27 -0
- package/dist_ts/ts_nosqldb/storage-root-relocation.js +211 -0
- package/dist_ts_debugserver/bundled.d.ts +4 -0
- package/dist_ts_debugserver/bundled.js +16 -0
- package/dist_ts_debugserver/classes.debugserver.d.ts +36 -0
- package/dist_ts_debugserver/classes.debugserver.js +95 -0
- package/dist_ts_debugserver/index.d.ts +2 -0
- package/dist_ts_debugserver/index.js +2 -0
- package/dist_ts_debugserver/plugins.d.ts +2 -0
- package/dist_ts_debugserver/plugins.js +3 -0
- package/dist_ts_debugui/index.d.ts +2 -0
- package/dist_ts_debugui/index.js +2 -0
- package/dist_ts_debugui/nosqldb-debugui.d.ts +62 -0
- package/dist_ts_debugui/nosqldb-debugui.js +1132 -0
- package/dist_ts_debugui/plugins.d.ts +1 -0
- package/dist_ts_debugui/plugins.js +2 -0
- package/license +21 -0
- package/package.json +82 -0
- package/readme.md +1733 -0
- package/third-party-notices.md +925 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/index.ts +85 -0
- package/ts/ts_local/classes.localnosqldb.ts +500 -0
- package/ts/ts_local/index.ts +19 -0
- package/ts/ts_local/plugins.ts +1 -0
- package/ts/ts_nosqldb/classes.storagerehearsal.ts +109 -0
- package/ts/ts_nosqldb/file-storage-options.ts +33 -0
- package/ts/ts_nosqldb/file-storage-startup.ts +9 -0
- package/ts/ts_nosqldb/index.ts +104 -0
- package/ts/ts_nosqldb/offline-inspection.ts +375 -0
- package/ts/ts_nosqldb/plugins.ts +12 -0
- package/ts/ts_nosqldb/resource-fencing.ts +368 -0
- package/ts/ts_nosqldb/rust-db-bridge.ts +964 -0
- package/ts/ts_nosqldb/server/classes.nosqldbserver.ts +696 -0
- package/ts/ts_nosqldb/server/index.ts +4 -0
- package/ts/ts_nosqldb/service-types.ts +484 -0
- package/ts/ts_nosqldb/storage-rehearsal.ts +215 -0
- package/ts/ts_nosqldb/storage-root-relocation.ts +277 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autocreated commitinfo by @push.rocks/commitinfo
|
|
3
|
+
*/
|
|
4
|
+
export const commitinfo = {
|
|
5
|
+
name: '@lossless.org/nosqldb',
|
|
6
|
+
version: '8.0.0',
|
|
7
|
+
description: 'A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.'
|
|
8
|
+
}
|
package/ts/index.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { commitinfo } from './00_commitinfo_data.js';
|
|
2
|
+
|
|
3
|
+
// Export core NoSQLDB module
|
|
4
|
+
export * from './ts_nosqldb/index.js';
|
|
5
|
+
|
|
6
|
+
// Export LocalNoSqlDb
|
|
7
|
+
export { LocalNoSqlDb } from './ts_local/index.js';
|
|
8
|
+
export type {
|
|
9
|
+
ILocalNoSqlDbOptions,
|
|
10
|
+
ILocalNoSqlDbConnectionInfo,
|
|
11
|
+
ILocalNoSqlDbOfflineInspectionLimits,
|
|
12
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspectionLimits,
|
|
13
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
|
|
14
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult,
|
|
15
|
+
ILocalNoSqlDbOfflineStringValueInspection,
|
|
16
|
+
TLocalNoSqlDbOfflineStringValueInspectionResult,
|
|
17
|
+
ILocalNoSqlDbStoppedStorageRootRelocationInput,
|
|
18
|
+
ILocalNoSqlDbStoppedStorageRootRelocationReceipt,
|
|
19
|
+
TLocalNoSqlDbStorageRootRelocationErrorCode,
|
|
20
|
+
} from './ts_local/index.js';
|
|
21
|
+
export {
|
|
22
|
+
LocalNoSqlDbStorageRootRelocationError,
|
|
23
|
+
localNoSqlDbStorageRootRelocationErrorCodes,
|
|
24
|
+
} from './ts_local/index.js';
|
|
25
|
+
|
|
26
|
+
// Export commitinfo
|
|
27
|
+
export { commitinfo };
|
|
28
|
+
|
|
29
|
+
// Re-export oplog / debug types for convenience
|
|
30
|
+
export type {
|
|
31
|
+
IOpLogEntry,
|
|
32
|
+
IOpLogResult,
|
|
33
|
+
IOpLogStats,
|
|
34
|
+
IRevertResult,
|
|
35
|
+
ICollectionInfo,
|
|
36
|
+
IDocumentsResult,
|
|
37
|
+
INoSqlDbMetrics,
|
|
38
|
+
INoSqlDbHealth,
|
|
39
|
+
INoSqlDbResourceFence,
|
|
40
|
+
INoSqlDbResourceFenceReceipt,
|
|
41
|
+
INoSqlDbHeldPublicationReceipt,
|
|
42
|
+
INoSqlDbCommitDatabasePublicationInput,
|
|
43
|
+
INoSqlDbManagementOperationOptions,
|
|
44
|
+
INoSqlDbDatabaseResourceFenceState,
|
|
45
|
+
INoSqlDbDatabaseAllocationIdentity,
|
|
46
|
+
INoSqlDbDatabaseAllocationLifecycle,
|
|
47
|
+
INoSqlDbDatabaseAllocationReadAccessGrantIdentity,
|
|
48
|
+
INoSqlDbIssueDatabaseAllocationReadAccessGrantInput,
|
|
49
|
+
INoSqlDbIssueDatabaseAllocationReadAccessGrantResult,
|
|
50
|
+
INoSqlDbRevokeDatabaseAllocationReadAccessGrantInput,
|
|
51
|
+
INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult,
|
|
52
|
+
TNoSqlDbCommitDatabasePublicationResult,
|
|
53
|
+
TNoSqlDbResourceFenceErrorCode,
|
|
54
|
+
TNoSqlDbAllocationReadAccessGrantErrorCode,
|
|
55
|
+
INoSqlDbDatabaseTenantInput,
|
|
56
|
+
INoSqlDbAttestDatabaseTenantInput,
|
|
57
|
+
INoSqlDbAttestDatabaseTenantResult,
|
|
58
|
+
INoSqlDbEnsureDatabaseTenantInput,
|
|
59
|
+
INoSqlDbEnsureDatabaseTenantResult,
|
|
60
|
+
INoSqlDbAllocateDatabaseTenantInput,
|
|
61
|
+
INoSqlDbAllocateDatabaseTenantResult,
|
|
62
|
+
INoSqlDbDeleteDatabaseTenantInput,
|
|
63
|
+
INoSqlDbRotateDatabaseTenantPasswordInput,
|
|
64
|
+
INoSqlDbDatabaseTenantDescriptor,
|
|
65
|
+
INoSqlDbDeleteDatabaseTenantResult,
|
|
66
|
+
INoSqlDbDatabaseExportCollection,
|
|
67
|
+
INoSqlDbDatabaseExport,
|
|
68
|
+
INoSqlDbDatabaseExportLimits,
|
|
69
|
+
INoSqlDbExportDatabaseInput,
|
|
70
|
+
INoSqlDbDatabaseContentDigestLimits,
|
|
71
|
+
INoSqlDbGetDatabaseContentDigestInput,
|
|
72
|
+
INoSqlDbDatabaseContentDigest,
|
|
73
|
+
INoSqlDbImportDatabaseInput,
|
|
74
|
+
INoSqlDbImportDatabaseResult,
|
|
75
|
+
TNoSqlDbEffectivePayloadInputV1,
|
|
76
|
+
} from './ts_nosqldb/index.js';
|
|
77
|
+
export {
|
|
78
|
+
NoSqlDbAllocationReadAccessGrantError,
|
|
79
|
+
normalizeNoSqlDbAllocationReadAccessGrantError,
|
|
80
|
+
noSqlDbAllocationReadAccessGrantErrorCodes,
|
|
81
|
+
NoSqlDbResourceFenceError,
|
|
82
|
+
normalizeNoSqlDbResourceFenceError,
|
|
83
|
+
noSqlDbResourceFenceErrorCodes,
|
|
84
|
+
createNoSqlDbEffectivePayloadSha256V1,
|
|
85
|
+
} from './ts_nosqldb/index.js';
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as net from 'net';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
import {
|
|
7
|
+
NoSqlDbServer,
|
|
8
|
+
type INoSqlDbAuthOptions,
|
|
9
|
+
type ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
|
|
10
|
+
type ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult,
|
|
11
|
+
type ILocalNoSqlDbStoppedStorageRootRelocationInput,
|
|
12
|
+
type ILocalNoSqlDbStoppedStorageRootRelocationReceipt,
|
|
13
|
+
type ILocalNoSqlDbOfflineStringValueInspection,
|
|
14
|
+
type INoSqlDbManagementOperationOptions,
|
|
15
|
+
type TLocalNoSqlDbOfflineStringValueInspectionResult,
|
|
16
|
+
} from '../ts_nosqldb/index.js';
|
|
17
|
+
import {
|
|
18
|
+
RustDbBridge,
|
|
19
|
+
rustDbBridgeStorageRootRelocation,
|
|
20
|
+
} from '../ts_nosqldb/rust-db-bridge.js';
|
|
21
|
+
import { validateNoSqlDbStorageRehearsalStartup } from '../ts_nosqldb/storage-rehearsal.js';
|
|
22
|
+
import {
|
|
23
|
+
type TNoSqlDbFileStorageStartup,
|
|
24
|
+
} from '../ts_nosqldb/file-storage-startup.js';
|
|
25
|
+
import {
|
|
26
|
+
normalizeNoSqlDbFileStorageOptions,
|
|
27
|
+
} from '../ts_nosqldb/file-storage-options.js';
|
|
28
|
+
import {
|
|
29
|
+
normalizeLocalNoSqlDbOfflinePhysicalNamespaceInspectionInput,
|
|
30
|
+
normalizeLocalNoSqlDbOfflineInspectionInput,
|
|
31
|
+
validateNoSqlDbManagementOperationOptions,
|
|
32
|
+
} from '../ts_nosqldb/offline-inspection.js';
|
|
33
|
+
import {
|
|
34
|
+
assertNoLocalNoSqlDbStorageRootRelocationWorkspace,
|
|
35
|
+
isLocalNoSqlDbStorageRootRelocationErrorCode,
|
|
36
|
+
LocalNoSqlDbStorageRootRelocationError,
|
|
37
|
+
normalizeLocalNoSqlDbStoppedStorageRootRelocationInput,
|
|
38
|
+
normalizeLocalNoSqlDbStoppedStorageRootRelocationReceipt,
|
|
39
|
+
} from '../ts_nosqldb/storage-root-relocation.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Connection information returned by LocalNoSqlDb.start()
|
|
43
|
+
*/
|
|
44
|
+
export interface ILocalNoSqlDbConnectionInfo {
|
|
45
|
+
/** The Unix socket file path */
|
|
46
|
+
socketPath: string;
|
|
47
|
+
/** Connection URI (mongodb:// scheme) ready for MongoClient */
|
|
48
|
+
connectionUri: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ILocalNoSqlDbOptions {
|
|
52
|
+
/** Persisted authentication, including the source-verbatim credentials of a rehearsal destination. */
|
|
53
|
+
auth?: INoSqlDbAuthOptions;
|
|
54
|
+
/** Exact rehearsal operation ID; requires current-format-only startup and an explicit private socket. */
|
|
55
|
+
storageRehearsalId?: string;
|
|
56
|
+
/** Exact engine executable. Invalid explicit paths reject without chmod or discovery fallback. Omit for normal discovery. */
|
|
57
|
+
binaryPath?: string;
|
|
58
|
+
/** Default: current-format-only. Native ownership precedes all file-root initialization. */
|
|
59
|
+
fileStorageStartup?: TNoSqlDbFileStorageStartup;
|
|
60
|
+
/** Required: where to store data */
|
|
61
|
+
folderPath: string;
|
|
62
|
+
/** Optional: custom socket path (default: auto-generated in /tmp) */
|
|
63
|
+
socketPath?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* LocalNoSqlDb - Lightweight local MongoDB-compatible database using Unix sockets
|
|
68
|
+
*
|
|
69
|
+
* This class wraps NoSqlDbServer and provides a simple interface for
|
|
70
|
+
* starting a local file-based database server. Returns connection
|
|
71
|
+
* info that you can use with any compatible driver instance.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* import { LocalNoSqlDb } from '@lossless.org/nosqldb';
|
|
76
|
+
* import { MongoClient } from 'mongodb';
|
|
77
|
+
*
|
|
78
|
+
* const db = new LocalNoSqlDb({ folderPath: './data' });
|
|
79
|
+
* const { connectionUri } = await db.start();
|
|
80
|
+
*
|
|
81
|
+
* // Connect with the driver
|
|
82
|
+
* const client = new MongoClient(connectionUri, { directConnection: true });
|
|
83
|
+
* await client.connect();
|
|
84
|
+
*
|
|
85
|
+
* // Use the client
|
|
86
|
+
* const collection = client.db('mydb').collection('users');
|
|
87
|
+
* await collection.insertOne({ name: 'Alice' });
|
|
88
|
+
*
|
|
89
|
+
* // When done
|
|
90
|
+
* await client.close();
|
|
91
|
+
* await db.stop();
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export class LocalNoSqlDb {
|
|
95
|
+
private static storageRootRelocationGate: Promise<void> = Promise.resolve();
|
|
96
|
+
private static storageRootRelocationBridge: RustDbBridge | undefined;
|
|
97
|
+
private options: ILocalNoSqlDbOptions;
|
|
98
|
+
private server: NoSqlDbServer | null = null;
|
|
99
|
+
private generatedSocketPath: string | null = null;
|
|
100
|
+
|
|
101
|
+
constructor(options: ILocalNoSqlDbOptions) {
|
|
102
|
+
validateNoSqlDbStorageRehearsalStartup({ ...options, storage: 'file', storagePath: options.folderPath });
|
|
103
|
+
this.options = {
|
|
104
|
+
...options,
|
|
105
|
+
...normalizeNoSqlDbFileStorageOptions({ ...options, storage: 'file' }),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private static createOfflineInspectionBridge(): RustDbBridge {
|
|
110
|
+
return new RustDbBridge();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static createStorageRootRelocationBridge(): RustDbBridge {
|
|
114
|
+
return new RustDbBridge();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private static async withStorageRootRelocationGate<TResult>(
|
|
118
|
+
operationArg: () => Promise<TResult>,
|
|
119
|
+
): Promise<TResult> {
|
|
120
|
+
const previous = LocalNoSqlDb.storageRootRelocationGate;
|
|
121
|
+
let release!: () => void;
|
|
122
|
+
LocalNoSqlDb.storageRootRelocationGate = new Promise<void>((resolveArg) => {
|
|
123
|
+
release = resolveArg;
|
|
124
|
+
});
|
|
125
|
+
await previous;
|
|
126
|
+
try {
|
|
127
|
+
return await operationArg();
|
|
128
|
+
} finally {
|
|
129
|
+
release();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private static remainingManagementTimeout(
|
|
134
|
+
deadlineArg: number | undefined,
|
|
135
|
+
): number | undefined {
|
|
136
|
+
if (deadlineArg === undefined) return undefined;
|
|
137
|
+
const remaining = deadlineArg - Date.now();
|
|
138
|
+
if (remaining <= 0) {
|
|
139
|
+
throw new LocalNoSqlDbStorageRootRelocationError('IO');
|
|
140
|
+
}
|
|
141
|
+
return remaining;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Relocate one stopped current native file root, including authenticated
|
|
146
|
+
* roots, under continuous ownership. Preserve the returned source receipt;
|
|
147
|
+
* interrupted operations require the identical request to finish recovery.
|
|
148
|
+
*/
|
|
149
|
+
public static async relocateStoppedStorageRoot(
|
|
150
|
+
inputArg: ILocalNoSqlDbStoppedStorageRootRelocationInput,
|
|
151
|
+
optionsArg?: INoSqlDbManagementOperationOptions,
|
|
152
|
+
): Promise<ILocalNoSqlDbStoppedStorageRootRelocationReceipt> {
|
|
153
|
+
let input: ILocalNoSqlDbStoppedStorageRootRelocationInput;
|
|
154
|
+
try {
|
|
155
|
+
input = normalizeLocalNoSqlDbStoppedStorageRootRelocationInput(inputArg);
|
|
156
|
+
validateNoSqlDbManagementOperationOptions(optionsArg);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error instanceof LocalNoSqlDbStorageRootRelocationError) throw error;
|
|
159
|
+
throw new LocalNoSqlDbStorageRootRelocationError('INVALID_REQUEST');
|
|
160
|
+
}
|
|
161
|
+
if (optionsArg?.signal?.aborted) {
|
|
162
|
+
throw new LocalNoSqlDbStorageRootRelocationError('IO');
|
|
163
|
+
}
|
|
164
|
+
if (process.platform !== 'linux' && process.platform !== 'darwin') {
|
|
165
|
+
throw new LocalNoSqlDbStorageRootRelocationError('UNSUPPORTED');
|
|
166
|
+
}
|
|
167
|
+
const deadline = optionsArg?.timeoutMs === undefined
|
|
168
|
+
? undefined
|
|
169
|
+
: Date.now() + optionsArg.timeoutMs;
|
|
170
|
+
|
|
171
|
+
return LocalNoSqlDb.withStorageRootRelocationGate(async () => {
|
|
172
|
+
const retainedBridge = LocalNoSqlDb.storageRootRelocationBridge;
|
|
173
|
+
if (retainedBridge) {
|
|
174
|
+
try {
|
|
175
|
+
await retainedBridge.terminate({ gracePeriodMs: 0 });
|
|
176
|
+
} catch {
|
|
177
|
+
throw new LocalNoSqlDbStorageRootRelocationError('RECOVERY_REQUIRED');
|
|
178
|
+
}
|
|
179
|
+
if (!retainedBridge.ownershipReleased) {
|
|
180
|
+
throw new LocalNoSqlDbStorageRootRelocationError('RECOVERY_REQUIRED');
|
|
181
|
+
}
|
|
182
|
+
if (LocalNoSqlDb.storageRootRelocationBridge === retainedBridge) {
|
|
183
|
+
LocalNoSqlDb.storageRootRelocationBridge = undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let bridge: RustDbBridge | undefined;
|
|
188
|
+
let receipt: ILocalNoSqlDbStoppedStorageRootRelocationReceipt | undefined;
|
|
189
|
+
let operationError: LocalNoSqlDbStorageRootRelocationError | undefined;
|
|
190
|
+
try {
|
|
191
|
+
bridge = LocalNoSqlDb.createStorageRootRelocationBridge();
|
|
192
|
+
LocalNoSqlDb.storageRootRelocationBridge = bridge;
|
|
193
|
+
const spawnTimeout = LocalNoSqlDb.remainingManagementTimeout(deadline);
|
|
194
|
+
const spawned = await bridge.spawn({
|
|
195
|
+
...(optionsArg?.signal ? { signal: optionsArg.signal } : {}),
|
|
196
|
+
...(spawnTimeout === undefined ? {} : { timeoutMs: spawnTimeout }),
|
|
197
|
+
});
|
|
198
|
+
if (!spawned) {
|
|
199
|
+
throw new LocalNoSqlDbStorageRootRelocationError('IO');
|
|
200
|
+
}
|
|
201
|
+
const commandTimeout = LocalNoSqlDb.remainingManagementTimeout(deadline);
|
|
202
|
+
const result = await bridge[rustDbBridgeStorageRootRelocation](input, {
|
|
203
|
+
...(optionsArg?.signal ? { signal: optionsArg.signal } : {}),
|
|
204
|
+
...(commandTimeout === undefined ? {} : { timeoutMs: commandTimeout }),
|
|
205
|
+
});
|
|
206
|
+
receipt = normalizeLocalNoSqlDbStoppedStorageRootRelocationReceipt(result, input);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof LocalNoSqlDbStorageRootRelocationError) {
|
|
209
|
+
operationError = error;
|
|
210
|
+
} else {
|
|
211
|
+
const requestWasDispatched = error instanceof Error
|
|
212
|
+
&& 'requestId' in error
|
|
213
|
+
&& typeof error.requestId === 'string';
|
|
214
|
+
if (
|
|
215
|
+
requestWasDispatched
|
|
216
|
+
&& error instanceof Error
|
|
217
|
+
&& 'responseErrorCode' in error
|
|
218
|
+
&& isLocalNoSqlDbStorageRootRelocationErrorCode(error.responseErrorCode)
|
|
219
|
+
) {
|
|
220
|
+
operationError = new LocalNoSqlDbStorageRootRelocationError(error.responseErrorCode);
|
|
221
|
+
} else {
|
|
222
|
+
operationError = new LocalNoSqlDbStorageRootRelocationError(
|
|
223
|
+
requestWasDispatched ? 'RECOVERY_REQUIRED' : 'IO',
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (bridge) {
|
|
230
|
+
try {
|
|
231
|
+
await bridge.terminate({ gracePeriodMs: 0 });
|
|
232
|
+
if (!bridge.ownershipReleased) {
|
|
233
|
+
throw new Error('termination was not confirmed');
|
|
234
|
+
}
|
|
235
|
+
if (LocalNoSqlDb.storageRootRelocationBridge === bridge) {
|
|
236
|
+
LocalNoSqlDb.storageRootRelocationBridge = undefined;
|
|
237
|
+
}
|
|
238
|
+
} catch {
|
|
239
|
+
operationError = new LocalNoSqlDbStorageRootRelocationError('RECOVERY_REQUIRED');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (operationError) throw operationError;
|
|
243
|
+
if (!receipt) {
|
|
244
|
+
throw new LocalNoSqlDbStorageRootRelocationError('RECOVERY_REQUIRED');
|
|
245
|
+
}
|
|
246
|
+
return receipt;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private static async runOfflineInspection<
|
|
251
|
+
TInput extends { folderPath: string },
|
|
252
|
+
TResult,
|
|
253
|
+
>(
|
|
254
|
+
inputArg: TInput,
|
|
255
|
+
optionsArg: INoSqlDbManagementOperationOptions | undefined,
|
|
256
|
+
normalizeInputArg: (inputArg: TInput) => TInput,
|
|
257
|
+
inspectArg: (
|
|
258
|
+
bridgeArg: RustDbBridge,
|
|
259
|
+
inputArg: TInput,
|
|
260
|
+
optionsArg?: INoSqlDbManagementOperationOptions,
|
|
261
|
+
) => Promise<TResult>,
|
|
262
|
+
): Promise<TResult> {
|
|
263
|
+
const validatedInput = normalizeInputArg(inputArg);
|
|
264
|
+
validateNoSqlDbManagementOperationOptions(optionsArg);
|
|
265
|
+
optionsArg?.signal?.throwIfAborted();
|
|
266
|
+
|
|
267
|
+
const input = normalizeInputArg({
|
|
268
|
+
...validatedInput,
|
|
269
|
+
folderPath: path.resolve(validatedInput.folderPath),
|
|
270
|
+
});
|
|
271
|
+
const deadline = optionsArg?.timeoutMs === undefined
|
|
272
|
+
? undefined
|
|
273
|
+
: Date.now() + optionsArg.timeoutMs;
|
|
274
|
+
const bridge = LocalNoSqlDb.createOfflineInspectionBridge();
|
|
275
|
+
let operationFailed = false;
|
|
276
|
+
let operationError: unknown;
|
|
277
|
+
let result: TResult | undefined;
|
|
278
|
+
try {
|
|
279
|
+
let spawnOptions = optionsArg;
|
|
280
|
+
if (deadline !== undefined) {
|
|
281
|
+
const remainingMs = deadline - Date.now();
|
|
282
|
+
if (remainingMs <= 0) {
|
|
283
|
+
throw new Error('NoSQLDB offline inspection timed out before sidecar spawn');
|
|
284
|
+
}
|
|
285
|
+
spawnOptions = {
|
|
286
|
+
signal: optionsArg?.signal,
|
|
287
|
+
timeoutMs: remainingMs,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (!await bridge.spawn(spawnOptions)) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
'nosqldb Rust binary not found. Set NOSQLDB_RUST_BINARY env var, ' +
|
|
293
|
+
'install the platform package, or build locally with `tsrust`.',
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
let commandOptions = optionsArg;
|
|
297
|
+
if (deadline !== undefined) {
|
|
298
|
+
const remainingMs = deadline - Date.now();
|
|
299
|
+
if (remainingMs <= 0) {
|
|
300
|
+
throw new Error('NoSQLDB offline inspection timed out while starting the sidecar');
|
|
301
|
+
}
|
|
302
|
+
commandOptions = {
|
|
303
|
+
signal: optionsArg?.signal,
|
|
304
|
+
timeoutMs: remainingMs,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
result = await inspectArg(bridge, input, commandOptions);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
operationFailed = true;
|
|
310
|
+
operationError = error;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
await bridge.terminate({ gracePeriodMs: 0 });
|
|
315
|
+
} catch (terminationError) {
|
|
316
|
+
if (operationFailed) {
|
|
317
|
+
throw new AggregateError(
|
|
318
|
+
[operationError, terminationError],
|
|
319
|
+
'NoSQLDB offline inspection failed and sidecar termination was incomplete',
|
|
320
|
+
{ cause: operationError },
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
throw terminationError;
|
|
324
|
+
}
|
|
325
|
+
if (operationFailed) {
|
|
326
|
+
throw operationError;
|
|
327
|
+
}
|
|
328
|
+
return result as TResult;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Inspect physical database and collection names while the owning engine is stopped.
|
|
333
|
+
* This starts only the Rust management sidecar and never initializes storage.
|
|
334
|
+
*/
|
|
335
|
+
public static async inspectOfflinePhysicalNamespaces(
|
|
336
|
+
inputArg: ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
|
|
337
|
+
optionsArg?: INoSqlDbManagementOperationOptions,
|
|
338
|
+
): Promise<ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult> {
|
|
339
|
+
return LocalNoSqlDb.runOfflineInspection(
|
|
340
|
+
inputArg,
|
|
341
|
+
optionsArg,
|
|
342
|
+
normalizeLocalNoSqlDbOfflinePhysicalNamespaceInspectionInput,
|
|
343
|
+
(bridgeArg, input, commandOptions) =>
|
|
344
|
+
bridgeArg.inspectOfflinePhysicalNamespaces(input, commandOptions),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Inspect one exact string value while the owning LocalNoSqlDb engine is stopped.
|
|
350
|
+
* This starts only the Rust management sidecar and never initializes storage.
|
|
351
|
+
*/
|
|
352
|
+
public static async inspectOfflineStringValue(
|
|
353
|
+
inputArg: ILocalNoSqlDbOfflineStringValueInspection,
|
|
354
|
+
optionsArg?: INoSqlDbManagementOperationOptions,
|
|
355
|
+
): Promise<TLocalNoSqlDbOfflineStringValueInspectionResult> {
|
|
356
|
+
return LocalNoSqlDb.runOfflineInspection(
|
|
357
|
+
inputArg,
|
|
358
|
+
optionsArg,
|
|
359
|
+
normalizeLocalNoSqlDbOfflineInspectionInput,
|
|
360
|
+
(bridgeArg, input, commandOptions) =>
|
|
361
|
+
bridgeArg.inspectOfflineStringValue(input, commandOptions),
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Generate a unique socket path in /tmp
|
|
367
|
+
*/
|
|
368
|
+
private generateSocketPath(): string {
|
|
369
|
+
const randomId = crypto.randomBytes(8).toString('hex');
|
|
370
|
+
return path.join(os.tmpdir(), `nosqldb-${randomId}.sock`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Check if a Unix socket is alive by attempting to connect.
|
|
375
|
+
*/
|
|
376
|
+
private static isSocketAlive(socketPath: string): Promise<boolean> {
|
|
377
|
+
return new Promise((resolve) => {
|
|
378
|
+
const client = net.createConnection({ path: socketPath }, () => {
|
|
379
|
+
client.destroy();
|
|
380
|
+
resolve(true);
|
|
381
|
+
});
|
|
382
|
+
client.on('error', () => {
|
|
383
|
+
resolve(false);
|
|
384
|
+
});
|
|
385
|
+
client.setTimeout(500, () => {
|
|
386
|
+
client.destroy();
|
|
387
|
+
resolve(false);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Remove stale nosqldb-*.sock files from /tmp.
|
|
394
|
+
* A socket is considered stale if connecting to it fails.
|
|
395
|
+
*/
|
|
396
|
+
private static async cleanStaleSockets(): Promise<void> {
|
|
397
|
+
const tmpDir = os.tmpdir();
|
|
398
|
+
let entries: string[];
|
|
399
|
+
try {
|
|
400
|
+
entries = await fs.readdir(tmpDir);
|
|
401
|
+
} catch {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const socketFiles = entries.filter(
|
|
405
|
+
(f) => f.startsWith('nosqldb-') && f.endsWith('.sock')
|
|
406
|
+
);
|
|
407
|
+
for (const name of socketFiles) {
|
|
408
|
+
const fullPath = path.join(tmpDir, name);
|
|
409
|
+
try {
|
|
410
|
+
const stat = await fs.stat(fullPath);
|
|
411
|
+
if (!stat.isSocket()) continue;
|
|
412
|
+
const alive = await LocalNoSqlDb.isSocketAlive(fullPath);
|
|
413
|
+
if (!alive) {
|
|
414
|
+
await fs.unlink(fullPath);
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
// File may have been removed already; ignore
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Start the local NoSQLDB server and return connection info
|
|
424
|
+
*/
|
|
425
|
+
async start(): Promise<ILocalNoSqlDbConnectionInfo> {
|
|
426
|
+
if (this.server) {
|
|
427
|
+
throw new Error('LocalNoSqlDb is already running');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Use provided socket path or generate one
|
|
431
|
+
this.generatedSocketPath = this.options.socketPath ?? this.generateSocketPath();
|
|
432
|
+
|
|
433
|
+
this.server = new NoSqlDbServer({
|
|
434
|
+
binaryPath: this.options.binaryPath,
|
|
435
|
+
storageRehearsalId: this.options.storageRehearsalId,
|
|
436
|
+
auth: this.options.auth,
|
|
437
|
+
fileStorageStartup: this.options.fileStorageStartup,
|
|
438
|
+
socketPath: this.generatedSocketPath,
|
|
439
|
+
storage: 'file',
|
|
440
|
+
storagePath: this.options.folderPath,
|
|
441
|
+
});
|
|
442
|
+
await this.server.start();
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
socketPath: this.generatedSocketPath,
|
|
446
|
+
connectionUri: this.server.getConnectionUri(),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get connection info (throws if not started)
|
|
452
|
+
*/
|
|
453
|
+
getConnectionInfo(): ILocalNoSqlDbConnectionInfo {
|
|
454
|
+
if (!this.server || !this.generatedSocketPath) {
|
|
455
|
+
throw new Error('LocalNoSqlDb is not running. Call start() first.');
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
socketPath: this.generatedSocketPath,
|
|
459
|
+
connectionUri: this.server.getConnectionUri(),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get the underlying NoSqlDbServer instance (throws if not started)
|
|
465
|
+
*/
|
|
466
|
+
getServer(): NoSqlDbServer {
|
|
467
|
+
if (!this.server) {
|
|
468
|
+
throw new Error('LocalNoSqlDb is not running. Call start() first.');
|
|
469
|
+
}
|
|
470
|
+
return this.server;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Get the connection URI
|
|
475
|
+
*/
|
|
476
|
+
getConnectionUri(): string {
|
|
477
|
+
if (!this.server) {
|
|
478
|
+
throw new Error('LocalNoSqlDb is not running. Call start() first.');
|
|
479
|
+
}
|
|
480
|
+
return this.server.getConnectionUri();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Check if the server is running
|
|
485
|
+
*/
|
|
486
|
+
get running(): boolean {
|
|
487
|
+
return this.server !== null && this.server.running;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Stop the local NoSQLDB server
|
|
492
|
+
*/
|
|
493
|
+
async stop(): Promise<void> {
|
|
494
|
+
if (this.server) {
|
|
495
|
+
await this.server.stop();
|
|
496
|
+
this.server = null;
|
|
497
|
+
this.generatedSocketPath = null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { LocalNoSqlDb } from './classes.localnosqldb.js';
|
|
2
|
+
export type { ILocalNoSqlDbOptions, ILocalNoSqlDbConnectionInfo } from './classes.localnosqldb.js';
|
|
3
|
+
export type {
|
|
4
|
+
ILocalNoSqlDbOfflineInspectionLimits,
|
|
5
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspectionLimits,
|
|
6
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
|
|
7
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult,
|
|
8
|
+
ILocalNoSqlDbOfflineStringValueInspection,
|
|
9
|
+
TLocalNoSqlDbOfflineStringValueInspectionResult,
|
|
10
|
+
} from '../ts_nosqldb/service-types.js';
|
|
11
|
+
export type {
|
|
12
|
+
ILocalNoSqlDbStoppedStorageRootRelocationInput,
|
|
13
|
+
ILocalNoSqlDbStoppedStorageRootRelocationReceipt,
|
|
14
|
+
TLocalNoSqlDbStorageRootRelocationErrorCode,
|
|
15
|
+
} from '../ts_nosqldb/storage-root-relocation.js';
|
|
16
|
+
export {
|
|
17
|
+
LocalNoSqlDbStorageRootRelocationError,
|
|
18
|
+
localNoSqlDbStorageRootRelocationErrorCodes,
|
|
19
|
+
} from '../ts_nosqldb/storage-root-relocation.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Local module plugins - currently no external dependencies needed
|