@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.
Files changed (81) hide show
  1. package/.smartconfig.json +107 -0
  2. package/dist_rust/rustdb_linux_amd64 +0 -0
  3. package/dist_rust/rustdb_linux_amd64.tsrust-build.json +14 -0
  4. package/dist_rust/rustdb_linux_arm64 +0 -0
  5. package/dist_rust/rustdb_linux_arm64.tsrust-build.json +14 -0
  6. package/dist_rust/rustdb_macos_amd64 +0 -0
  7. package/dist_rust/rustdb_macos_amd64.tsrust-build.json +14 -0
  8. package/dist_rust/rustdb_macos_arm64 +0 -0
  9. package/dist_rust/rustdb_macos_arm64.tsrust-build.json +14 -0
  10. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  11. package/dist_ts/00_commitinfo_data.js +9 -0
  12. package/dist_ts/index.d.ts +8 -0
  13. package/dist_ts/index.js +10 -0
  14. package/dist_ts/ts_local/classes.localnosqldb.d.ts +119 -0
  15. package/dist_ts/ts_local/classes.localnosqldb.js +386 -0
  16. package/dist_ts/ts_local/index.d.ts +5 -0
  17. package/dist_ts/ts_local/index.js +3 -0
  18. package/dist_ts/ts_local/plugins.d.ts +1 -0
  19. package/dist_ts/ts_local/plugins.js +3 -0
  20. package/dist_ts/ts_nosqldb/classes.storagerehearsal.d.ts +19 -0
  21. package/dist_ts/ts_nosqldb/classes.storagerehearsal.js +122 -0
  22. package/dist_ts/ts_nosqldb/file-storage-options.d.ts +13 -0
  23. package/dist_ts/ts_nosqldb/file-storage-options.js +24 -0
  24. package/dist_ts/ts_nosqldb/file-storage-startup.d.ts +3 -0
  25. package/dist_ts/ts_nosqldb/file-storage-startup.js +6 -0
  26. package/dist_ts/ts_nosqldb/index.d.ts +14 -0
  27. package/dist_ts/ts_nosqldb/index.js +11 -0
  28. package/dist_ts/ts_nosqldb/offline-inspection.d.ts +72 -0
  29. package/dist_ts/ts_nosqldb/offline-inspection.js +282 -0
  30. package/dist_ts/ts_nosqldb/plugins.d.ts +7 -0
  31. package/dist_ts/ts_nosqldb/plugins.js +10 -0
  32. package/dist_ts/ts_nosqldb/resource-fencing.d.ts +39 -0
  33. package/dist_ts/ts_nosqldb/resource-fencing.js +271 -0
  34. package/dist_ts/ts_nosqldb/rust-db-bridge.d.ts +213 -0
  35. package/dist_ts/ts_nosqldb/rust-db-bridge.js +439 -0
  36. package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.d.ts +204 -0
  37. package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.js +456 -0
  38. package/dist_ts/ts_nosqldb/server/index.d.ts +3 -0
  39. package/dist_ts/ts_nosqldb/server/index.js +3 -0
  40. package/dist_ts/ts_nosqldb/service-types.d.ts +341 -0
  41. package/dist_ts/ts_nosqldb/service-types.js +62 -0
  42. package/dist_ts/ts_nosqldb/storage-rehearsal.d.ts +103 -0
  43. package/dist_ts/ts_nosqldb/storage-rehearsal.js +131 -0
  44. package/dist_ts/ts_nosqldb/storage-root-relocation.d.ts +27 -0
  45. package/dist_ts/ts_nosqldb/storage-root-relocation.js +211 -0
  46. package/dist_ts_debugserver/bundled.d.ts +4 -0
  47. package/dist_ts_debugserver/bundled.js +16 -0
  48. package/dist_ts_debugserver/classes.debugserver.d.ts +36 -0
  49. package/dist_ts_debugserver/classes.debugserver.js +95 -0
  50. package/dist_ts_debugserver/index.d.ts +2 -0
  51. package/dist_ts_debugserver/index.js +2 -0
  52. package/dist_ts_debugserver/plugins.d.ts +2 -0
  53. package/dist_ts_debugserver/plugins.js +3 -0
  54. package/dist_ts_debugui/index.d.ts +2 -0
  55. package/dist_ts_debugui/index.js +2 -0
  56. package/dist_ts_debugui/nosqldb-debugui.d.ts +62 -0
  57. package/dist_ts_debugui/nosqldb-debugui.js +1132 -0
  58. package/dist_ts_debugui/plugins.d.ts +1 -0
  59. package/dist_ts_debugui/plugins.js +2 -0
  60. package/license +21 -0
  61. package/package.json +82 -0
  62. package/readme.md +1733 -0
  63. package/third-party-notices.md +925 -0
  64. package/ts/00_commitinfo_data.ts +8 -0
  65. package/ts/index.ts +85 -0
  66. package/ts/ts_local/classes.localnosqldb.ts +500 -0
  67. package/ts/ts_local/index.ts +19 -0
  68. package/ts/ts_local/plugins.ts +1 -0
  69. package/ts/ts_nosqldb/classes.storagerehearsal.ts +109 -0
  70. package/ts/ts_nosqldb/file-storage-options.ts +33 -0
  71. package/ts/ts_nosqldb/file-storage-startup.ts +9 -0
  72. package/ts/ts_nosqldb/index.ts +104 -0
  73. package/ts/ts_nosqldb/offline-inspection.ts +375 -0
  74. package/ts/ts_nosqldb/plugins.ts +12 -0
  75. package/ts/ts_nosqldb/resource-fencing.ts +368 -0
  76. package/ts/ts_nosqldb/rust-db-bridge.ts +964 -0
  77. package/ts/ts_nosqldb/server/classes.nosqldbserver.ts +696 -0
  78. package/ts/ts_nosqldb/server/index.ts +4 -0
  79. package/ts/ts_nosqldb/service-types.ts +484 -0
  80. package/ts/ts_nosqldb/storage-rehearsal.ts +215 -0
  81. package/ts/ts_nosqldb/storage-root-relocation.ts +277 -0
