tsondb 0.19.6 → 0.19.7
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/src/node/index.js
CHANGED
|
@@ -164,8 +164,9 @@ export class TSONDB {
|
|
|
164
164
|
};
|
|
165
165
|
return validateDeclStructuralIntegrity(validationContext, [], entity, [], instanceContent);
|
|
166
166
|
}
|
|
167
|
-
#validate(data,
|
|
167
|
+
#validate(data, options = {}) {
|
|
168
168
|
const { checkReferentialIntegrity, checkOnlyEntities } = this.#validationOptions;
|
|
169
|
+
const { skipStructuralValidation = false, logToConsole = true, changedEntities: changedEntityNames, } = options;
|
|
169
170
|
const entities = this.#schema.entities;
|
|
170
171
|
const getEntity = this.#schema.getEntity.bind(this.#schema);
|
|
171
172
|
for (const onlyEntity of checkOnlyEntities) {
|
|
@@ -176,12 +177,15 @@ export class TSONDB {
|
|
|
176
177
|
const onlyEntities = checkOnlyEntities.length > 0
|
|
177
178
|
? entities.filter(entity => checkOnlyEntities.includes(entity.name))
|
|
178
179
|
: entities;
|
|
180
|
+
const changedEntities = changedEntityNames
|
|
181
|
+
? entities.filter(entity => changedEntityNames.includes(entity.name))
|
|
182
|
+
: undefined;
|
|
179
183
|
const validationContext = {
|
|
180
184
|
validationOptions: this.#validationOptions,
|
|
181
185
|
useStyling: true,
|
|
182
186
|
};
|
|
183
187
|
const errors = [];
|
|
184
|
-
if (!
|
|
188
|
+
if (!skipStructuralValidation) {
|
|
185
189
|
debug("Checking structural integrity ...");
|
|
186
190
|
const structureErrors = onlyEntities
|
|
187
191
|
.flatMap(entity => parallelizeErrors(data
|
|
@@ -198,7 +202,7 @@ export class TSONDB {
|
|
|
198
202
|
}
|
|
199
203
|
}
|
|
200
204
|
if (errors.length === 0) {
|
|
201
|
-
if (!
|
|
205
|
+
if (!skipStructuralValidation) {
|
|
202
206
|
if (checkReferentialIntegrity) {
|
|
203
207
|
debug("Checking referential integrity ...");
|
|
204
208
|
const referenceValidator = createReferenceValidator(this.#schema.isEntityName.bind(this.#schema), data, true);
|
|
@@ -222,7 +226,7 @@ export class TSONDB {
|
|
|
222
226
|
}
|
|
223
227
|
debug("Checking unique constraints ...");
|
|
224
228
|
const instanceOverviewsByEntityName = getAllInstanceOverviewsByEntityName(getEntity, data, this.#locales);
|
|
225
|
-
const uniqueConstraintResult = checkUniqueConstraintsForAllEntities(data, onlyEntities, instanceOverviewsByEntityName);
|
|
229
|
+
const uniqueConstraintResult = checkUniqueConstraintsForAllEntities(data, changedEntities ?? onlyEntities, instanceOverviewsByEntityName);
|
|
226
230
|
if (isError(uniqueConstraintResult)) {
|
|
227
231
|
const errorCount = countError(uniqueConstraintResult.error);
|
|
228
232
|
debug(`${errorCount.toString()} unique constraint violation${errorCount === 1 ? "" : "s"} found`);
|
|
@@ -245,13 +249,15 @@ export class TSONDB {
|
|
|
245
249
|
else {
|
|
246
250
|
debug("Skipping further checks due to previous structural integrity errors");
|
|
247
251
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
252
|
+
if (logToConsole) {
|
|
253
|
+
console.log(`${data.totalSize.toString()} instance${data.totalSize === 1 ? "" : "s"} checked`);
|
|
254
|
+
if (errors.length === 0) {
|
|
255
|
+
console.log(styleText("green", "All instances are valid"));
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const errorCount = countErrors(errors);
|
|
259
|
+
console.error(styleText("red", `${errorCount.toString()} validation error${errorCount === 1 ? "" : "s"} found\n\n${errors.map(err => getErrorMessageForDisplay(err)).join("\n\n")}`, { stream: stderr }));
|
|
260
|
+
}
|
|
255
261
|
}
|
|
256
262
|
return errors;
|
|
257
263
|
}
|
|
@@ -261,25 +267,30 @@ export class TSONDB {
|
|
|
261
267
|
* Returns `true` if the data is valid, `false` otherwise.
|
|
262
268
|
*/
|
|
263
269
|
validate() {
|
|
270
|
+
debug("Validating database ...");
|
|
264
271
|
return this.#validate(this.#data).length === 0;
|
|
265
272
|
}
|
|
266
273
|
/**
|
|
267
274
|
* Formats the data on disk according to the current in-memory representation.
|
|
268
275
|
*/
|
|
269
276
|
async format() {
|
|
277
|
+
debug("Formatting database ...");
|
|
270
278
|
await this.#data.forEachInstance(async (entityName, instance) => {
|
|
271
279
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
272
280
|
const entity = this.#schema.getEntity(entityName);
|
|
273
281
|
await writeInstance(this.#dataRootPath, entity, instance.id, instance.content);
|
|
274
282
|
}, true);
|
|
283
|
+
debug("All data is formatted");
|
|
275
284
|
}
|
|
276
285
|
/**
|
|
277
286
|
* Reloads the data from disk into memory.
|
|
278
287
|
*/
|
|
279
288
|
async sync() {
|
|
289
|
+
debug("Syncing with disk ...");
|
|
280
290
|
const { data, referencesToInstances } = await initData(this.#dataRootPath, this.#schema, this.#locales, this.#git, undefined, false);
|
|
281
291
|
this.#data = data;
|
|
282
292
|
this.#referencesToInstances = referencesToInstances;
|
|
293
|
+
debug("Synced with disk");
|
|
283
294
|
}
|
|
284
295
|
/**
|
|
285
296
|
* Runs a transaction on the database.
|
|
@@ -289,8 +300,10 @@ export class TSONDB {
|
|
|
289
300
|
*/
|
|
290
301
|
async runTransaction(fn) {
|
|
291
302
|
if (this.#locked) {
|
|
303
|
+
debug("Another transaction is currently running");
|
|
292
304
|
throw new HTTPError(503, "Another transaction is currently running. Transactions cannot be run concurrently.");
|
|
293
305
|
}
|
|
306
|
+
debug("Starting transaction, locking database ...");
|
|
294
307
|
this.#locked = true;
|
|
295
308
|
try {
|
|
296
309
|
const getEntity = this.#schema.getEntity.bind(this.#schema);
|
|
@@ -304,12 +317,19 @@ export class TSONDB {
|
|
|
304
317
|
}));
|
|
305
318
|
const txtResult = txn.getResult();
|
|
306
319
|
const { data: newData, referencesToInstances: newRefs, steps } = txtResult;
|
|
307
|
-
const
|
|
320
|
+
const changedEntities = [...new Set(steps.map(step => step.entity.name))];
|
|
321
|
+
const errors = this.#validate(newData, {
|
|
322
|
+
logToConsole: false,
|
|
323
|
+
skipStructuralValidation: true,
|
|
324
|
+
changedEntities,
|
|
325
|
+
});
|
|
308
326
|
if (errors.length > 0) {
|
|
327
|
+
debug("Transaction validation failed with %d error%s", errors.length, errors.length === 1 ? "" : "s");
|
|
309
328
|
throw new AggregateError(errors, "Validation errors occurred");
|
|
310
329
|
}
|
|
311
330
|
const diskResult = await applyStepsToDisk(this.#dataRootPath, steps);
|
|
312
331
|
if (isError(diskResult)) {
|
|
332
|
+
debug("Error applying changes to disk: %s", diskResult.error.message);
|
|
313
333
|
throw diskResult.error;
|
|
314
334
|
}
|
|
315
335
|
if (this.#git) {
|
|
@@ -322,6 +342,7 @@ export class TSONDB {
|
|
|
322
342
|
}
|
|
323
343
|
this.#referencesToInstances = newRefs;
|
|
324
344
|
this.#locked = false;
|
|
345
|
+
debug("Transaction successful, released lock");
|
|
325
346
|
return res;
|
|
326
347
|
}
|
|
327
348
|
catch (error) {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { removeAt } from "@elyukai/utils/array/modify";
|
|
2
2
|
import { difference } from "@elyukai/utils/array/sets";
|
|
3
3
|
import { isOk } from "@elyukai/utils/result";
|
|
4
|
-
import Debug from "debug";
|
|
5
4
|
import { resolve } from "node:path";
|
|
6
5
|
import { getReferences } from "../schema/treeOperations/references.js";
|
|
7
6
|
import {} from "./databaseInMemory.js";
|
|
8
7
|
import { WorkerPool } from "./workers.js";
|
|
9
|
-
const debug = Debug("tsondb:utils:references");
|
|
10
8
|
export const isReferencedByOtherInstances = (referencesToInstances, instanceId, otherInstancesToDelete) => {
|
|
11
9
|
const allInstancesToDelete = [instanceId, ...(otherInstancesToDelete ?? [])];
|
|
12
10
|
return referencesToInstances[instanceId]?.some(ref => allInstancesToDelete.includes(ref)) ?? false;
|
|
@@ -37,15 +35,11 @@ const removeReference = (acc, reference, instanceId) => {
|
|
|
37
35
|
};
|
|
38
36
|
const removeReferences = (acc, references, instanceId) => references.reduce((acc1, reference) => removeReference(acc1, reference, instanceId), acc);
|
|
39
37
|
export const getReferencesToInstances = async (databaseInMemory, serializedDeclarationsByName) => {
|
|
40
|
-
debug("creating reference worker pool ...");
|
|
41
38
|
const pool = new WorkerPool(6, resolve(import.meta.dirname, "./referencesWorker.js"), serializedDeclarationsByName);
|
|
42
|
-
debug("collecting references ...");
|
|
43
39
|
const separateResults = await Promise.all(databaseInMemory.getAllInstances().map(([entityName, instances]) => new Promise((resolve, reject) => {
|
|
44
40
|
try {
|
|
45
|
-
debug("collecting references for entity %s in %d instances", entityName, instances.length);
|
|
46
41
|
pool.runTask({ entityName, instances }, result => {
|
|
47
42
|
if (isOk(result)) {
|
|
48
|
-
debug("collected references for entity %s in %d instances", entityName, instances.length);
|
|
49
43
|
resolve(result.value);
|
|
50
44
|
}
|
|
51
45
|
else {
|
|
@@ -54,13 +48,11 @@ export const getReferencesToInstances = async (databaseInMemory, serializedDecla
|
|
|
54
48
|
});
|
|
55
49
|
}
|
|
56
50
|
catch (err) {
|
|
57
|
-
debug("error collecting references for entity %s: %O", entityName, err);
|
|
58
51
|
reject(err);
|
|
59
52
|
}
|
|
60
53
|
})));
|
|
61
54
|
await pool.close();
|
|
62
55
|
const results = separateResults.reduce(mergeReferences, {});
|
|
63
|
-
debug("collected references");
|
|
64
56
|
return results;
|
|
65
57
|
};
|
|
66
58
|
export const updateReferencesToInstances = (getEntity, referencesToInstances, entityName, instanceId, oldInstance, newInstance) => {
|