@push.rocks/smartdb 5.0.1 → 5.0.2

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.
@@ -6,12 +6,15 @@ import * as os from 'os';
6
6
  import {
7
7
  RustDbBridge,
8
8
  SmartdbServer,
9
+ type ILocalSmartDbOfflinePhysicalNamespaceInspection,
10
+ type ILocalSmartDbOfflinePhysicalNamespaceInspectionResult,
9
11
  type ILocalSmartDbOfflineStringValueInspection,
10
12
  type ISmartDbManagementOperationOptions,
11
13
  type TLocalSmartDbOfflineStringValueInspectionResult,
12
14
  } from '../ts_smartdb/index.js';
13
15
  import { StorageMigrator } from '../ts_migration/index.js';
14
16
  import {
17
+ normalizeLocalSmartDbOfflinePhysicalNamespaceInspectionInput,
15
18
  normalizeLocalSmartDbOfflineInspectionInput,
16
19
  validateSmartDbManagementOperationOptions,
17
20
  } from '../ts_smartdb/offline-inspection.js';
@@ -74,19 +77,24 @@ export class LocalSmartDb {
74
77
  return new RustDbBridge();
75
78
  }
76
79
 
77
- /**
78
- * Inspect one exact string value while the owning LocalSmartDb engine is stopped.
79
- * This starts only the Rust management sidecar and never initializes storage.
80
- */
81
- public static async inspectOfflineStringValue(
82
- inputArg: ILocalSmartDbOfflineStringValueInspection,
83
- optionsArg?: ISmartDbManagementOperationOptions,
84
- ): Promise<TLocalSmartDbOfflineStringValueInspectionResult> {
85
- const validatedInput = normalizeLocalSmartDbOfflineInspectionInput(inputArg);
80
+ private static async runOfflineInspection<
81
+ TInput extends { folderPath: string },
82
+ TResult,
83
+ >(
84
+ inputArg: TInput,
85
+ optionsArg: ISmartDbManagementOperationOptions | undefined,
86
+ normalizeInputArg: (inputArg: TInput) => TInput,
87
+ inspectArg: (
88
+ bridgeArg: RustDbBridge,
89
+ inputArg: TInput,
90
+ optionsArg?: ISmartDbManagementOperationOptions,
91
+ ) => Promise<TResult>,
92
+ ): Promise<TResult> {
93
+ const validatedInput = normalizeInputArg(inputArg);
86
94
  validateSmartDbManagementOperationOptions(optionsArg);
87
95
  optionsArg?.signal?.throwIfAborted();
88
96
 
89
- const input = normalizeLocalSmartDbOfflineInspectionInput({
97
+ const input = normalizeInputArg({
90
98
  ...validatedInput,
91
99
  folderPath: path.resolve(validatedInput.folderPath),
92
100
  });
@@ -96,7 +104,7 @@ export class LocalSmartDb {
96
104
  const bridge = LocalSmartDb.createOfflineInspectionBridge();
97
105
  let operationFailed = false;
98
106
  let operationError: unknown;
99
- let result: TLocalSmartDbOfflineStringValueInspectionResult | undefined;
107
+ let result: TResult | undefined;
100
108
  try {
101
109
  let spawnOptions = optionsArg;
102
110
  if (deadline !== undefined) {
@@ -126,7 +134,7 @@ export class LocalSmartDb {
126
134
  timeoutMs: remainingMs,
127
135
  };
128
136
  }
129
- result = await bridge.inspectOfflineStringValue(input, commandOptions);
137
+ result = await inspectArg(bridge, input, commandOptions);
130
138
  } catch (error) {
131
139
  operationFailed = true;
132
140
  operationError = error;
@@ -147,7 +155,41 @@ export class LocalSmartDb {
147
155
  if (operationFailed) {
148
156
  throw operationError;
149
157
  }
150
- return result as TLocalSmartDbOfflineStringValueInspectionResult;
158
+ return result as TResult;
159
+ }
160
+
161
+ /**
162
+ * Inspect physical database and collection names while the owning engine is stopped.
163
+ * This starts only the Rust management sidecar and never initializes storage.
164
+ */
165
+ public static async inspectOfflinePhysicalNamespaces(
166
+ inputArg: ILocalSmartDbOfflinePhysicalNamespaceInspection,
167
+ optionsArg?: ISmartDbManagementOperationOptions,
168
+ ): Promise<ILocalSmartDbOfflinePhysicalNamespaceInspectionResult> {
169
+ return LocalSmartDb.runOfflineInspection(
170
+ inputArg,
171
+ optionsArg,
172
+ normalizeLocalSmartDbOfflinePhysicalNamespaceInspectionInput,
173
+ (bridgeArg, input, commandOptions) =>
174
+ bridgeArg.inspectOfflinePhysicalNamespaces(input, commandOptions),
175
+ );
176
+ }
177
+
178
+ /**
179
+ * Inspect one exact string value while the owning LocalSmartDb engine is stopped.
180
+ * This starts only the Rust management sidecar and never initializes storage.
181
+ */
182
+ public static async inspectOfflineStringValue(
183
+ inputArg: ILocalSmartDbOfflineStringValueInspection,
184
+ optionsArg?: ISmartDbManagementOperationOptions,
185
+ ): Promise<TLocalSmartDbOfflineStringValueInspectionResult> {
186
+ return LocalSmartDb.runOfflineInspection(
187
+ inputArg,
188
+ optionsArg,
189
+ normalizeLocalSmartDbOfflineInspectionInput,
190
+ (bridgeArg, input, commandOptions) =>
191
+ bridgeArg.inspectOfflineStringValue(input, commandOptions),
192
+ );
151
193
  }
152
194
 
153
195
  /**
@@ -2,6 +2,9 @@ export { LocalSmartDb } from './classes.localsmartdb.js';
2
2
  export type { ILocalSmartDbOptions, ILocalSmartDbConnectionInfo } from './classes.localsmartdb.js';
3
3
  export type {
4
4
  ILocalSmartDbOfflineInspectionLimits,
5
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionLimits,
6
+ ILocalSmartDbOfflinePhysicalNamespaceInspection,
7
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionResult,
5
8
  ILocalSmartDbOfflineStringValueInspection,
6
9
  TLocalSmartDbOfflineStringValueInspectionResult,
7
10
  } from '../ts_smartdb/service-types.js';
@@ -44,6 +44,9 @@ export type {
44
44
  ISmartDbRevokeDatabaseAllocationReadAccessGrantInput,
45
45
  ISmartDbRevokeDatabaseAllocationReadAccessGrantResult,
46
46
  ILocalSmartDbOfflineInspectionLimits,
47
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionLimits,
48
+ ILocalSmartDbOfflinePhysicalNamespaceInspection,
49
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionResult,
47
50
  ILocalSmartDbOfflineStringValueInspection,
48
51
  TLocalSmartDbOfflineStringValueInspectionResult,
49
52
  TSmartDbCommitDatabasePublicationResult,
@@ -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,6 +7,7 @@ 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';
@@ -36,6 +37,8 @@ import type {
36
37
  ISmartDbCommitDatabasePublicationInput,
37
38
  ISmartDbManagementOperationOptions,
38
39
  ISmartDbDatabaseResourceFenceState,
40
+ ILocalSmartDbOfflinePhysicalNamespaceInspection,
41
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionResult,
39
42
  ILocalSmartDbOfflineStringValueInspection,
40
43
  TLocalSmartDbOfflineStringValueInspectionResult,
41
44
  TSmartDbCommitDatabasePublicationResult,
@@ -68,6 +71,9 @@ export type {
68
71
  ISmartDbManagementOperationOptions,
69
72
  ISmartDbDatabaseResourceFenceState,
70
73
  ILocalSmartDbOfflineInspectionLimits,
74
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionLimits,
75
+ ILocalSmartDbOfflinePhysicalNamespaceInspection,
76
+ ILocalSmartDbOfflinePhysicalNamespaceInspectionResult,
71
77
  ILocalSmartDbOfflineStringValueInspection,
72
78
  TLocalSmartDbOfflineStringValueInspectionResult,
73
79
  TSmartDbCommitDatabasePublicationResult,
@@ -169,7 +175,15 @@ export interface ISmartDbMetrics {
169
175
  /**
170
176
  * Type-safe command definitions for the RustDb IPC protocol.
171
177
  */
178
+ type TOfflinePhysicalNamespaceInspectionCommandParams = {
179
+ folderPath: string;
180
+ } & ILocalSmartDbOfflinePhysicalNamespaceInspection['limits'];
181
+
172
182
  type TSmartDbCommands = {
183
+ inspectOfflinePhysicalNamespaces: {
184
+ params: TOfflinePhysicalNamespaceInspectionCommandParams;
185
+ result: ILocalSmartDbOfflinePhysicalNamespaceInspectionResult;
186
+ };
173
187
  inspectOfflineStringValue: {
174
188
  params: ILocalSmartDbOfflineStringValueInspection;
175
189
  result: TLocalSmartDbOfflineStringValueInspectionResult;
@@ -543,6 +557,28 @@ export class RustDbBridge extends EventEmitter {
543
557
 
544
558
  // --- Convenience methods for each management command ---
545
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
+
546
582
  public async inspectOfflineStringValue(
547
583
  inputArg: ILocalSmartDbOfflineStringValueInspection,
548
584
  optionsArg?: ISmartDbManagementOperationOptions,
@@ -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',