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.
@@ -164,8 +164,9 @@ export class TSONDB {
164
164
  };
165
165
  return validateDeclStructuralIntegrity(validationContext, [], entity, [], instanceContent);
166
166
  }
167
- #validate(data, skipForTransaction = false) {
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 (!skipForTransaction) {
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 (!skipForTransaction) {
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
- console.log(`${data.totalSize.toString()} instance${data.totalSize === 1 ? "" : "s"} checked`);
249
- if (errors.length === 0) {
250
- console.log(styleText("green", "All instances are valid"));
251
- }
252
- else {
253
- const errorCount = countErrors(errors);
254
- console.error(styleText("red", `${errorCount.toString()} validation error${errorCount === 1 ? "" : "s"} found\n\n${errors.map(err => getErrorMessageForDisplay(err)).join("\n\n")}`, { stream: stderr }));
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 errors = this.#validate(newData, true);
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsondb",
3
- "version": "0.19.6",
3
+ "version": "0.19.7",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "Lukas Obermann",