@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,109 @@
|
|
|
1
|
+
import { RustDbBridge, rustDbBridgeStorageRehearsal } from './rust-db-bridge.js';
|
|
2
|
+
import type { INoSqlDbManagementOperationOptions } from './service-types.js';
|
|
3
|
+
import {
|
|
4
|
+
normalizeNoSqlDbStorageRehearsalInput, normalizeNoSqlDbStorageRehearsalReceipt,
|
|
5
|
+
normalizeNoSqlDbStorageRehearsalOptions, normalizeNoSqlDbStorageRehearsalOperationOptions,
|
|
6
|
+
NoSqlDbStorageRehearsalError, noSqlDbStorageRehearsalErrorCodes,
|
|
7
|
+
type INoSqlDbStorageRehearsalInput, type INoSqlDbStorageRehearsalOptions,
|
|
8
|
+
type INoSqlDbStorageRehearsalReceipt, type TNoSqlDbStorageRehearsalErrorCode,
|
|
9
|
+
} from './storage-rehearsal.js';
|
|
10
|
+
|
|
11
|
+
/** Owns one offline rehearsal sidecar at a time. Separate instances are independent;
|
|
12
|
+
* native root ownership excludes concurrent use of a source or destination. */
|
|
13
|
+
export class NoSqlDbStorageRehearsal {
|
|
14
|
+
private readonly binaryPath: string | undefined;
|
|
15
|
+
private bridge: RustDbBridge | undefined;
|
|
16
|
+
private cancellation: AbortController | undefined;
|
|
17
|
+
private completion: Promise<void> | undefined;
|
|
18
|
+
|
|
19
|
+
public constructor(options: INoSqlDbStorageRehearsalOptions = {}) {
|
|
20
|
+
this.binaryPath = normalizeNoSqlDbStorageRehearsalOptions(options).binaryPath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Create or exactly retry a rehearsal. The default deadline is one hour,
|
|
24
|
+
* including spawn and the operation; maximum 24 hours. Cleanup confirms exit
|
|
25
|
+
* before this method settles. Retry the identical request after interruption. */
|
|
26
|
+
public async createDestination(inputArg: INoSqlDbStorageRehearsalInput,
|
|
27
|
+
optionsArg?: INoSqlDbManagementOperationOptions): Promise<INoSqlDbStorageRehearsalReceipt> {
|
|
28
|
+
let input: ReturnType<typeof normalizeNoSqlDbStorageRehearsalInput>;
|
|
29
|
+
try { input = normalizeNoSqlDbStorageRehearsalInput(inputArg); }
|
|
30
|
+
catch { throw new NoSqlDbStorageRehearsalError('INVALID_REQUEST'); }
|
|
31
|
+
const options = normalizeNoSqlDbStorageRehearsalOperationOptions(optionsArg);
|
|
32
|
+
const { timeoutMs } = options;
|
|
33
|
+
if (options.signal?.aborted) throw new NoSqlDbStorageRehearsalError('CANCELLED');
|
|
34
|
+
if (process.platform !== 'linux' && process.platform !== 'darwin') throw new NoSqlDbStorageRehearsalError('UNSUPPORTED');
|
|
35
|
+
if (this.completion) throw new NoSqlDbStorageRehearsalError('BUSY');
|
|
36
|
+
const deadline = performance.now() + timeoutMs;
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
this.cancellation = controller;
|
|
39
|
+
const cancel = () => controller.abort();
|
|
40
|
+
options.signal?.addEventListener('abort', cancel, { once: true });
|
|
41
|
+
let complete!: () => void;
|
|
42
|
+
this.completion = new Promise<void>((resolve) => { complete = resolve; });
|
|
43
|
+
let dispatched = false;
|
|
44
|
+
let receipt: INoSqlDbStorageRehearsalReceipt | undefined;
|
|
45
|
+
let failure: NoSqlDbStorageRehearsalError | undefined;
|
|
46
|
+
const remaining = () => {
|
|
47
|
+
const timeout = Math.ceil(deadline - performance.now());
|
|
48
|
+
if (timeout <= 0 || controller.signal.aborted) throw new NoSqlDbStorageRehearsalError(dispatched ? 'RECOVERY_REQUIRED' : 'CANCELLED');
|
|
49
|
+
return timeout;
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
// A failed previous cleanup keeps its bridge here until exit is confirmed.
|
|
53
|
+
await this.terminateBridge();
|
|
54
|
+
remaining();
|
|
55
|
+
const bridge = new RustDbBridge({ binaryPath: this.binaryPath });
|
|
56
|
+
this.bridge = bridge;
|
|
57
|
+
if (!await bridge.spawn({ timeoutMs: remaining(), signal: controller.signal })) {
|
|
58
|
+
throw new NoSqlDbStorageRehearsalError('BINARY_UNAVAILABLE');
|
|
59
|
+
}
|
|
60
|
+
const commandTimeout = remaining();
|
|
61
|
+
dispatched = true;
|
|
62
|
+
const result = await bridge[rustDbBridgeStorageRehearsal](input, {
|
|
63
|
+
timeoutMs: commandTimeout, signal: controller.signal,
|
|
64
|
+
});
|
|
65
|
+
receipt = normalizeNoSqlDbStorageRehearsalReceipt(result, input);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error instanceof NoSqlDbStorageRehearsalError) failure = error;
|
|
68
|
+
else {
|
|
69
|
+
const nativeCode = error instanceof Error && 'responseErrorCode' in error ? error.responseErrorCode : undefined;
|
|
70
|
+
const code: TNoSqlDbStorageRehearsalErrorCode = typeof nativeCode === 'string'
|
|
71
|
+
&& (noSqlDbStorageRehearsalErrorCodes as readonly string[]).includes(nativeCode)
|
|
72
|
+
? nativeCode as TNoSqlDbStorageRehearsalErrorCode
|
|
73
|
+
: dispatched ? 'RECOVERY_REQUIRED' : controller.signal.aborted || performance.now() >= deadline ? 'CANCELLED' : 'BINARY_UNAVAILABLE';
|
|
74
|
+
failure = new NoSqlDbStorageRehearsalError(code);
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
try { await this.terminateBridge(); }
|
|
78
|
+
catch { failure = new NoSqlDbStorageRehearsalError('RECOVERY_REQUIRED'); }
|
|
79
|
+
options.signal?.removeEventListener('abort', cancel);
|
|
80
|
+
this.cancellation = undefined;
|
|
81
|
+
this.completion = undefined;
|
|
82
|
+
complete();
|
|
83
|
+
}
|
|
84
|
+
if (failure) throw failure;
|
|
85
|
+
if (!receipt) throw new NoSqlDbStorageRehearsalError('RECOVERY_REQUIRED');
|
|
86
|
+
return receipt;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Cancel active work and confirm sidecar exit. Retry this method if cleanup
|
|
90
|
+
* reports RECOVERY_REQUIRED; the instance retains unconfirmed ownership. */
|
|
91
|
+
public async stop(): Promise<void> {
|
|
92
|
+
const completion = this.completion;
|
|
93
|
+
this.cancellation?.abort();
|
|
94
|
+
let failure: unknown;
|
|
95
|
+
try { await this.terminateBridge(); } catch (error) { failure = error; }
|
|
96
|
+
await completion;
|
|
97
|
+
if (failure) throw new NoSqlDbStorageRehearsalError('RECOVERY_REQUIRED');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async terminateBridge(): Promise<void> {
|
|
101
|
+
const bridge = this.bridge;
|
|
102
|
+
if (!bridge) return;
|
|
103
|
+
try {
|
|
104
|
+
await bridge.terminate({ gracePeriodMs: 0 });
|
|
105
|
+
if (!bridge.ownershipReleased) throw new NoSqlDbStorageRehearsalError('RECOVERY_REQUIRED');
|
|
106
|
+
if (this.bridge === bridge) this.bridge = undefined;
|
|
107
|
+
} catch { throw new NoSqlDbStorageRehearsalError('RECOVERY_REQUIRED'); }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeNoSqlDbFileStorageStartup,
|
|
3
|
+
type TNoSqlDbFileStorageStartup,
|
|
4
|
+
} from './file-storage-startup.js';
|
|
5
|
+
|
|
6
|
+
interface IFileStorageOptions {
|
|
7
|
+
storage?: string;
|
|
8
|
+
fileStorageStartup?: unknown;
|
|
9
|
+
auth?: { users?: unknown };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Pure, instance-local admission for the sole native serving engine. */
|
|
13
|
+
export const normalizeNoSqlDbFileStorageOptions = (optionsArg: IFileStorageOptions): {
|
|
14
|
+
fileStorageStartup: TNoSqlDbFileStorageStartup | undefined;
|
|
15
|
+
} => {
|
|
16
|
+
for (const removed of ['fileStorageEngine', 'persistPath', 'persistIntervalMs']) {
|
|
17
|
+
if (removed in optionsArg) throw new TypeError(`NoSQLDB ${removed} has been removed; native memory and file storage are selected by storage`);
|
|
18
|
+
}
|
|
19
|
+
if (optionsArg.auth && 'usersPath' in optionsArg.auth) {
|
|
20
|
+
throw new TypeError('NoSQLDB auth.usersPath has been removed; authentication is stored in the native database');
|
|
21
|
+
}
|
|
22
|
+
const storage = optionsArg.storage ?? 'memory';
|
|
23
|
+
if (storage !== 'file' && storage !== 'memory') throw new TypeError('NoSQLDB storage must be "memory" or "file"');
|
|
24
|
+
if (process.platform !== 'linux' && process.platform !== 'darwin') {
|
|
25
|
+
throw new TypeError('NoSQLDB native storage requires Linux or macOS');
|
|
26
|
+
}
|
|
27
|
+
if (optionsArg.fileStorageStartup !== undefined && storage !== 'file') {
|
|
28
|
+
throw new TypeError('NoSQLDB fileStorageStartup requires storage: "file"');
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
fileStorageStartup: storage === 'file' ? normalizeNoSqlDbFileStorageStartup(optionsArg.fileStorageStartup) : undefined,
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Native format admission is read-only until the engine owns the file root. */
|
|
2
|
+
export type TNoSqlDbFileStorageStartup = 'current-format-only';
|
|
3
|
+
|
|
4
|
+
export const normalizeNoSqlDbFileStorageStartup = (
|
|
5
|
+
valueArg: unknown,
|
|
6
|
+
): TNoSqlDbFileStorageStartup => {
|
|
7
|
+
if (valueArg === undefined || valueArg === 'current-format-only') return 'current-format-only';
|
|
8
|
+
throw new TypeError('NoSQLDB fileStorageStartup must be "current-format-only"; automatic storage conversion has been removed');
|
|
9
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// NoSQLDB - Wire protocol compatible embedded database server
|
|
2
|
+
export { NoSqlDbStorageRehearsal } from './classes.storagerehearsal.js';
|
|
3
|
+
export { NoSqlDbStorageRehearsalError, noSqlDbStorageRehearsalErrorCodes } from './storage-rehearsal.js';
|
|
4
|
+
export type {
|
|
5
|
+
INoSqlDbStorageRehearsalOptions, INoSqlDbStorageRehearsalInput,
|
|
6
|
+
INoSqlDbStorageRehearsalReceipt, INoSqlDbStorageRehearsalPreservation,
|
|
7
|
+
TNoSqlDbStorageRehearsalErrorCode,
|
|
8
|
+
} from './storage-rehearsal.js';
|
|
9
|
+
|
|
10
|
+
// Export server (the main entry point for using NoSQLDB)
|
|
11
|
+
export { NoSqlDbServer } from './server/classes.nosqldbserver.js';
|
|
12
|
+
export type { TNoSqlDbFileStorageStartup } from './file-storage-startup.js';
|
|
13
|
+
export type {
|
|
14
|
+
INoSqlDbAuthOptions,
|
|
15
|
+
INoSqlDbAuthUser,
|
|
16
|
+
INoSqlDbOplogOptions,
|
|
17
|
+
INoSqlDbServerOptions,
|
|
18
|
+
INoSqlDbTlsOptions,
|
|
19
|
+
} from './server/classes.nosqldbserver.js';
|
|
20
|
+
|
|
21
|
+
// Export bridge for advanced usage and its fail-stop ownership error.
|
|
22
|
+
export {
|
|
23
|
+
RustDbBridge,
|
|
24
|
+
NoSqlDbManagementOperationTerminationError,
|
|
25
|
+
} from './rust-db-bridge.js';
|
|
26
|
+
|
|
27
|
+
// Export oplog / debug types
|
|
28
|
+
export type {
|
|
29
|
+
IRustDbBridgeOptions,
|
|
30
|
+
INoSqlDbQueryOptions,
|
|
31
|
+
IOpLogEntry,
|
|
32
|
+
IOpLogResult,
|
|
33
|
+
IOpLogStats,
|
|
34
|
+
IRevertResult,
|
|
35
|
+
ICollectionInfo,
|
|
36
|
+
IDocumentsResult,
|
|
37
|
+
INoSqlDbMetrics,
|
|
38
|
+
} from './rust-db-bridge.js';
|
|
39
|
+
|
|
40
|
+
// Export service API types
|
|
41
|
+
export type {
|
|
42
|
+
INoSqlDbHealth,
|
|
43
|
+
INoSqlDbResourceFence,
|
|
44
|
+
INoSqlDbResourceFenceReceipt,
|
|
45
|
+
INoSqlDbHeldPublicationReceipt,
|
|
46
|
+
INoSqlDbCommitDatabasePublicationInput,
|
|
47
|
+
INoSqlDbManagementOperationOptions,
|
|
48
|
+
INoSqlDbDatabaseResourceFenceState,
|
|
49
|
+
INoSqlDbDatabaseAllocationIdentity,
|
|
50
|
+
INoSqlDbDatabaseAllocationLifecycle,
|
|
51
|
+
INoSqlDbDatabaseAllocationReadAccessGrantIdentity,
|
|
52
|
+
INoSqlDbIssueDatabaseAllocationReadAccessGrantInput,
|
|
53
|
+
INoSqlDbIssueDatabaseAllocationReadAccessGrantResult,
|
|
54
|
+
INoSqlDbRevokeDatabaseAllocationReadAccessGrantInput,
|
|
55
|
+
INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult,
|
|
56
|
+
ILocalNoSqlDbOfflineInspectionLimits,
|
|
57
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspectionLimits,
|
|
58
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
|
|
59
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult,
|
|
60
|
+
ILocalNoSqlDbOfflineStringValueInspection,
|
|
61
|
+
TLocalNoSqlDbOfflineStringValueInspectionResult,
|
|
62
|
+
TNoSqlDbCommitDatabasePublicationResult,
|
|
63
|
+
TNoSqlDbResourceFenceErrorCode,
|
|
64
|
+
TNoSqlDbAllocationReadAccessGrantErrorCode,
|
|
65
|
+
INoSqlDbDatabaseTenantInput,
|
|
66
|
+
INoSqlDbAttestDatabaseTenantInput,
|
|
67
|
+
INoSqlDbAttestDatabaseTenantResult,
|
|
68
|
+
INoSqlDbEnsureDatabaseTenantInput,
|
|
69
|
+
INoSqlDbEnsureDatabaseTenantResult,
|
|
70
|
+
INoSqlDbAllocateDatabaseTenantInput,
|
|
71
|
+
INoSqlDbAllocateDatabaseTenantResult,
|
|
72
|
+
INoSqlDbDeleteDatabaseTenantInput,
|
|
73
|
+
INoSqlDbRotateDatabaseTenantPasswordInput,
|
|
74
|
+
INoSqlDbDatabaseTenantDescriptor,
|
|
75
|
+
INoSqlDbDeleteDatabaseTenantResult,
|
|
76
|
+
INoSqlDbDatabaseExportCollection,
|
|
77
|
+
INoSqlDbDatabaseExport,
|
|
78
|
+
INoSqlDbDatabaseExportLimits,
|
|
79
|
+
INoSqlDbExportDatabaseInput,
|
|
80
|
+
INoSqlDbDatabaseContentDigestLimits,
|
|
81
|
+
INoSqlDbGetDatabaseContentDigestInput,
|
|
82
|
+
INoSqlDbDatabaseContentDigest,
|
|
83
|
+
INoSqlDbImportDatabaseInput,
|
|
84
|
+
INoSqlDbImportDatabaseResult,
|
|
85
|
+
} from './service-types.js';
|
|
86
|
+
export {
|
|
87
|
+
NoSqlDbAllocationReadAccessGrantError,
|
|
88
|
+
normalizeNoSqlDbAllocationReadAccessGrantError,
|
|
89
|
+
noSqlDbAllocationReadAccessGrantErrorCodes,
|
|
90
|
+
NoSqlDbResourceFenceError,
|
|
91
|
+
normalizeNoSqlDbResourceFenceError,
|
|
92
|
+
noSqlDbResourceFenceErrorCodes,
|
|
93
|
+
} from './service-types.js';
|
|
94
|
+
export type {
|
|
95
|
+
ILocalNoSqlDbStoppedStorageRootRelocationInput,
|
|
96
|
+
ILocalNoSqlDbStoppedStorageRootRelocationReceipt,
|
|
97
|
+
TLocalNoSqlDbStorageRootRelocationErrorCode,
|
|
98
|
+
} from './storage-root-relocation.js';
|
|
99
|
+
export {
|
|
100
|
+
LocalNoSqlDbStorageRootRelocationError,
|
|
101
|
+
localNoSqlDbStorageRootRelocationErrorCodes,
|
|
102
|
+
} from './storage-root-relocation.js';
|
|
103
|
+
export type { TNoSqlDbEffectivePayloadInputV1 } from './resource-fencing.js';
|
|
104
|
+
export { createNoSqlDbEffectivePayloadSha256V1 } from './resource-fencing.js';
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import type {
|
|
3
|
+
ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
|
|
4
|
+
ILocalNoSqlDbOfflineStringValueInspection,
|
|
5
|
+
INoSqlDbManagementOperationOptions,
|
|
6
|
+
} from './service-types.js';
|
|
7
|
+
|
|
8
|
+
export const localNoSqlDbOfflineInspectionBounds = {
|
|
9
|
+
maximumFolderPathBytes: 4095,
|
|
10
|
+
maximumNamespaceComponentBytes: 255,
|
|
11
|
+
maximumFieldNameBytes: 255,
|
|
12
|
+
maximumMatchValueBytes: 1024 * 1024,
|
|
13
|
+
maximumRequestBytes: 2 * 1024 * 1024,
|
|
14
|
+
maximumTimeoutMs: 2_147_483_647,
|
|
15
|
+
limits: {
|
|
16
|
+
maximumEntries: { minimum: 1, maximum: 100_000 },
|
|
17
|
+
maximumTotalFileBytes: { minimum: 1, maximum: 1024 ** 4 },
|
|
18
|
+
maximumRecords: { minimum: 1, maximum: 1_000_000_000 },
|
|
19
|
+
maximumRecordBytes: { minimum: 5, maximum: 16 * 1024 * 1024 },
|
|
20
|
+
maximumResultBytes: { minimum: 1, maximum: 16 * 1024 * 1024 },
|
|
21
|
+
},
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
export const localNoSqlDbOfflinePhysicalNamespaceInspectionBounds = {
|
|
25
|
+
maximumFolderPathBytes: 4095,
|
|
26
|
+
maximumRequestBytes: 2 * 1024 * 1024,
|
|
27
|
+
limits: {
|
|
28
|
+
maximumDatabases: { minimum: 1, maximum: 1_000_000 },
|
|
29
|
+
maximumCollections: { minimum: 1, maximum: 1_000_000 },
|
|
30
|
+
maximumIndexes: { minimum: 1, maximum: 1_000_000 },
|
|
31
|
+
maximumEntries: { minimum: 1, maximum: 100_000 },
|
|
32
|
+
maximumTotalFileBytes: { minimum: 1, maximum: 1024 * 1024 * 1024 * 1024 },
|
|
33
|
+
maximumRecordsPerCollection: { minimum: 1, maximum: 1_000_000_000 },
|
|
34
|
+
maximumRecordBytes: { minimum: 5, maximum: 16 * 1024 * 1024 },
|
|
35
|
+
maximumResultBytes: { minimum: 32, maximum: 16 * 1024 * 1024 },
|
|
36
|
+
},
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
const inputKeys = new Set([
|
|
40
|
+
'folderPath',
|
|
41
|
+
'databaseName',
|
|
42
|
+
'collectionName',
|
|
43
|
+
'match',
|
|
44
|
+
'valueField',
|
|
45
|
+
'limits',
|
|
46
|
+
]);
|
|
47
|
+
const matchKeys = new Set(['field', 'value']);
|
|
48
|
+
const limitKeys = [
|
|
49
|
+
'maximumEntries',
|
|
50
|
+
'maximumTotalFileBytes',
|
|
51
|
+
'maximumRecords',
|
|
52
|
+
'maximumRecordBytes',
|
|
53
|
+
'maximumResultBytes',
|
|
54
|
+
] as const;
|
|
55
|
+
const limitKeySet = new Set<string>(limitKeys);
|
|
56
|
+
const requestStructuralBytes = plugins.buffer.Buffer.byteLength(
|
|
57
|
+
'{"folderPath":,"databaseName":,"collectionName":,"match":{"field":,"value":},"valueField":,"limits":{"maximumEntries":,"maximumTotalFileBytes":,"maximumRecords":,"maximumRecordBytes":,"maximumResultBytes":}}',
|
|
58
|
+
'utf8',
|
|
59
|
+
);
|
|
60
|
+
const requestEnvelopeBytes = plugins.buffer.Buffer.byteLength(
|
|
61
|
+
'{"id":"req_9007199254740991","method":"inspectOfflineStringValue","params":}',
|
|
62
|
+
'utf8',
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const physicalNamespaceInputKeys = new Set(['folderPath', 'limits']);
|
|
66
|
+
const physicalNamespaceLimitKeys = [
|
|
67
|
+
'maximumDatabases',
|
|
68
|
+
'maximumCollections',
|
|
69
|
+
'maximumIndexes',
|
|
70
|
+
'maximumEntries',
|
|
71
|
+
'maximumTotalFileBytes',
|
|
72
|
+
'maximumRecordsPerCollection',
|
|
73
|
+
'maximumRecordBytes',
|
|
74
|
+
'maximumResultBytes',
|
|
75
|
+
] as const;
|
|
76
|
+
const physicalNamespaceLimitKeySet = new Set<string>(physicalNamespaceLimitKeys);
|
|
77
|
+
|
|
78
|
+
const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> =>
|
|
79
|
+
typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
|
|
80
|
+
|
|
81
|
+
const requirePhysicalNamespacePlainObject = (
|
|
82
|
+
valueArg: unknown,
|
|
83
|
+
allowedKeysArg: Set<string>,
|
|
84
|
+
labelArg: string,
|
|
85
|
+
): Record<string, unknown> => {
|
|
86
|
+
if (!isRecord(valueArg)) {
|
|
87
|
+
throw new TypeError(
|
|
88
|
+
`LocalNoSqlDb offline physical namespace inspection ${labelArg} must be a plain object`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
for (const key in valueArg) {
|
|
92
|
+
if (!allowedKeysArg.has(key)) {
|
|
93
|
+
throw new TypeError(
|
|
94
|
+
`LocalNoSqlDb offline physical namespace inspection ${labelArg} contains unknown key '${key}'`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (Object.getPrototypeOf(valueArg) !== Object.prototype) {
|
|
99
|
+
throw new TypeError(
|
|
100
|
+
`LocalNoSqlDb offline physical namespace inspection ${labelArg} must be a plain object`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
for (const key of allowedKeysArg) {
|
|
104
|
+
if (!Object.prototype.propertyIsEnumerable.call(valueArg, key)) {
|
|
105
|
+
throw new TypeError(
|
|
106
|
+
`LocalNoSqlDb offline physical namespace inspection ${labelArg} must contain own enumerable key '${key}'`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return valueArg;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const rejectUnknownEnumerableKeys = (
|
|
114
|
+
valueArg: object,
|
|
115
|
+
allowedKeysArg: Set<string>,
|
|
116
|
+
labelArg: string,
|
|
117
|
+
): void => {
|
|
118
|
+
for (const key in valueArg) {
|
|
119
|
+
if (!allowedKeysArg.has(key)) {
|
|
120
|
+
throw new TypeError(`LocalNoSqlDb offline inspection ${labelArg} contains unknown key '${key}'`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const encodedJsonStringBytes = (valueArg: string): number => {
|
|
126
|
+
let bytes = 2;
|
|
127
|
+
for (let index = 0; index < valueArg.length; index++) {
|
|
128
|
+
const code = valueArg.charCodeAt(index);
|
|
129
|
+
if (
|
|
130
|
+
code === 0x22
|
|
131
|
+
|| code === 0x5c
|
|
132
|
+
|| code === 0x08
|
|
133
|
+
|| code === 0x09
|
|
134
|
+
|| code === 0x0a
|
|
135
|
+
|| code === 0x0c
|
|
136
|
+
|| code === 0x0d
|
|
137
|
+
) {
|
|
138
|
+
bytes += 2;
|
|
139
|
+
} else if (code <= 0x1f) {
|
|
140
|
+
bytes += 6;
|
|
141
|
+
} else if (code <= 0x7f) {
|
|
142
|
+
bytes += 1;
|
|
143
|
+
} else if (code <= 0x7ff) {
|
|
144
|
+
bytes += 2;
|
|
145
|
+
} else if (code >= 0xd800 && code <= 0xdbff) {
|
|
146
|
+
const next = valueArg.charCodeAt(index + 1);
|
|
147
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
148
|
+
bytes += 4;
|
|
149
|
+
index++;
|
|
150
|
+
} else {
|
|
151
|
+
bytes += 6;
|
|
152
|
+
}
|
|
153
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
154
|
+
bytes += 6;
|
|
155
|
+
} else {
|
|
156
|
+
bytes += 3;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return bytes;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const validateString = (
|
|
163
|
+
valueArg: unknown,
|
|
164
|
+
labelArg: string,
|
|
165
|
+
minimumBytesArg: number,
|
|
166
|
+
maximumBytesArg: number,
|
|
167
|
+
rejectControlsArg: boolean,
|
|
168
|
+
): void => {
|
|
169
|
+
if (typeof valueArg !== 'string') {
|
|
170
|
+
throw new TypeError(`LocalNoSqlDb offline inspection ${labelArg} must be a string`);
|
|
171
|
+
}
|
|
172
|
+
const bytes = plugins.buffer.Buffer.byteLength(valueArg, 'utf8');
|
|
173
|
+
if (bytes < minimumBytesArg || bytes > maximumBytesArg) {
|
|
174
|
+
throw new RangeError(
|
|
175
|
+
`LocalNoSqlDb offline inspection ${labelArg} must be between ${minimumBytesArg} and ${maximumBytesArg} UTF-8 bytes`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (rejectControlsArg) {
|
|
179
|
+
for (const character of valueArg) {
|
|
180
|
+
if (/\p{Cc}/u.test(character)) {
|
|
181
|
+
throw new TypeError(
|
|
182
|
+
`LocalNoSqlDb offline inspection ${labelArg} must not contain control characters`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const normalizeLocalNoSqlDbOfflineInspectionInput = (
|
|
190
|
+
inputArg: ILocalNoSqlDbOfflineStringValueInspection,
|
|
191
|
+
): ILocalNoSqlDbOfflineStringValueInspection => {
|
|
192
|
+
if (!isRecord(inputArg as unknown)) {
|
|
193
|
+
throw new TypeError('LocalNoSqlDb offline inspection input must be an object');
|
|
194
|
+
}
|
|
195
|
+
rejectUnknownEnumerableKeys(inputArg, inputKeys, 'input');
|
|
196
|
+
const inputRecord = inputArg as unknown as Record<string, unknown>;
|
|
197
|
+
const match = inputRecord.match;
|
|
198
|
+
if (!isRecord(match)) {
|
|
199
|
+
throw new TypeError('LocalNoSqlDb offline inspection match must be an object');
|
|
200
|
+
}
|
|
201
|
+
rejectUnknownEnumerableKeys(match, matchKeys, 'match');
|
|
202
|
+
const limits = inputRecord.limits;
|
|
203
|
+
if (!isRecord(limits)) {
|
|
204
|
+
throw new TypeError('LocalNoSqlDb offline inspection limits must be an object');
|
|
205
|
+
}
|
|
206
|
+
rejectUnknownEnumerableKeys(limits, limitKeySet, 'limits');
|
|
207
|
+
|
|
208
|
+
const normalized = {
|
|
209
|
+
folderPath: inputRecord.folderPath,
|
|
210
|
+
databaseName: inputRecord.databaseName,
|
|
211
|
+
collectionName: inputRecord.collectionName,
|
|
212
|
+
match: {
|
|
213
|
+
field: match.field,
|
|
214
|
+
value: match.value,
|
|
215
|
+
},
|
|
216
|
+
valueField: inputRecord.valueField,
|
|
217
|
+
limits: {
|
|
218
|
+
maximumEntries: limits.maximumEntries,
|
|
219
|
+
maximumTotalFileBytes: limits.maximumTotalFileBytes,
|
|
220
|
+
maximumRecords: limits.maximumRecords,
|
|
221
|
+
maximumRecordBytes: limits.maximumRecordBytes,
|
|
222
|
+
maximumResultBytes: limits.maximumResultBytes,
|
|
223
|
+
},
|
|
224
|
+
} as unknown as ILocalNoSqlDbOfflineStringValueInspection;
|
|
225
|
+
|
|
226
|
+
const bounds = localNoSqlDbOfflineInspectionBounds;
|
|
227
|
+
validateString(normalized.folderPath, 'folderPath', 1, bounds.maximumFolderPathBytes, true);
|
|
228
|
+
for (const [label, value] of [
|
|
229
|
+
['databaseName', normalized.databaseName],
|
|
230
|
+
['collectionName', normalized.collectionName],
|
|
231
|
+
] as const) {
|
|
232
|
+
validateString(value, label, 1, bounds.maximumNamespaceComponentBytes, true);
|
|
233
|
+
if (value === '.' || value === '..' || value.includes('/') || value.includes('\\')) {
|
|
234
|
+
throw new TypeError(
|
|
235
|
+
`LocalNoSqlDb offline inspection ${label} must be one canonical path component`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
validateString(normalized.match.field, 'match.field', 1, bounds.maximumFieldNameBytes, true);
|
|
240
|
+
validateString(normalized.valueField, 'valueField', 1, bounds.maximumFieldNameBytes, true);
|
|
241
|
+
validateString(
|
|
242
|
+
normalized.match.value,
|
|
243
|
+
'match.value',
|
|
244
|
+
0,
|
|
245
|
+
bounds.maximumMatchValueBytes,
|
|
246
|
+
false,
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
for (const key of limitKeys) {
|
|
250
|
+
const value = normalized.limits[key];
|
|
251
|
+
const range = bounds.limits[key as keyof typeof bounds.limits];
|
|
252
|
+
if (!Number.isSafeInteger(value) || value < range.minimum || value > range.maximum) {
|
|
253
|
+
throw new RangeError(
|
|
254
|
+
`LocalNoSqlDb offline inspection limits.${key} must be a safe integer between ${range.minimum} and ${range.maximum}`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (plugins.buffer.Buffer.byteLength(normalized.match.value, 'utf8') > normalized.limits.maximumRecordBytes) {
|
|
259
|
+
throw new RangeError(
|
|
260
|
+
'LocalNoSqlDb offline inspection match.value must not exceed limits.maximumRecordBytes',
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let requestBytes = 32 // Reserve the optional native deadline field.
|
|
265
|
+
+ requestEnvelopeBytes
|
|
266
|
+
+ requestStructuralBytes
|
|
267
|
+
+ encodedJsonStringBytes(normalized.folderPath)
|
|
268
|
+
+ encodedJsonStringBytes(normalized.databaseName)
|
|
269
|
+
+ encodedJsonStringBytes(normalized.collectionName)
|
|
270
|
+
+ encodedJsonStringBytes(normalized.match.field)
|
|
271
|
+
+ encodedJsonStringBytes(normalized.match.value)
|
|
272
|
+
+ encodedJsonStringBytes(normalized.valueField);
|
|
273
|
+
for (const key of limitKeys) {
|
|
274
|
+
requestBytes += String(normalized.limits[key]).length;
|
|
275
|
+
}
|
|
276
|
+
if (requestBytes > bounds.maximumRequestBytes) {
|
|
277
|
+
throw new RangeError(
|
|
278
|
+
`LocalNoSqlDb offline inspection request exceeds the ${bounds.maximumRequestBytes}-byte serialized limit`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return normalized;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const normalizeLocalNoSqlDbOfflinePhysicalNamespaceInspectionInput = (
|
|
285
|
+
inputArg: ILocalNoSqlDbOfflinePhysicalNamespaceInspection,
|
|
286
|
+
): ILocalNoSqlDbOfflinePhysicalNamespaceInspection => {
|
|
287
|
+
const inputRecord = requirePhysicalNamespacePlainObject(
|
|
288
|
+
inputArg,
|
|
289
|
+
physicalNamespaceInputKeys,
|
|
290
|
+
'input',
|
|
291
|
+
);
|
|
292
|
+
const limits = requirePhysicalNamespacePlainObject(
|
|
293
|
+
inputRecord.limits,
|
|
294
|
+
physicalNamespaceLimitKeySet,
|
|
295
|
+
'limits',
|
|
296
|
+
);
|
|
297
|
+
const normalized: ILocalNoSqlDbOfflinePhysicalNamespaceInspection = {
|
|
298
|
+
folderPath: inputRecord.folderPath as string,
|
|
299
|
+
limits: {
|
|
300
|
+
maximumDatabases: limits.maximumDatabases as number,
|
|
301
|
+
maximumCollections: limits.maximumCollections as number,
|
|
302
|
+
maximumIndexes: limits.maximumIndexes as number,
|
|
303
|
+
maximumEntries: limits.maximumEntries as number,
|
|
304
|
+
maximumTotalFileBytes: limits.maximumTotalFileBytes as number,
|
|
305
|
+
maximumRecordsPerCollection: limits.maximumRecordsPerCollection as number,
|
|
306
|
+
maximumRecordBytes: limits.maximumRecordBytes as number,
|
|
307
|
+
maximumResultBytes: limits.maximumResultBytes as number,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
const bounds = localNoSqlDbOfflinePhysicalNamespaceInspectionBounds;
|
|
311
|
+
if (typeof normalized.folderPath !== 'string') {
|
|
312
|
+
throw new TypeError(
|
|
313
|
+
'LocalNoSqlDb offline physical namespace inspection folderPath must be a string',
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
const folderPathBytes = plugins.buffer.Buffer.byteLength(normalized.folderPath, 'utf8');
|
|
317
|
+
if (folderPathBytes < 1 || folderPathBytes > bounds.maximumFolderPathBytes) {
|
|
318
|
+
throw new RangeError(
|
|
319
|
+
`LocalNoSqlDb offline physical namespace inspection folderPath must be between 1 and ${bounds.maximumFolderPathBytes} UTF-8 bytes`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
for (const character of normalized.folderPath) {
|
|
323
|
+
if (/\p{Cc}/u.test(character)) {
|
|
324
|
+
throw new TypeError(
|
|
325
|
+
'LocalNoSqlDb offline physical namespace inspection folderPath must not contain control characters',
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
for (const key of physicalNamespaceLimitKeys) {
|
|
331
|
+
const value = normalized.limits[key];
|
|
332
|
+
const range = bounds.limits[key];
|
|
333
|
+
if (!Number.isSafeInteger(value) || value < range.minimum || value > range.maximum) {
|
|
334
|
+
throw new RangeError(
|
|
335
|
+
`LocalNoSqlDb offline physical namespace inspection limits.${key} must be a safe integer between ${range.minimum} and ${range.maximum}`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const requestBytes = plugins.buffer.Buffer.byteLength(JSON.stringify({
|
|
341
|
+
id: 'req_9007199254740991',
|
|
342
|
+
method: 'inspectOfflinePhysicalNamespaces',
|
|
343
|
+
params: {
|
|
344
|
+
folderPath: normalized.folderPath,
|
|
345
|
+
...normalized.limits,
|
|
346
|
+
},
|
|
347
|
+
}), 'utf8');
|
|
348
|
+
if (requestBytes > bounds.maximumRequestBytes) {
|
|
349
|
+
throw new RangeError(
|
|
350
|
+
`LocalNoSqlDb offline physical namespace inspection request exceeds the ${bounds.maximumRequestBytes}-byte serialized limit`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return normalized;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
export const validateNoSqlDbManagementOperationOptions = (
|
|
357
|
+
optionsArg?: INoSqlDbManagementOperationOptions,
|
|
358
|
+
): void => {
|
|
359
|
+
if (optionsArg === undefined) return;
|
|
360
|
+
if (!isRecord(optionsArg as unknown)) {
|
|
361
|
+
throw new TypeError('NoSQLDB management operation options must be an object');
|
|
362
|
+
}
|
|
363
|
+
if (
|
|
364
|
+
optionsArg.timeoutMs !== undefined
|
|
365
|
+
&& (
|
|
366
|
+
!Number.isSafeInteger(optionsArg.timeoutMs)
|
|
367
|
+
|| optionsArg.timeoutMs <= 0
|
|
368
|
+
|| optionsArg.timeoutMs > localNoSqlDbOfflineInspectionBounds.maximumTimeoutMs
|
|
369
|
+
)
|
|
370
|
+
) {
|
|
371
|
+
throw new RangeError(
|
|
372
|
+
`NoSQLDB management operation timeoutMs must be an integer between 1 and ${localNoSqlDbOfflineInspectionBounds.maximumTimeoutMs}`,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// node native scope
|
|
2
|
+
import * as buffer from 'node:buffer';
|
|
3
|
+
import * as crypto from 'node:crypto';
|
|
4
|
+
import * as fs from 'node:fs/promises';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
|
|
7
|
+
export { buffer, crypto, fs, path };
|
|
8
|
+
|
|
9
|
+
// @push.rocks scope
|
|
10
|
+
import * as smartrust from '@push.rocks/smartrust';
|
|
11
|
+
|
|
12
|
+
export { smartrust };
|