@lossless.org/nosqldb 10.0.0 → 10.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.
- package/dist_rust/rustdb_linux_amd64 +0 -0
- package/dist_rust/rustdb_linux_amd64.tsrust-build.json +4 -4
- package/dist_rust/rustdb_linux_amd64_musl +0 -0
- package/dist_rust/rustdb_linux_amd64_musl.tsrust-build.json +4 -4
- package/dist_rust/rustdb_linux_arm64 +0 -0
- package/dist_rust/rustdb_linux_arm64.tsrust-build.json +4 -4
- package/dist_rust/rustdb_linux_arm64_musl +0 -0
- package/dist_rust/rustdb_linux_arm64_musl.tsrust-build.json +4 -4
- package/dist_rust/rustdb_macos_amd64 +0 -0
- package/dist_rust/rustdb_macos_amd64.tsrust-build.json +4 -4
- package/dist_rust/rustdb_macos_arm64 +0 -0
- package/dist_rust/rustdb_macos_arm64.tsrust-build.json +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/ts_local/classes.localnosqldb.d.ts +15 -5
- package/dist_ts/ts_local/classes.localnosqldb.js +119 -11
- package/dist_ts/ts_nosqldb/file-storage-startup.d.ts +1 -1
- package/dist_ts/ts_nosqldb/file-storage-startup.js +4 -2
- package/dist_ts/ts_nosqldb/index.d.ts +3 -0
- package/dist_ts/ts_nosqldb/index.js +2 -1
- package/dist_ts/ts_nosqldb/rust-db-bridge.d.ts +10 -0
- package/dist_ts/ts_nosqldb/rust-db-bridge.js +37 -5
- package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.d.ts +1 -1
- package/dist_ts/ts_nosqldb/server/classes.nosqldbserver.js +2 -1
- package/dist_ts/ts_nosqldb/service-types.d.ts +4 -0
- package/dist_ts/ts_nosqldb/service-types.js +1 -1
- package/dist_ts/ts_nosqldb/stopped-storage.d.ts +69 -0
- package/dist_ts/ts_nosqldb/stopped-storage.js +44 -0
- package/dist_ts/ts_nosqldb/storage-root-alignment.d.ts +58 -0
- package/dist_ts/ts_nosqldb/storage-root-alignment.js +129 -0
- package/dist_ts/ts_nosqldb/storage-root-relocation.js +12 -10
- package/package.json +1 -1
- package/readme.md +162 -16
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/ts_local/classes.localnosqldb.ts +123 -15
- package/ts/ts_nosqldb/file-storage-startup.ts +3 -2
- package/ts/ts_nosqldb/index.ts +13 -0
- package/ts/ts_nosqldb/rust-db-bridge.ts +52 -4
- package/ts/ts_nosqldb/server/classes.nosqldbserver.ts +2 -1
- package/ts/ts_nosqldb/service-types.ts +4 -0
- package/ts/ts_nosqldb/stopped-storage.ts +108 -0
- package/ts/ts_nosqldb/storage-root-alignment.ts +176 -0
- package/ts/ts_nosqldb/storage-root-relocation.ts +9 -7
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { normalizeLocalNoSqlDbStoppedStorageRootRelocationInput } from './storage-root-relocation.js';
|
|
3
|
+
import {
|
|
4
|
+
NoSqlDbStoppedOperationError, normalizeStoppedStorageRootInspectionInput,
|
|
5
|
+
type ILocalNoSqlDbAuditedStorageRoot, type TNoSqlDbStorageRootBindingChange,
|
|
6
|
+
} from './stopped-storage.js';
|
|
7
|
+
|
|
8
|
+
export interface INoSqlDbStorageRootPins {
|
|
9
|
+
groupId: string;
|
|
10
|
+
timelineId: string;
|
|
11
|
+
lsn: string;
|
|
12
|
+
recordsSha256: string;
|
|
13
|
+
providerRootId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ILocalNoSqlDbStoppedStorageRootAlignmentInput {
|
|
16
|
+
folderPath: string;
|
|
17
|
+
/** Retained immutable receipt in a private existing parent outside the root, on the same filesystem. */
|
|
18
|
+
receiptPath: string;
|
|
19
|
+
alignmentId: string;
|
|
20
|
+
identityPolicy: 'fork';
|
|
21
|
+
attestLineageNeverFenced: true;
|
|
22
|
+
expect?: INoSqlDbStorageRootPins;
|
|
23
|
+
acknowledgeUnverifiedCopy?: true;
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface ILocalNoSqlDbStoppedStorageRootAlignmentReceipt {
|
|
27
|
+
format: 'smartdb.storage-root-alignment.receipt.v1';
|
|
28
|
+
status: 'aligned';
|
|
29
|
+
alignmentId: string;
|
|
30
|
+
folderPath: string;
|
|
31
|
+
receiptPath: string;
|
|
32
|
+
requestSha256: string;
|
|
33
|
+
previousProviderRootId: string;
|
|
34
|
+
providerRootId: string;
|
|
35
|
+
bindingChange: Exclude<TNoSqlDbStorageRootBindingChange, 'none'>;
|
|
36
|
+
groupId: string;
|
|
37
|
+
timelineId: string;
|
|
38
|
+
sourceLsn: string;
|
|
39
|
+
alignedLsn: string;
|
|
40
|
+
records: string;
|
|
41
|
+
sourceRecordsSha256: string;
|
|
42
|
+
alignedRecordsSha256: string;
|
|
43
|
+
preservedRecordsSha256: string;
|
|
44
|
+
pinned: boolean;
|
|
45
|
+
receiptSha256: string;
|
|
46
|
+
}
|
|
47
|
+
export type TLocalNoSqlDbStoppedStorageRootAlignmentResult =
|
|
48
|
+
| ILocalNoSqlDbStoppedStorageRootAlignmentReceipt
|
|
49
|
+
| { status: 'eligible'; inspection: ILocalNoSqlDbAuditedStorageRoot }
|
|
50
|
+
| { status: 'already-bound'; bindingChange: 'none'; inspection: ILocalNoSqlDbAuditedStorageRoot };
|
|
51
|
+
export interface ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult {
|
|
52
|
+
status: 'aborted' | 'not-pending';
|
|
53
|
+
alignmentId: string;
|
|
54
|
+
folderPath: string;
|
|
55
|
+
receiptPath: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function fail(code: 'INVALID_REQUEST' | 'RECOVERY_REQUIRED'): never { throw new NoSqlDbStoppedOperationError(code); }
|
|
59
|
+
const data = (value: unknown, required: string[], optional: string[] = []): value is Record<string, unknown> => {
|
|
60
|
+
if (!value || Object.getPrototypeOf(value) !== Object.prototype) return false;
|
|
61
|
+
const keys = Reflect.ownKeys(value);
|
|
62
|
+
if (keys.some(key => typeof key !== 'string' || ![...required, ...optional].includes(key))) return false;
|
|
63
|
+
return required.every(key => keys.includes(key)) && keys.every(key => {
|
|
64
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key)!;
|
|
65
|
+
return descriptor.enumerable && 'value' in descriptor;
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const sha = (value: unknown): value is string => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
|
|
69
|
+
const decimal = (value: unknown): value is string => typeof value === 'string'
|
|
70
|
+
&& /^(0|[1-9][0-9]{0,19})$/.test(value) && BigInt(value) <= 18446744073709551615n;
|
|
71
|
+
const lineage = (value: unknown): value is string => typeof value === 'string' && value.length > 0
|
|
72
|
+
&& plugins.buffer.Buffer.byteLength(value) <= 256 && !/\p{Cc}/u.test(value);
|
|
73
|
+
const digest = (domain: string, payload: unknown[]): string => plugins.crypto.createHash('sha256')
|
|
74
|
+
.update(domain).update(JSON.stringify(payload)).digest('hex');
|
|
75
|
+
|
|
76
|
+
export const normalizeStoppedStorageRootAlignmentInput = (
|
|
77
|
+
value: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
78
|
+
): ILocalNoSqlDbStoppedStorageRootAlignmentInput => {
|
|
79
|
+
if (!data(value, ['folderPath', 'receiptPath', 'alignmentId', 'identityPolicy', 'attestLineageNeverFenced'],
|
|
80
|
+
['expect', 'acknowledgeUnverifiedCopy', 'dryRun'])) fail('INVALID_REQUEST');
|
|
81
|
+
try {
|
|
82
|
+
normalizeStoppedStorageRootInspectionInput({ folderPath: value.folderPath });
|
|
83
|
+
normalizeStoppedStorageRootInspectionInput({ folderPath: value.receiptPath });
|
|
84
|
+
normalizeLocalNoSqlDbStoppedStorageRootRelocationInput({ sourceFolderPath: value.folderPath,
|
|
85
|
+
destinationFolderPath: value.receiptPath, relocationId: value.alignmentId });
|
|
86
|
+
} catch { fail('INVALID_REQUEST'); }
|
|
87
|
+
if (value.identityPolicy !== 'fork' || value.attestLineageNeverFenced !== true
|
|
88
|
+
|| (value.dryRun !== undefined && typeof value.dryRun !== 'boolean')) fail('INVALID_REQUEST');
|
|
89
|
+
let expect: INoSqlDbStorageRootPins | undefined;
|
|
90
|
+
if (value.expect !== undefined) {
|
|
91
|
+
const pins = value.expect;
|
|
92
|
+
if (value.acknowledgeUnverifiedCopy !== undefined || !data(pins, ['groupId', 'timelineId', 'lsn', 'recordsSha256'], ['providerRootId'])
|
|
93
|
+
|| !lineage(pins.groupId) || !lineage(pins.timelineId) || !decimal(pins.lsn) || pins.lsn === '0'
|
|
94
|
+
|| !sha(pins.recordsSha256) || (pins.providerRootId !== undefined && !sha(pins.providerRootId))) fail('INVALID_REQUEST');
|
|
95
|
+
expect = { groupId: pins.groupId, timelineId: pins.timelineId, lsn: pins.lsn, recordsSha256: pins.recordsSha256,
|
|
96
|
+
...(pins.providerRootId === undefined ? {} : { providerRootId: pins.providerRootId }) };
|
|
97
|
+
} else if (value.acknowledgeUnverifiedCopy !== true) fail('INVALID_REQUEST');
|
|
98
|
+
return { folderPath: value.folderPath, receiptPath: value.receiptPath, alignmentId: value.alignmentId,
|
|
99
|
+
identityPolicy: 'fork', attestLineageNeverFenced: true,
|
|
100
|
+
...(expect ? { expect } : { acknowledgeUnverifiedCopy: true }), ...(value.dryRun ? { dryRun: true } : {}) };
|
|
101
|
+
};
|
|
102
|
+
export const storageRootAlignmentRequestDigest = (request: ILocalNoSqlDbStoppedStorageRootAlignmentInput): string =>
|
|
103
|
+
digest('smartdb.storage-root-alignment.request.v1\0', [request.folderPath, request.receiptPath, request.alignmentId,
|
|
104
|
+
request.identityPolicy, request.attestLineageNeverFenced, request.expect ? [request.expect.groupId,
|
|
105
|
+
request.expect.timelineId, request.expect.lsn, request.expect.recordsSha256, request.expect.providerRootId ?? null] : null,
|
|
106
|
+
request.acknowledgeUnverifiedCopy ?? null]);
|
|
107
|
+
|
|
108
|
+
const receiptFields = ['format', 'status', 'alignmentId', 'folderPath', 'receiptPath', 'requestSha256',
|
|
109
|
+
'previousProviderRootId', 'providerRootId', 'bindingChange', 'groupId', 'timelineId', 'sourceLsn', 'alignedLsn', 'records',
|
|
110
|
+
'sourceRecordsSha256', 'alignedRecordsSha256', 'preservedRecordsSha256', 'pinned'] as const;
|
|
111
|
+
|
|
112
|
+
const eligibleGenerations = (value: unknown): boolean => {
|
|
113
|
+
if (!data(value, ['manifest', 'onDisk']) || !decimal(value.manifest) || !Array.isArray(value.onDisk)
|
|
114
|
+
|| !value.onDisk.length || !value.onDisk.includes(value.manifest)) return false;
|
|
115
|
+
const manifest = BigInt(value.manifest);
|
|
116
|
+
return value.onDisk.every(generation => decimal(generation) && BigInt(generation) <= manifest);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const eligibleInspection = (value: unknown, request: ILocalNoSqlDbStoppedStorageRootAlignmentInput): value is ILocalNoSqlDbAuditedStorageRoot => {
|
|
120
|
+
if (!data(value, ['status', 'bindingState', 'bindingChange', 'storagePurpose', 'profileSchema', 'providerRootId',
|
|
121
|
+
'groupId', 'timelineId', 'lsn', 'records', 'recordsSha256', 'recordsWithoutFencingProfileSha256', 'authUsers',
|
|
122
|
+
'authority', 'generations', 'alignmentEligible', 'reasons', 'physicalAdmissionViolations'])
|
|
123
|
+
|| value.status !== 'audited' || value.alignmentEligible !== true || value.storagePurpose !== 'production'
|
|
124
|
+
|| ![1, 4].includes(value.profileSchema as number) || !sha(value.providerRootId) || !sha(value.recordsSha256)
|
|
125
|
+
|| !sha(value.recordsWithoutFencingProfileSha256) || !lineage(value.groupId) || !lineage(value.timelineId)
|
|
126
|
+
|| !decimal(value.lsn) || value.lsn === '0' || !decimal(value.records) || value.records === '0' || value.authUsers !== '0'
|
|
127
|
+
|| !['none', 'path', 'inode', 'path+inode'].includes(value.bindingChange as string)
|
|
128
|
+
|| value.bindingState !== (value.bindingChange === 'none' ? 'matching' : 'foreign')
|
|
129
|
+
|| !data(value.authority, ['resourceFences', 'receipts', 'publicationHolds', 'activeAllocations', 'retiredAllocations',
|
|
130
|
+
'allocationReplays', 'activeGrants', 'importStages']) || Object.values(value.authority).some(count => count !== '0')
|
|
131
|
+
|| !eligibleGenerations(value.generations)
|
|
132
|
+
|| !Array.isArray(value.reasons) || value.reasons.length || !Array.isArray(value.physicalAdmissionViolations)
|
|
133
|
+
|| value.physicalAdmissionViolations.length) return false;
|
|
134
|
+
const pins = request.expect;
|
|
135
|
+
return !pins || (value.groupId === pins.groupId && value.timelineId === pins.timelineId && value.lsn === pins.lsn
|
|
136
|
+
&& value.recordsSha256 === pins.recordsSha256 && (pins.providerRootId === undefined || value.providerRootId === pins.providerRootId));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const normalizeStoppedStorageRootAlignmentResult = (value: unknown,
|
|
140
|
+
request: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
141
|
+
): TLocalNoSqlDbStoppedStorageRootAlignmentResult => {
|
|
142
|
+
if (!value || typeof value !== 'object' || !('status' in value)) fail('RECOVERY_REQUIRED');
|
|
143
|
+
if (value.status === 'eligible' || value.status === 'already-bound') {
|
|
144
|
+
if (!data(value, value.status === 'eligible' ? ['status', 'inspection'] : ['status', 'inspection', 'bindingChange'])) fail('RECOVERY_REQUIRED');
|
|
145
|
+
const record = value as Record<string, unknown>;
|
|
146
|
+
const inspection = record.inspection as ILocalNoSqlDbAuditedStorageRoot;
|
|
147
|
+
if (!eligibleInspection(inspection, request)
|
|
148
|
+
|| (value.status === 'eligible' && !request.dryRun)
|
|
149
|
+
|| (value.status === 'already-bound' && (record.bindingChange !== 'none' || inspection.bindingState !== 'matching'))) fail('RECOVERY_REQUIRED');
|
|
150
|
+
return value as unknown as TLocalNoSqlDbStoppedStorageRootAlignmentResult;
|
|
151
|
+
}
|
|
152
|
+
if (!data(value, [...receiptFields, 'receiptSha256'])) fail('RECOVERY_REQUIRED');
|
|
153
|
+
const receipt = value as unknown as ILocalNoSqlDbStoppedStorageRootAlignmentReceipt;
|
|
154
|
+
if (receipt.format !== 'smartdb.storage-root-alignment.receipt.v1' || receipt.status !== 'aligned'
|
|
155
|
+
|| receipt.alignmentId !== request.alignmentId || receipt.folderPath !== request.folderPath || receipt.receiptPath !== request.receiptPath
|
|
156
|
+
|| receipt.requestSha256 !== storageRootAlignmentRequestDigest(request) || receipt.pinned !== (request.expect !== undefined)
|
|
157
|
+
|| ![receipt.previousProviderRootId, receipt.providerRootId, receipt.sourceRecordsSha256, receipt.alignedRecordsSha256,
|
|
158
|
+
receipt.preservedRecordsSha256, receipt.receiptSha256].every(sha)
|
|
159
|
+
|| receipt.previousProviderRootId === receipt.providerRootId || !lineage(receipt.groupId) || !lineage(receipt.timelineId)
|
|
160
|
+
|| !['path', 'inode', 'path+inode'].includes(receipt.bindingChange)
|
|
161
|
+
|| !decimal(receipt.sourceLsn) || receipt.sourceLsn === '0' || !decimal(receipt.alignedLsn)
|
|
162
|
+
|| BigInt(receipt.sourceLsn) + 1n !== BigInt(receipt.alignedLsn) || !decimal(receipt.records) || receipt.records === '0'
|
|
163
|
+
|| receipt.receiptSha256 !== digest('smartdb.storage-root-alignment.receipt.v1\0', receiptFields.map(key => receipt[key]))) fail('RECOVERY_REQUIRED');
|
|
164
|
+
if (request.expect && (receipt.groupId !== request.expect.groupId || receipt.timelineId !== request.expect.timelineId
|
|
165
|
+
|| receipt.sourceLsn !== request.expect.lsn || receipt.sourceRecordsSha256 !== request.expect.recordsSha256
|
|
166
|
+
|| (request.expect.providerRootId !== undefined && receipt.previousProviderRootId !== request.expect.providerRootId))) fail('RECOVERY_REQUIRED');
|
|
167
|
+
return { ...receipt };
|
|
168
|
+
};
|
|
169
|
+
export const normalizeStoppedStorageRootAlignmentAbortResult = (value: unknown,
|
|
170
|
+
request: ILocalNoSqlDbStoppedStorageRootAlignmentInput,
|
|
171
|
+
): ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult => {
|
|
172
|
+
if (!data(value, ['status', 'alignmentId', 'folderPath', 'receiptPath'])
|
|
173
|
+
|| (value.status !== 'aborted' && value.status !== 'not-pending') || value.alignmentId !== request.alignmentId
|
|
174
|
+
|| value.folderPath !== request.folderPath || value.receiptPath !== request.receiptPath) fail('RECOVERY_REQUIRED');
|
|
175
|
+
return value as unknown as ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult;
|
|
176
|
+
};
|
|
@@ -240,7 +240,6 @@ export const assertNoLocalNoSqlDbStorageRootRelocationWorkspace = async (
|
|
|
240
240
|
): Promise<void> => {
|
|
241
241
|
const storagePath = plugins.path.resolve(storagePathArg);
|
|
242
242
|
const internalPath = plugins.path.join(storagePath, '.__rustdb_internal');
|
|
243
|
-
const workspacePath = plugins.path.join(internalPath, 'storage-root-relocation');
|
|
244
243
|
try {
|
|
245
244
|
const storage = await plugins.fs.stat(storagePath);
|
|
246
245
|
if (!storage.isDirectory()) {
|
|
@@ -259,13 +258,16 @@ export const assertNoLocalNoSqlDbStorageRootRelocationWorkspace = async (
|
|
|
259
258
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;
|
|
260
259
|
throw new Error('NoSQLDB storage-root relocation workspace inspection is ambiguous');
|
|
261
260
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
261
|
+
for (const operation of ['relocation', 'alignment'] as const) {
|
|
262
|
+
const workspacePath = plugins.path.join(internalPath, `storage-root-${operation}`);
|
|
263
|
+
try {
|
|
264
|
+
await plugins.fs.lstat(workspacePath);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
267
|
+
throw new Error(`NoSQLDB storage-root ${operation} workspace inspection is ambiguous`);
|
|
268
|
+
}
|
|
269
|
+
throw new Error(`NoSQLDB storage-root ${operation} recovery must complete before startup`);
|
|
267
270
|
}
|
|
268
|
-
throw new Error('NoSQLDB storage-root relocation recovery must complete before startup');
|
|
269
271
|
};
|
|
270
272
|
|
|
271
273
|
export const isLocalNoSqlDbStorageRootRelocationErrorCode = (
|