@push.rocks/smartdb 4.0.2 → 4.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.
@@ -1,5 +1,6 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import type {
3
+ ILocalSmartDbOfflinePhysicalNamespaceInspection,
3
4
  ILocalSmartDbOfflineStringValueInspection,
4
5
  ISmartDbManagementOperationOptions,
5
6
  } from './service-types.js';
@@ -20,6 +21,24 @@ export const localSmartDbOfflineInspectionBounds = {
20
21
  },
21
22
  } as const;
22
23
 
24
+ export const localSmartDbOfflinePhysicalNamespaceInspectionBounds = {
25
+ maximumFolderPathBytes: 4096,
26
+ maximumRequestBytes: 2 * 1024 * 1024,
27
+ limits: {
28
+ maximumEntries: { minimum: 1, maximum: 100_000 },
29
+ maximumDatabases: { minimum: 1, maximum: 4096 },
30
+ maximumCollections: { minimum: 1, maximum: 10_000 },
31
+ maximumIndexes: { minimum: 1, maximum: 100_000 },
32
+ maximumDataFileBytes: { minimum: 64, maximum: 256 * 1024 * 1024 },
33
+ maximumWalFileBytes: { minimum: 64, maximum: 64 * 1024 * 1024 },
34
+ maximumIndexFileBytes: { minimum: 2, maximum: 16 * 1024 * 1024 },
35
+ maximumTotalFileBytes: { minimum: 1, maximum: 1024 * 1024 * 1024 * 1024 },
36
+ maximumRecordsPerCollection: { minimum: 1, maximum: 1_000_000 },
37
+ maximumRecordBytes: { minimum: 22, maximum: 17 * 1024 * 1024 },
38
+ maximumResultBytes: { minimum: 32, maximum: 16 * 1024 * 1024 },
39
+ },
40
+ } as const;
41
+
23
42
  const inputKeys = new Set([
24
43
  'folderPath',
25
44
  'databaseName',
@@ -46,9 +65,57 @@ const requestEnvelopeBytes = plugins.buffer.Buffer.byteLength(
46
65
  'utf8',
47
66
  );
48
67
 
68
+ const physicalNamespaceInputKeys = new Set(['folderPath', 'limits']);
69
+ const physicalNamespaceLimitKeys = [
70
+ 'maximumEntries',
71
+ 'maximumDatabases',
72
+ 'maximumCollections',
73
+ 'maximumIndexes',
74
+ 'maximumDataFileBytes',
75
+ 'maximumWalFileBytes',
76
+ 'maximumIndexFileBytes',
77
+ 'maximumTotalFileBytes',
78
+ 'maximumRecordsPerCollection',
79
+ 'maximumRecordBytes',
80
+ 'maximumResultBytes',
81
+ ] as const;
82
+ const physicalNamespaceLimitKeySet = new Set<string>(physicalNamespaceLimitKeys);
83
+
49
84
  const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> =>
50
85
  typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
51
86
 
87
+ const requirePhysicalNamespacePlainObject = (
88
+ valueArg: unknown,
89
+ allowedKeysArg: Set<string>,
90
+ labelArg: string,
91
+ ): Record<string, unknown> => {
92
+ if (!isRecord(valueArg)) {
93
+ throw new TypeError(
94
+ `LocalSmartDb offline physical namespace inspection ${labelArg} must be a plain object`,
95
+ );
96
+ }
97
+ for (const key in valueArg) {
98
+ if (!allowedKeysArg.has(key)) {
99
+ throw new TypeError(
100
+ `LocalSmartDb offline physical namespace inspection ${labelArg} contains unknown key '${key}'`,
101
+ );
102
+ }
103
+ }
104
+ if (Object.getPrototypeOf(valueArg) !== Object.prototype) {
105
+ throw new TypeError(
106
+ `LocalSmartDb offline physical namespace inspection ${labelArg} must be a plain object`,
107
+ );
108
+ }
109
+ for (const key of allowedKeysArg) {
110
+ if (!Object.prototype.propertyIsEnumerable.call(valueArg, key)) {
111
+ throw new TypeError(
112
+ `LocalSmartDb offline physical namespace inspection ${labelArg} must contain own enumerable key '${key}'`,
113
+ );
114
+ }
115
+ }
116
+ return valueArg;
117
+ };
118
+
52
119
  const rejectUnknownEnumerableKeys = (
53
120
  valueArg: object,
54
121
  allowedKeysArg: Set<string>,
@@ -219,6 +286,81 @@ export const normalizeLocalSmartDbOfflineInspectionInput = (
219
286
  return normalized;
220
287
  };
221
288
 
289
+ export const normalizeLocalSmartDbOfflinePhysicalNamespaceInspectionInput = (
290
+ inputArg: ILocalSmartDbOfflinePhysicalNamespaceInspection,
291
+ ): ILocalSmartDbOfflinePhysicalNamespaceInspection => {
292
+ const inputRecord = requirePhysicalNamespacePlainObject(
293
+ inputArg,
294
+ physicalNamespaceInputKeys,
295
+ 'input',
296
+ );
297
+ const limits = requirePhysicalNamespacePlainObject(
298
+ inputRecord.limits,
299
+ physicalNamespaceLimitKeySet,
300
+ 'limits',
301
+ );
302
+ const normalized: ILocalSmartDbOfflinePhysicalNamespaceInspection = {
303
+ folderPath: inputRecord.folderPath as string,
304
+ limits: {
305
+ maximumEntries: limits.maximumEntries as number,
306
+ maximumDatabases: limits.maximumDatabases as number,
307
+ maximumCollections: limits.maximumCollections as number,
308
+ maximumIndexes: limits.maximumIndexes as number,
309
+ maximumDataFileBytes: limits.maximumDataFileBytes as number,
310
+ maximumWalFileBytes: limits.maximumWalFileBytes as number,
311
+ maximumIndexFileBytes: limits.maximumIndexFileBytes as number,
312
+ maximumTotalFileBytes: limits.maximumTotalFileBytes as number,
313
+ maximumRecordsPerCollection: limits.maximumRecordsPerCollection as number,
314
+ maximumRecordBytes: limits.maximumRecordBytes as number,
315
+ maximumResultBytes: limits.maximumResultBytes as number,
316
+ },
317
+ };
318
+ const bounds = localSmartDbOfflinePhysicalNamespaceInspectionBounds;
319
+ if (typeof normalized.folderPath !== 'string') {
320
+ throw new TypeError(
321
+ 'LocalSmartDb offline physical namespace inspection folderPath must be a string',
322
+ );
323
+ }
324
+ const folderPathBytes = plugins.buffer.Buffer.byteLength(normalized.folderPath, 'utf8');
325
+ if (folderPathBytes < 1 || folderPathBytes > bounds.maximumFolderPathBytes) {
326
+ throw new RangeError(
327
+ `LocalSmartDb offline physical namespace inspection folderPath must be between 1 and ${bounds.maximumFolderPathBytes} UTF-8 bytes`,
328
+ );
329
+ }
330
+ for (const character of normalized.folderPath) {
331
+ if (/\p{Cc}/u.test(character)) {
332
+ throw new TypeError(
333
+ 'LocalSmartDb offline physical namespace inspection folderPath must not contain control characters',
334
+ );
335
+ }
336
+ }
337
+
338
+ for (const key of physicalNamespaceLimitKeys) {
339
+ const value = normalized.limits[key];
340
+ const range = bounds.limits[key];
341
+ if (!Number.isSafeInteger(value) || value < range.minimum || value > range.maximum) {
342
+ throw new RangeError(
343
+ `LocalSmartDb offline physical namespace inspection limits.${key} must be a safe integer between ${range.minimum} and ${range.maximum}`,
344
+ );
345
+ }
346
+ }
347
+
348
+ const requestBytes = plugins.buffer.Buffer.byteLength(JSON.stringify({
349
+ id: 'req_9007199254740991',
350
+ method: 'inspectOfflinePhysicalNamespaces',
351
+ params: {
352
+ folderPath: normalized.folderPath,
353
+ ...normalized.limits,
354
+ },
355
+ }), 'utf8');
356
+ if (requestBytes > bounds.maximumRequestBytes) {
357
+ throw new RangeError(
358
+ `LocalSmartDb offline physical namespace inspection request exceeds the ${bounds.maximumRequestBytes}-byte serialized limit`,
359
+ );
360
+ }
361
+ return normalized;
362
+ };
363
+
222
364
  export const validateSmartDbManagementOperationOptions = (
223
365
  optionsArg?: ISmartDbManagementOperationOptions,
224
366
  ): void => {
@@ -7,12 +7,15 @@ import {
7
7
  normalizeSmartDbResourceFenceError,
8
8
  } from './service-types.js';
9
9
  import {
10
+ normalizeLocalSmartDbOfflinePhysicalNamespaceInspectionInput,
10
11
  normalizeLocalSmartDbOfflineInspectionInput,
11
12
  validateSmartDbManagementOperationOptions,
12
13
  } from './offline-inspection.js';
13
14
  import type {
14
15
  ISmartDbHealth,
15
16
  ISmartDbDatabaseTenantInput,
17
+ ISmartDbAttestDatabaseTenantInput,
18
+ ISmartDbAttestDatabaseTenantResult,
16
19
  ISmartDbEnsureDatabaseTenantInput,
17
20
  ISmartDbEnsureDatabaseTenantResult,
18
21
  ISmartDbAllocateDatabaseTenantInput,
@@ -34,6 +37,8 @@ import type {
34
37
  ISmartDbCommitDatabasePublicationInput,
35
38
  ISmartDbManagementOperationOptions,
36
39
  ISmartDbDatabaseResourceFenceState,
40
+ ILocalSmartDbOfflinePhysicalNamespaceInspection,
41
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionResult,
37
42
  ILocalSmartDbOfflineStringValueInspection,
38
43
  TLocalSmartDbOfflineStringValueInspectionResult,
39
44
  TSmartDbCommitDatabasePublicationResult,
@@ -42,6 +47,8 @@ import type {
42
47
  export type {
43
48
  ISmartDbHealth,
44
49
  ISmartDbDatabaseTenantInput,
50
+ ISmartDbAttestDatabaseTenantInput,
51
+ ISmartDbAttestDatabaseTenantResult,
45
52
  ISmartDbEnsureDatabaseTenantInput,
46
53
  ISmartDbEnsureDatabaseTenantResult,
47
54
  ISmartDbAllocateDatabaseTenantInput,
@@ -64,6 +71,9 @@ export type {
64
71
  ISmartDbManagementOperationOptions,
65
72
  ISmartDbDatabaseResourceFenceState,
66
73
  ILocalSmartDbOfflineInspectionLimits,
74
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionLimits,
75
+ ILocalSmartDbOfflinePhysicalNamespaceInspection,
76
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionResult,
67
77
  ILocalSmartDbOfflineStringValueInspection,
68
78
  TLocalSmartDbOfflineStringValueInspectionResult,
69
79
  TSmartDbCommitDatabasePublicationResult,
@@ -165,7 +175,15 @@ export interface ISmartDbMetrics {
165
175
  /**
166
176
  * Type-safe command definitions for the RustDb IPC protocol.
167
177
  */
178
+ type TOfflinePhysicalNamespaceInspectionCommandParams = {
179
+ folderPath: string;
180
+ } & ILocalSmartDbOfflinePhysicalNamespaceInspection['limits'];
181
+
168
182
  type TSmartDbCommands = {
183
+ inspectOfflinePhysicalNamespaces: {
184
+ params: TOfflinePhysicalNamespaceInspectionCommandParams;
185
+ result: ILocalSmartDbOfflinePhysicalNamespaceInspectionResult;
186
+ };
169
187
  inspectOfflineStringValue: {
170
188
  params: ILocalSmartDbOfflineStringValueInspection;
171
189
  result: TLocalSmartDbOfflineStringValueInspectionResult;
@@ -191,6 +209,10 @@ type TSmartDbCommands = {
191
209
  params: ISmartDbEnsureDatabaseTenantInput;
192
210
  result: ISmartDbEnsureDatabaseTenantResult;
193
211
  };
212
+ attestDatabaseTenant: {
213
+ params: ISmartDbAttestDatabaseTenantInput;
214
+ result: ISmartDbAttestDatabaseTenantResult;
215
+ };
194
216
  deleteDatabaseTenant: {
195
217
  params: ISmartDbDeleteDatabaseTenantInput;
196
218
  result: ISmartDbDeleteDatabaseTenantResult;
@@ -535,6 +557,28 @@ export class RustDbBridge extends EventEmitter {
535
557
 
536
558
  // --- Convenience methods for each management command ---
537
559
 
560
+ public async inspectOfflinePhysicalNamespaces(
561
+ inputArg: ILocalSmartDbOfflinePhysicalNamespaceInspection,
562
+ optionsArg?: ISmartDbManagementOperationOptions,
563
+ ): Promise<ILocalSmartDbOfflinePhysicalNamespaceInspectionResult> {
564
+ const input = normalizeLocalSmartDbOfflinePhysicalNamespaceInspectionInput(inputArg);
565
+ validateSmartDbManagementOperationOptions(optionsArg);
566
+ const params: TOfflinePhysicalNamespaceInspectionCommandParams = {
567
+ folderPath: input.folderPath,
568
+ ...input.limits,
569
+ };
570
+ try {
571
+ return await this.bridge.sendCommand(
572
+ 'inspectOfflinePhysicalNamespaces',
573
+ params,
574
+ optionsArg,
575
+ ) as ILocalSmartDbOfflinePhysicalNamespaceInspectionResult;
576
+ } catch (error) {
577
+ await this.awaitManagementOperationTermination(error);
578
+ throw error;
579
+ }
580
+ }
581
+
538
582
  public async inspectOfflineStringValue(
539
583
  inputArg: ILocalSmartDbOfflineStringValueInspection,
540
584
  optionsArg?: ISmartDbManagementOperationOptions,
@@ -647,6 +691,22 @@ export class RustDbBridge extends EventEmitter {
647
691
  }
648
692
  }
649
693
 
694
+ public async attestDatabaseTenant(
695
+ params: ISmartDbAttestDatabaseTenantInput,
696
+ optionsArg?: ISmartDbManagementOperationOptions,
697
+ ): Promise<ISmartDbAttestDatabaseTenantResult> {
698
+ try {
699
+ return await this.bridge.sendCommand(
700
+ 'attestDatabaseTenant',
701
+ params,
702
+ optionsArg,
703
+ ) as ISmartDbAttestDatabaseTenantResult;
704
+ } catch (error) {
705
+ await this.awaitManagementOperationTermination(error);
706
+ throw normalizeSmartDbResourceFenceError(error);
707
+ }
708
+ }
709
+
650
710
  public async allocateDatabaseTenant(
651
711
  params: ISmartDbAllocateDatabaseTenantInput,
652
712
  optionsArg?: ISmartDbManagementOperationOptions,
@@ -20,6 +20,8 @@ import type {
20
20
  import type {
21
21
  ISmartDbHealth,
22
22
  ISmartDbDatabaseTenantInput,
23
+ ISmartDbAttestDatabaseTenantInput,
24
+ ISmartDbAttestDatabaseTenantResult,
23
25
  ISmartDbEnsureDatabaseTenantInput,
24
26
  ISmartDbEnsureDatabaseTenantResult,
25
27
  ISmartDbAllocateDatabaseTenantInput,
@@ -455,6 +457,16 @@ export class SmartdbServer {
455
457
  return this.withTenantMongoUri(descriptor, params.password);
456
458
  }
457
459
 
460
+ /** Verify an exact tenant credential without changing provider state. */
461
+ async attestDatabaseTenant(
462
+ params: ISmartDbAttestDatabaseTenantInput,
463
+ optionsArg?: ISmartDbManagementOperationOptions,
464
+ ): Promise<ISmartDbAttestDatabaseTenantResult> {
465
+ return this.runDatabaseManagementOperation(
466
+ () => this.bridge.attestDatabaseTenant(params, optionsArg),
467
+ );
468
+ }
469
+
458
470
  /**
459
471
  * Delete a tenant database and its tenant user(s).
460
472
  */
@@ -45,6 +45,33 @@ export type TLocalSmartDbOfflineStringValueInspectionResult =
45
45
  | { status: 'not-found' }
46
46
  | { status: 'found'; value: string };
47
47
 
48
+ export interface ILocalSmartDbOfflinePhysicalNamespaceInspectionLimits {
49
+ maximumEntries: number;
50
+ maximumDatabases: number;
51
+ maximumCollections: number;
52
+ maximumIndexes: number;
53
+ maximumDataFileBytes: number;
54
+ maximumWalFileBytes: number;
55
+ maximumIndexFileBytes: number;
56
+ maximumTotalFileBytes: number;
57
+ maximumRecordsPerCollection: number;
58
+ maximumRecordBytes: number;
59
+ maximumResultBytes: number;
60
+ }
61
+
62
+ export interface ILocalSmartDbOfflinePhysicalNamespaceInspection {
63
+ folderPath: string;
64
+ limits: ILocalSmartDbOfflinePhysicalNamespaceInspectionLimits;
65
+ }
66
+
67
+ export interface ILocalSmartDbOfflinePhysicalNamespaceInspectionResult {
68
+ schemaVersion: 1;
69
+ databases: Array<{
70
+ name: string;
71
+ collections: string[];
72
+ }>;
73
+ }
74
+
48
75
  export const smartDbResourceFenceErrorCodes = [
49
76
  'EFENCE_REQUIRED',
50
77
  'EFENCE_STALE',
@@ -302,6 +329,17 @@ export interface ISmartDbEnsureDatabaseTenantInput
302
329
  allocation?: ISmartDbDatabaseAllocationIdentity;
303
330
  }
304
331
 
332
+ export interface ISmartDbAttestDatabaseTenantInput {
333
+ databaseName: string;
334
+ username: string;
335
+ roles: string[];
336
+ password: string;
337
+ }
338
+
339
+ export interface ISmartDbAttestDatabaseTenantResult {
340
+ matches: boolean;
341
+ }
342
+
305
343
  export interface ISmartDbAllocateDatabaseTenantInput
306
344
  extends ISmartDbDatabaseTenantInput {
307
345
  expectedAbsent: true;