@@ -0,0 +1,484 @@
1
+ export interface INoSqlDbHealth {
2
+ running: boolean;
3
+ storage?: 'memory' | 'file';
4
+ fileStorageEngine?: 'mvcc';
5
+ storagePurpose?: 'production' | 'rehearsal';
6
+ storageRehearsalId?: string | null;
7
+ storagePath?: string;
8
+ resourceFencingVersion?: 1;
9
+ resourceFencingSupported?: boolean;
10
+ resourceFencingRequiresDrain?: true;
11
+ allocationFencingVersion?: 1;
12
+ allocationFencingSupported?: boolean;
13
+ allocationFencingRequiresDrain?: true;
14
+ databaseAllocationReadAccessGrantVersion?: 1;
15
+ databaseAllocationReadAccessGrantSupported?: true;
16
+ publicationHoldVersion: 1;
17
+ publicationHoldSupported: boolean;
18
+ publicationHoldRequiresExternalDrain: boolean;
19
+ authEnabled?: boolean;
20
+ authUsers?: number;
21
+ usersPathConfigured?: boolean;
22
+ databaseCount: number;
23
+ collectionCount: number;
24
+ uptimeSeconds?: number;
25
+ }
26
+
27
+ /** Per-operation admission bounds for read-only stopped native storage inspection. */
28
+ export interface ILocalNoSqlDbOfflineInspectionLimits {
29
+ maximumEntries: number;
30
+ maximumTotalFileBytes: number;
31
+ maximumRecords: number;
32
+ maximumRecordBytes: number;
33
+ maximumResultBytes: number;
34
+ }
35
+
36
+ export interface ILocalNoSqlDbOfflineStringValueInspection {
37
+ folderPath: string;
38
+ databaseName: string;
39
+ collectionName: string;
40
+ match: {
41
+ field: string;
42
+ value: string;
43
+ };
44
+ valueField: string;
45
+ limits: ILocalNoSqlDbOfflineInspectionLimits;
46
+ }
47
+
48
+ export type TLocalNoSqlDbOfflineStringValueInspectionResult =
49
+ | { status: 'not-found' }
50
+ | { status: 'found'; value: string };
51
+
52
+ export interface ILocalNoSqlDbOfflinePhysicalNamespaceInspectionLimits {
53
+ maximumEntries: number;
54
+ maximumDatabases: number;
55
+ maximumCollections: number;
56
+ maximumIndexes: number;
57
+ maximumTotalFileBytes: number;
58
+ maximumRecordsPerCollection: number;
59
+ maximumRecordBytes: number;
60
+ maximumResultBytes: number;
61
+ }
62
+
63
+ export interface ILocalNoSqlDbOfflinePhysicalNamespaceInspection {
64
+ folderPath: string;
65
+ limits: ILocalNoSqlDbOfflinePhysicalNamespaceInspectionLimits;
66
+ }
67
+
68
+ export interface ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult {
69
+ schemaVersion: 1;
70
+ databases: Array<{
71
+ name: string;
72
+ collections: string[];
73
+ }>;
74
+ }
75
+
76
+ export const noSqlDbResourceFenceErrorCodes = [
77
+ 'EFENCE_REQUIRED',
78
+ 'EFENCE_STALE',
79
+ 'EFENCE_SCOPE_MISMATCH',
80
+ 'EFENCE_UNSUPPORTED',
81
+ 'EFENCE_MUTATION_MISMATCH',
82
+ 'EFENCE_OWNERSHIP_MISMATCH',
83
+ 'EFENCE_STATE_CORRUPT',
84
+ 'EFENCE_RECEIPT_LIMIT',
85
+ 'EFENCE_BUSY',
86
+ 'EFENCE_PUBLICATION_HELD',
87
+ 'EFENCE_CAPABILITY_MISMATCH',
88
+ 'EFENCE_NOT_HELD',
89
+ 'EALLOCATION_EXPECTED_ABSENT_CONFLICT',
90
+ 'EALLOCATION_REQUIRED',
91
+ 'EALLOCATION_MISMATCH',
92
+ 'EALLOCATION_GENERATION_EXHAUSTED',
93
+ 'EALLOCATION_DEPROVISIONED',
94
+ ] as const;
95
+
96
+ export type TNoSqlDbResourceFenceErrorCode =
97
+ (typeof noSqlDbResourceFenceErrorCodes)[number];
98
+
99
+ export interface INoSqlDbResourceFence {
100
+ version: 1;
101
+ scopeId: string;
102
+ token: number;
103
+ mutationId: string;
104
+ payloadSha256: string;
105
+ }
106
+
107
+ export interface INoSqlDbResourceFenceReceipt
108
+ extends INoSqlDbResourceFence {
109
+ kind:
110
+ | 'smartdb.database.replace.v1'
111
+ | 'smartdb.database.delete.v1'
112
+ | 'smartdb.database.ensure.v1'
113
+ | 'smartdb.database.allocate.v1';
114
+ /** Digest of the canonical payload interpretation derived by NoSQLDB. */
115
+ effectivePayloadSha256: string;
116
+ provider?: 'smartdb';
117
+ resourceIdSha256?: string;
118
+ holdPublication?: true;
119
+ publicationState?: 'held' | 'released';
120
+ bindingSha256?: string;
121
+ resultSha256?: string;
122
+ receiptSha256?: string;
123
+ publicationCapabilitySha256?: string;
124
+ /** Present only while publication is held; never returned after release. */
125
+ publicationCapability?: string;
126
+ allocation?: INoSqlDbDatabaseAllocationIdentity;
127
+ }
128
+
129
+ export interface INoSqlDbDatabaseAllocationIdentity {
130
+ version: 1;
131
+ provider: 'smartdb';
132
+ state: 'active';
133
+ databaseName: string;
134
+ username: string;
135
+ allocationId: string;
136
+ generation: number;
137
+ principalId: string;
138
+ resourceIdSha256: string;
139
+ bindingSha256: string;
140
+ receiptSha256: string;
141
+ }
142
+
143
+ export interface INoSqlDbDatabaseAllocationLifecycle {
144
+ version: 1;
145
+ provider: 'smartdb';
146
+ state: 'allocating' | 'active' | 'deprovisioning' | 'deprovisioned';
147
+ databaseName: string;
148
+ username: string;
149
+ allocationId: string;
150
+ generation: number;
151
+ principalId: string;
152
+ resourceIdSha256: string;
153
+ bindingSha256: string;
154
+ receiptSha256: string;
155
+ }
156
+
157
+ export interface INoSqlDbDatabaseAllocationReadAccessGrantIdentity {
158
+ version: 1;
159
+ grantId: string;
160
+ principalId: string;
161
+ generation: number;
162
+ }
163
+
164
+ export interface INoSqlDbIssueDatabaseAllocationReadAccessGrantInput {
165
+ databaseName: string;
166
+ grantId: string;
167
+ username: string;
168
+ password: string;
169
+ allocation: INoSqlDbDatabaseAllocationIdentity;
170
+ }
171
+
172
+ export interface INoSqlDbIssueDatabaseAllocationReadAccessGrantResult {
173
+ databaseName: string;
174
+ username: string;
175
+ roles: ['read'];
176
+ authSource: string;
177
+ mongodbUri?: string;
178
+ allocation: INoSqlDbDatabaseAllocationIdentity;
179
+ grant: INoSqlDbDatabaseAllocationReadAccessGrantIdentity;
180
+ issuedAt: number;
181
+ expiresAt: number;
182
+ }
183
+
184
+ export interface INoSqlDbRevokeDatabaseAllocationReadAccessGrantInput {
185
+ databaseName: string;
186
+ grantId: string;
187
+ allocation: INoSqlDbDatabaseAllocationIdentity;
188
+ }
189
+
190
+ export interface INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult {
191
+ databaseName: string;
192
+ grantId: string;
193
+ allocation: INoSqlDbDatabaseAllocationIdentity;
194
+ revoked: true;
195
+ }
196
+
197
+ export const noSqlDbAllocationReadAccessGrantErrorCodes = [
198
+ 'EGRANT_INVALID_REQUEST',
199
+ 'EGRANT_CREDENTIAL_MISMATCH',
200
+ 'EGRANT_CONFLICT',
201
+ 'EGRANT_REPLAY_CAPACITY',
202
+ 'EGRANT_STATE_UNAVAILABLE',
203
+ ] as const;
204
+
205
+ export type TNoSqlDbAllocationReadAccessGrantErrorCode =
206
+ (typeof noSqlDbAllocationReadAccessGrantErrorCodes)[number];
207
+
208
+ export class NoSqlDbAllocationReadAccessGrantError extends Error {
209
+ public readonly code: TNoSqlDbAllocationReadAccessGrantErrorCode;
210
+
211
+ public constructor(
212
+ codeArg: TNoSqlDbAllocationReadAccessGrantErrorCode,
213
+ messageArg: string,
214
+ causeArg?: unknown,
215
+ ) {
216
+ super(`${codeArg}: ${messageArg}`, { cause: causeArg });
217
+ this.name = 'NoSqlDbAllocationReadAccessGrantError';
218
+ this.code = codeArg;
219
+ }
220
+ }
221
+
222
+ export const normalizeNoSqlDbAllocationReadAccessGrantError = (
223
+ errorArg: unknown,
224
+ codeArg?: unknown,
225
+ ): Error => {
226
+ const error =
227
+ errorArg instanceof Error
228
+ ? errorArg
229
+ : new Error(String(errorArg));
230
+ if (
231
+ typeof codeArg !== 'string'
232
+ || !noSqlDbAllocationReadAccessGrantErrorCodes.includes(
233
+ codeArg as TNoSqlDbAllocationReadAccessGrantErrorCode,
234
+ )
235
+ ) {
236
+ return error;
237
+ }
238
+ return new NoSqlDbAllocationReadAccessGrantError(
239
+ codeArg as TNoSqlDbAllocationReadAccessGrantErrorCode,
240
+ error.message,
241
+ error,
242
+ );
243
+ };
244
+
245
+ export interface INoSqlDbHeldPublicationReceipt
246
+ extends INoSqlDbResourceFenceReceipt {
247
+ provider: 'smartdb';
248
+ resourceIdSha256: string;
249
+ holdPublication: true;
250
+ publicationState: 'held';
251
+ bindingSha256: string;
252
+ resultSha256: string;
253
+ receiptSha256: string;
254
+ publicationCapabilitySha256: string;
255
+ publicationCapability: string;
256
+ }
257
+
258
+ export interface INoSqlDbCommitDatabasePublicationInput {
259
+ databaseName: string;
260
+ resourceFence: INoSqlDbHeldPublicationReceipt;
261
+ }
262
+
263
+ /**
264
+ * Cancellation and deadline ownership for one durable database-management
265
+ * operation. Aborting or timing out is fail-stop: NoSQLDB terminates the Rust
266
+ * engine so the operation cannot continue after the caller has released it.
267
+ */
268
+ export interface INoSqlDbManagementOperationOptions {
269
+ signal?: AbortSignal;
270
+ timeoutMs?: number;
271
+ }
272
+
273
+ export interface INoSqlDbDatabaseResourceFenceState {
274
+ version: 1;
275
+ databaseName: string;
276
+ resourceIdSha256: string;
277
+ scopeId: string;
278
+ highestToken: number;
279
+ publicationState: 'requested' | 'publishing' | 'held' | 'released';
280
+ allocation?: INoSqlDbDatabaseAllocationLifecycle;
281
+ }
282
+
283
+ export class NoSqlDbResourceFenceError extends Error {
284
+ public readonly code: TNoSqlDbResourceFenceErrorCode;
285
+
286
+ public constructor(
287
+ codeArg: TNoSqlDbResourceFenceErrorCode,
288
+ messageArg: string,
289
+ causeArg?: unknown,
290
+ ) {
291
+ super(`${codeArg}: ${messageArg}`, { cause: causeArg });
292
+ this.name = 'NoSqlDbResourceFenceError';
293
+ this.code = codeArg;
294
+ }
295
+ }
296
+
297
+ export const normalizeNoSqlDbResourceFenceError = (
298
+ errorArg: unknown,
299
+ ): Error => {
300
+ const error =
301
+ errorArg instanceof Error
302
+ ? errorArg
303
+ : new Error(String(errorArg));
304
+ const match = /^((?:EFENCE|EALLOCATION)_[A-Z_]+):\s*(.*)$/s.exec(error.message);
305
+ if (
306
+ !match ||
307
+ !noSqlDbResourceFenceErrorCodes.includes(
308
+ match[1] as TNoSqlDbResourceFenceErrorCode,
309
+ )
310
+ ) {
311
+ return error;
312
+ }
313
+ return new NoSqlDbResourceFenceError(
314
+ match[1] as TNoSqlDbResourceFenceErrorCode,
315
+ match[2],
316
+ error,
317
+ );
318
+ };
319
+
320
+ export interface INoSqlDbDatabaseTenantInput {
321
+ databaseName: string;
322
+ username: string;
323
+ password: string;
324
+ roles?: string[];
325
+ }
326
+
327
+ export interface INoSqlDbEnsureDatabaseTenantInput
328
+ extends INoSqlDbDatabaseTenantInput {
329
+ fence: INoSqlDbResourceFence;
330
+ allocation?: INoSqlDbDatabaseAllocationIdentity;
331
+ }
332
+
333
+ export interface INoSqlDbAttestDatabaseTenantInput {
334
+ databaseName: string;
335
+ username: string;
336
+ roles: string[];
337
+ password: string;
338
+ }
339
+
340
+ export interface INoSqlDbAttestDatabaseTenantResult {
341
+ matches: boolean;
342
+ }
343
+
344
+ export interface INoSqlDbAllocateDatabaseTenantInput
345
+ extends INoSqlDbDatabaseTenantInput {
346
+ expectedAbsent: true;
347
+ fence: INoSqlDbResourceFence;
348
+ }
349
+
350
+ export interface INoSqlDbDeleteDatabaseTenantInput {
351
+ databaseName: string;
352
+ /**
353
+ * Exact tenant user to revoke. For a fenced deletion this identity is
354
+ * included in the durable mutation and is removed idempotently after the
355
+ * database deletion is durable. Omitting it from a fenced deletion
356
+ * preserves auth users; an unfenced omission retains the legacy behavior
357
+ * of deleting every user for the database. If the database exists, a
358
+ * fenced deletion fails closed unless this exact user currently owns it.
359
+ */
360
+ username?: string;
361
+ fence?: INoSqlDbResourceFence;
362
+ allocation?: INoSqlDbDatabaseAllocationIdentity;
363
+ /** Keep the database data plane closed until commitDatabasePublication. */
364
+ holdPublication?: boolean;
365
+ }
366
+
367
+ export interface INoSqlDbRotateDatabaseTenantPasswordInput {
368
+ username: string;
369
+ password: string;
370
+ }
371
+
372
+ export interface INoSqlDbDatabaseTenantDescriptor {
373
+ databaseName: string;
374
+ username: string;
375
+ roles: string[];
376
+ authSource: string;
377
+ mongodbUri?: string;
378
+ resourceFence?: INoSqlDbResourceFenceReceipt;
379
+ allocation?: INoSqlDbDatabaseAllocationIdentity;
380
+ }
381
+
382
+ export interface INoSqlDbEnsureDatabaseTenantResult
383
+ extends INoSqlDbDatabaseTenantDescriptor {
384
+ resourceFence: INoSqlDbResourceFenceReceipt;
385
+ }
386
+
387
+ export interface INoSqlDbAllocateDatabaseTenantResult
388
+ extends INoSqlDbDatabaseTenantDescriptor {
389
+ resourceFence: INoSqlDbResourceFenceReceipt;
390
+ allocation: INoSqlDbDatabaseAllocationIdentity;
391
+ }
392
+
393
+ export interface INoSqlDbDeleteDatabaseTenantResult {
394
+ databaseName: string;
395
+ /**
396
+ * Users removed during the completing attempt. This can be zero after
397
+ * crash recovery when the exact user was durably removed by the prior
398
+ * attempt.
399
+ */
400
+ deletedUsers: number;
401
+ databaseDropped: boolean;
402
+ /**
403
+ * Present for fenced calls. False means the bound exact username is now
404
+ * absent; true means no username was supplied and auth users were preserved.
405
+ */
406
+ usersPreserved?: boolean;
407
+ resourceFence?: INoSqlDbResourceFenceReceipt;
408
+ allocation?: INoSqlDbDatabaseAllocationIdentity;
409
+ }
410
+
411
+ export interface INoSqlDbDatabaseExportCollection {
412
+ name: string;
413
+ documents: Record<string, any>[];
414
+ indexes: Record<string, any>[];
415
+ }
416
+
417
+ export interface INoSqlDbDatabaseExportLimits {
418
+ maxEncodedBytes?: number;
419
+ maxCollections?: number;
420
+ maxDocuments?: number;
421
+ maxIndexes?: number;
422
+ }
423
+
424
+ export interface INoSqlDbExportDatabaseInput {
425
+ databaseName: string;
426
+ limits?: INoSqlDbDatabaseExportLimits;
427
+ }
428
+
429
+ export interface INoSqlDbDatabaseContentDigestLimits {
430
+ maxScannedBsonBytes?: number;
431
+ maxCollections?: number;
432
+ maxDocuments?: number;
433
+ maxIndexes?: number;
434
+ }
435
+
436
+ export interface INoSqlDbGetDatabaseContentDigestInput {
437
+ databaseName: string;
438
+ limits?: INoSqlDbDatabaseContentDigestLimits;
439
+ }
440
+
441
+ export interface INoSqlDbDatabaseContentDigest {
442
+ format: 'smartdb.database.content-digest.v1';
443
+ algorithm: 'sha256';
444
+ databaseName: string;
445
+ sha256: string;
446
+ scannedBsonBytes: number;
447
+ collections: number;
448
+ documents: number;
449
+ indexes: number;
450
+ }
451
+
452
+ export interface INoSqlDbDatabaseExport {
453
+ format: 'smartdb.database.export.v1';
454
+ databaseName: string;
455
+ exportedAtMs: number;
456
+ collections: INoSqlDbDatabaseExportCollection[];
457
+ }
458
+
459
+ export interface INoSqlDbImportDatabaseInput {
460
+ databaseName: string;
461
+ source: INoSqlDbDatabaseExport;
462
+ /**
463
+ * Exact exclusive tenant owner required for this replacement. When used
464
+ * with a fence, the username is included in the provider-derived mutation
465
+ * identity. Omission preserves the original version 1 replacement profile.
466
+ */
467
+ username?: string;
468
+ fence?: INoSqlDbResourceFence;
469
+ allocation?: INoSqlDbDatabaseAllocationIdentity;
470
+ /** Keep the database data plane closed until commitDatabasePublication. */
471
+ holdPublication?: boolean;
472
+ }
473
+
474
+ export interface INoSqlDbImportDatabaseResult {
475
+ databaseName: string;
476
+ collections: number;
477
+ documents: number;
478
+ resourceFence?: INoSqlDbResourceFenceReceipt;
479
+ allocation?: INoSqlDbDatabaseAllocationIdentity;
480
+ }
481
+
482
+ export type TNoSqlDbCommitDatabasePublicationResult =
483
+ | INoSqlDbImportDatabaseResult
484
+ | INoSqlDbDeleteDatabaseTenantResult;
@@ -0,0 +1,215 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { INoSqlDbServerOptions } from './server/classes.nosqldbserver.js';
3
+
4
+ export const noSqlDbStorageRehearsalErrorCodes = [
5
+ 'INVALID_REQUEST', 'UNSUPPORTED', 'BUSY', 'NOT_FOUND', 'DESTINATION_CONFLICT',
6
+ 'IDENTITY_MISMATCH', 'STATE_CORRUPT', 'SOURCE_CHANGED', 'CAPACITY', 'CANCELLED',
7
+ 'IO', 'RECOVERY_REQUIRED', 'BINARY_UNAVAILABLE',
8
+ ] as const;
9
+ export type TNoSqlDbStorageRehearsalErrorCode = typeof noSqlDbStorageRehearsalErrorCodes[number];
10
+
11
+ /** Errors deliberately contain neither persisted values nor nested engine errors. */
12
+ export class NoSqlDbStorageRehearsalError extends Error {
13
+ public constructor(public readonly code: TNoSqlDbStorageRehearsalErrorCode) {
14
+ super(`NoSQLDB storage rehearsal failed with code ${code}`);
15
+ this.name = 'NoSqlDbStorageRehearsalError';
16
+ }
17
+ }
18
+
19
+ export interface INoSqlDbStorageRehearsalOptions {
20
+ /** Exact executable, with SmartRust's exclusive non-mutating selection contract. */
21
+ binaryPath?: string;
22
+ }
23
+
24
+ export interface INoSqlDbStorageRehearsalInput {
25
+ /** Absolute canonical path of an already initialized, stopped source. */
26
+ sourceFolderPath: string;
27
+ /** Absent destination; its existing parent must be private and on the source filesystem. */
28
+ destinationFolderPath: string;
29
+ /** Optional previously attested provider identity. The engine always verifies source ownership and bindings. */
30
+ expectedSourceProviderRootId?: string;
31
+ /** Stable caller operation ID. Every retry must retain the same request. */
32
+ operationId: string;
33
+ limits?: {
34
+ /** Source files plus directories, including the root. Default/maximum: 100,000. */
35
+ maximumEntries: number;
36
+ /** Total source file bytes. Default/maximum: 1 TiB. */
37
+ maximumBytes: number;
38
+ };
39
+ }
40
+
41
+ /** Counts include retained historical records separately from live contents. */
42
+ export interface INoSqlDbStorageRehearsalPreservation {
43
+ files: number;
44
+ bytes: number;
45
+ databases: number;
46
+ collections: number;
47
+ documents: number;
48
+ indexes: number;
49
+ indexAliases: number;
50
+ users: number;
51
+ principalIdentities: number;
52
+ retiredDatabases: number;
53
+ retiredCollections: number;
54
+ retiredDocuments: number;
55
+ retiredIndexes: number;
56
+ retiredPrincipals: number;
57
+ allocationReplays: number;
58
+ resources: number;
59
+ receipts: number;
60
+ maximumFencingToken: string;
61
+ publicationHolds: number;
62
+ activeAllocations: number;
63
+ retiredAllocations: number;
64
+ sessions: number;
65
+ retiringSessions: number;
66
+ retryOutcomes: number;
67
+ transactionOutcomes: number;
68
+ obsoleteOutcomes: number;
69
+ records: number;
70
+ encodedRecordBytes: number;
71
+ }
72
+
73
+ /** Immutable creation evidence. Completed retries replay this receipt even after isolated writes. */
74
+ export interface INoSqlDbStorageRehearsalReceipt {
75
+ format: 'smartdb.storage-rehearsal.receipt.v2';
76
+ operationId: string;
77
+ requestSha256: string;
78
+ storageProfile: 'native-mvcc-authenticated-v1';
79
+ sourceFolderPath: string;
80
+ destinationFolderPath: string;
81
+ sourceProviderRootId: string;
82
+ destinationProviderRootId: string;
83
+ sourceStorageRootSha256: string;
84
+ sourceSnapshotSha256: string;
85
+ destinationDevice: string;
86
+ destinationInode: string;
87
+ groupId: string;
88
+ timelineId: string;
89
+ sourceLsn: string;
90
+ destinationLsn: string;
91
+ sourceRecordsSha256: string;
92
+ preservedRecordsSha256: string;
93
+ scramIterations: number;
94
+ preservation: INoSqlDbStorageRehearsalPreservation;
95
+ sourceUnchanged: true;
96
+ contentsAndIndexesPreserved: true;
97
+ namespacesPreserved: true;
98
+ authenticationPreserved: true;
99
+ historicalFencingPreserved: true;
100
+ outcomesPreserved: true;
101
+ filesystemBindingsVerified: true;
102
+ productionAuthority: false;
103
+ promotionSupported: false;
104
+ receiptSha256: string;
105
+ }
106
+
107
+ export type TNormalizedNoSqlDbStorageRehearsalInput = INoSqlDbStorageRehearsalInput & {
108
+ limits: NonNullable<INoSqlDbStorageRehearsalInput['limits']>;
109
+ };
110
+
111
+ const fail = (code: TNoSqlDbStorageRehearsalErrorCode = 'INVALID_REQUEST'): never => {
112
+ throw new NoSqlDbStorageRehearsalError(code);
113
+ };
114
+ const object = (value: unknown): value is Record<string, unknown> => {
115
+ try {
116
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
117
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return false;
118
+ return Reflect.ownKeys(value).every((key) => {
119
+ const property = Object.getOwnPropertyDescriptor(value, key);
120
+ return typeof key === 'string' && property?.enumerable === true && Object.hasOwn(property, 'value');
121
+ });
122
+ } catch { return false; }
123
+ };
124
+ const digest = (value: unknown): value is string => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
125
+ const identifier = (value: unknown): value is string => typeof value === 'string'
126
+ && /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$/.test(value);
127
+ const canonicalPath = (value: unknown): value is string => typeof value === 'string'
128
+ && value.startsWith('/') && value !== '/' && plugins.buffer.Buffer.byteLength(value) <= 4095
129
+ && !/[\p{Cc}\p{Cs}]/u.test(value) && !value.endsWith('/') && !value.includes('//')
130
+ && value.split('/').every((part) => part !== '.' && part !== '..' && !part.startsWith('.smartdb-rehearsal-'));
131
+ const integer = (value: unknown, minimum: number, maximum: number): value is number =>
132
+ typeof value === 'number' && Number.isSafeInteger(value) && value >= minimum && value <= maximum;
133
+ const exactKeys = (value: Record<string, unknown>, required: readonly string[], optional: readonly string[] = []) =>
134
+ required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
135
+ const sha256 = (domain: string, value: unknown) => plugins.crypto.createHash('sha256').update(domain).update(JSON.stringify(value)).digest('hex');
136
+
137
+ export const normalizeNoSqlDbStorageRehearsalOptions = (value: unknown): INoSqlDbStorageRehearsalOptions => {
138
+ try {
139
+ if (!object(value) || !exactKeys(value, [], ['binaryPath'])
140
+ || (value.binaryPath !== undefined && (typeof value.binaryPath !== 'string' || value.binaryPath.length === 0))) return fail();
141
+ return { binaryPath: value.binaryPath as string | undefined };
142
+ } catch { return fail(); }
143
+ };
144
+
145
+ export const normalizeNoSqlDbStorageRehearsalOperationOptions = (value: unknown): { timeoutMs: number; signal?: AbortSignal } => {
146
+ try {
147
+ if (value === undefined) return { timeoutMs: 3_600_000 };
148
+ if (!object(value) || !exactKeys(value, [], ['timeoutMs', 'signal'])
149
+ || (value.timeoutMs !== undefined && !integer(value.timeoutMs, 1, 86_400_000))
150
+ || (value.signal !== undefined && !(value.signal instanceof AbortSignal))) return fail();
151
+ return { timeoutMs: value.timeoutMs as number | undefined ?? 3_600_000, signal: value.signal as AbortSignal | undefined };
152
+ } catch { return fail(); }
153
+ };
154
+
155
+ export const normalizeNoSqlDbStorageRehearsalInput = (value: unknown): TNormalizedNoSqlDbStorageRehearsalInput => {
156
+ if (!object(value) || !exactKeys(value, ['sourceFolderPath', 'destinationFolderPath', 'operationId'], ['expectedSourceProviderRootId', 'limits'])
157
+ || !canonicalPath(value.sourceFolderPath) || !canonicalPath(value.destinationFolderPath)
158
+ || value.sourceFolderPath === value.destinationFolderPath
159
+ || value.sourceFolderPath.startsWith(`${value.destinationFolderPath}/`)
160
+ || value.destinationFolderPath.startsWith(`${value.sourceFolderPath}/`)
161
+ || (value.expectedSourceProviderRootId !== undefined && !digest(value.expectedSourceProviderRootId))
162
+ || !identifier(value.operationId)) return fail();
163
+ const limits = value.limits === undefined ? { maximumEntries: 100_000, maximumBytes: 1024 ** 4 } : value.limits;
164
+ if (!object(limits) || !exactKeys(limits, ['maximumEntries', 'maximumBytes'])
165
+ || !integer(limits.maximumEntries, 1, 100_000) || !integer(limits.maximumBytes, 1, 1024 ** 4)) return fail();
166
+ return {
167
+ sourceFolderPath: value.sourceFolderPath,
168
+ destinationFolderPath: value.destinationFolderPath,
169
+ ...(value.expectedSourceProviderRootId === undefined ? {} : { expectedSourceProviderRootId: value.expectedSourceProviderRootId as string }),
170
+ operationId: value.operationId,
171
+ limits: { maximumEntries: limits.maximumEntries, maximumBytes: limits.maximumBytes },
172
+ };
173
+ };
174
+
175
+ const preservationKeys = ['files', 'bytes', 'databases', 'collections', 'documents', 'indexes', 'indexAliases', 'users', 'principalIdentities', 'retiredDatabases', 'retiredCollections', 'retiredDocuments', 'retiredIndexes', 'retiredPrincipals', 'allocationReplays', 'resources', 'receipts', 'maximumFencingToken', 'publicationHolds', 'activeAllocations', 'retiredAllocations', 'sessions', 'retiringSessions', 'retryOutcomes', 'transactionOutcomes', 'obsoleteOutcomes', 'records', 'encodedRecordBytes'] as const;
176
+ const receiptKeys = ['format', 'operationId', 'requestSha256', 'storageProfile', 'sourceFolderPath', 'destinationFolderPath', 'sourceProviderRootId', 'destinationProviderRootId', 'sourceStorageRootSha256', 'sourceSnapshotSha256', 'destinationDevice', 'destinationInode', 'groupId', 'timelineId', 'sourceLsn', 'destinationLsn', 'sourceRecordsSha256', 'preservedRecordsSha256', 'scramIterations', 'preservation', 'sourceUnchanged', 'contentsAndIndexesPreserved', 'namespacesPreserved', 'authenticationPreserved', 'historicalFencingPreserved', 'outcomesPreserved', 'filesystemBindingsVerified', 'productionAuthority', 'promotionSupported', 'receiptSha256'] as const;
177
+ const unsigned = (value: unknown): value is string => typeof value === 'string'
178
+ && /^(0|[1-9][0-9]{0,19})$/.test(value) && BigInt(value) <= 0xffff_ffff_ffff_ffffn;
179
+
180
+ export const normalizeNoSqlDbStorageRehearsalReceipt = (value: unknown, input: TNormalizedNoSqlDbStorageRehearsalInput): INoSqlDbStorageRehearsalReceipt => {
181
+ if (!object(value) || !exactKeys(value, receiptKeys) || !object(value.preservation)
182
+ || !exactKeys(value.preservation, preservationKeys)
183
+ || !preservationKeys.every((key) => key === 'maximumFencingToken'
184
+ ? unsigned((value.preservation as Record<string, unknown>)[key])
185
+ : integer((value.preservation as Record<string, unknown>)[key], 0, Number.MAX_SAFE_INTEGER))
186
+ || value.format !== 'smartdb.storage-rehearsal.receipt.v2' || value.storageProfile !== 'native-mvcc-authenticated-v1'
187
+ || value.operationId !== input.operationId || value.sourceFolderPath !== input.sourceFolderPath
188
+ || value.destinationFolderPath !== input.destinationFolderPath
189
+ || (input.expectedSourceProviderRootId !== undefined && value.sourceProviderRootId !== input.expectedSourceProviderRootId)
190
+ || !['sourceProviderRootId', 'destinationProviderRootId', 'sourceSnapshotSha256', 'sourceRecordsSha256', 'preservedRecordsSha256', 'receiptSha256'].every((key) => digest(value[key]))
191
+ || value.sourceProviderRootId === value.destinationProviderRootId
192
+ || value.sourceStorageRootSha256 !== plugins.crypto.createHash('sha256').update(`${input.sourceFolderPath}/.__rustdb_mvcc/data`).digest('hex')
193
+ || value.requestSha256 !== sha256('smartdb.storage-rehearsal.request.v2\0', input)
194
+ || !integer(value.scramIterations, 4096, 0xffff_ffff)
195
+ || ![value.destinationDevice, value.destinationInode, value.sourceLsn, value.destinationLsn].every(unsigned)
196
+ || BigInt(value.sourceLsn as string) < 1n || BigInt(value.sourceLsn as string) + 1n !== BigInt(value.destinationLsn as string)
197
+ || !identifier(value.groupId) || !identifier(value.timelineId)
198
+ || !['sourceUnchanged', 'contentsAndIndexesPreserved', 'namespacesPreserved', 'authenticationPreserved', 'historicalFencingPreserved', 'outcomesPreserved', 'filesystemBindingsVerified'].every((key) => value[key] === true)
199
+ || value.productionAuthority !== false || value.promotionSupported !== false) return fail('STATE_CORRUPT');
200
+ const preservation = Object.fromEntries(preservationKeys.map((key) => [key, (value.preservation as Record<string, unknown>)[key]]));
201
+ const normalized = Object.fromEntries(receiptKeys.map((key) => [key,
202
+ key === 'preservation' ? preservation : key === 'receiptSha256' ? '' : value[key]]));
203
+ if (sha256('smartdb.storage-rehearsal.receipt.v2\0', normalized) !== value.receiptSha256) return fail('STATE_CORRUPT');
204
+ normalized.receiptSha256 = value.receiptSha256;
205
+ return normalized as unknown as INoSqlDbStorageRehearsalReceipt;
206
+ };
207
+
208
+ export const validateNoSqlDbStorageRehearsalStartup = (options: INoSqlDbServerOptions): void => {
209
+ if (options.storageRehearsalId === undefined) return;
210
+ if (!identifier(options.storageRehearsalId) || options.storage !== 'file'
211
+ || (options.fileStorageStartup !== undefined && options.fileStorageStartup !== 'current-format-only') || !canonicalPath(options.storagePath)
212
+ || !canonicalPath(options.socketPath) || options.socketPath.startsWith(`${options.storagePath}/`)
213
+ || options.auth?.enabled !== true || (options.auth.users?.length ?? 0) !== 0
214
+ || !integer(options.auth.scramIterations, 4096, 0xffff_ffff)) return fail();
215
+ };