tsondb 0.19.3 → 0.19.6
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/bin/tsondb.js
CHANGED
|
@@ -13,6 +13,7 @@ import { access, constants } from "node:fs/promises";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { cwd } from "node:process";
|
|
15
15
|
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { styleText } from "node:util";
|
|
16
17
|
import { parseArguments } from "simple-cli-args";
|
|
17
18
|
import { validateConfigForData, validateConfigForGeneration, validateConfigForServer, validateConfigForTesting, } from "../node/config.js";
|
|
18
19
|
import { TSONDB } from "../node/index.js";
|
|
@@ -48,6 +49,25 @@ const passedArguments = parseArguments({
|
|
|
48
49
|
format: {
|
|
49
50
|
name: "format",
|
|
50
51
|
},
|
|
52
|
+
name: {
|
|
53
|
+
name: "name",
|
|
54
|
+
options: {
|
|
55
|
+
id: {
|
|
56
|
+
name: "id",
|
|
57
|
+
type: String,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
list: {
|
|
62
|
+
name: "list",
|
|
63
|
+
options: {
|
|
64
|
+
entity: {
|
|
65
|
+
name: "entity",
|
|
66
|
+
alias: "e",
|
|
67
|
+
type: String,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
51
71
|
},
|
|
52
72
|
});
|
|
53
73
|
// import the config
|
|
@@ -84,6 +104,10 @@ if (config === undefined) {
|
|
|
84
104
|
if (passedArguments.command === undefined) {
|
|
85
105
|
throw new Error("No command has been specified. Possible commands are: generate, serve, validate.");
|
|
86
106
|
}
|
|
107
|
+
const createDB = (config, validationOptions, skipReferenceCache) => TSONDB.create({
|
|
108
|
+
...config,
|
|
109
|
+
validationOptions,
|
|
110
|
+
}, skipReferenceCache);
|
|
87
111
|
if (passedArguments.command.name === "generate") {
|
|
88
112
|
debug(`running command: generate`);
|
|
89
113
|
validateConfigForGeneration(config);
|
|
@@ -91,21 +115,14 @@ if (passedArguments.command.name === "generate") {
|
|
|
91
115
|
}
|
|
92
116
|
else {
|
|
93
117
|
validateConfigForData(config);
|
|
94
|
-
const db = await TSONDB.create({
|
|
95
|
-
...config,
|
|
96
|
-
validationOptions: passedArguments.command.name === "validate"
|
|
97
|
-
? {
|
|
98
|
-
...config.validationOptions,
|
|
99
|
-
...omitUndefinedKeys(passedArguments.command.options ?? {}),
|
|
100
|
-
}
|
|
101
|
-
: config.validationOptions,
|
|
102
|
-
});
|
|
103
118
|
switch (passedArguments.command.name) {
|
|
104
|
-
case "serve":
|
|
119
|
+
case "serve": {
|
|
105
120
|
debug(`running command: serve`);
|
|
106
121
|
validateConfigForServer(config);
|
|
122
|
+
const db = await createDB(config, config.validationOptions, false);
|
|
107
123
|
await createServer(db, config.homeLayoutSections, config.serverOptions, config.validationOptions, config.customStylesheetPath);
|
|
108
124
|
break;
|
|
125
|
+
}
|
|
109
126
|
case "validate": {
|
|
110
127
|
debug(`running command: validate`);
|
|
111
128
|
validateConfigForTesting(config);
|
|
@@ -116,15 +133,57 @@ else {
|
|
|
116
133
|
const entities = passedArguments.command.options.checkOnlyEntities;
|
|
117
134
|
debug(`only check the following entities: ${entities.join(", ")}`);
|
|
118
135
|
}
|
|
136
|
+
const db = await createDB(config, {
|
|
137
|
+
...config.validationOptions,
|
|
138
|
+
...omitUndefinedKeys(passedArguments.command.options ?? {}),
|
|
139
|
+
}, false);
|
|
119
140
|
const result = db.validate();
|
|
120
141
|
if (!result) {
|
|
121
142
|
process.exitCode = 1;
|
|
122
143
|
}
|
|
123
144
|
break;
|
|
124
145
|
}
|
|
125
|
-
case "format":
|
|
146
|
+
case "format": {
|
|
126
147
|
debug(`running command: format`);
|
|
148
|
+
const db = await createDB(config, config.validationOptions, true);
|
|
127
149
|
await db.format();
|
|
128
150
|
break;
|
|
151
|
+
}
|
|
152
|
+
case "name": {
|
|
153
|
+
debug(`running command: name`);
|
|
154
|
+
const id = passedArguments.command.options?.id;
|
|
155
|
+
if (id === undefined) {
|
|
156
|
+
throw new Error("No ID specified for name command.");
|
|
157
|
+
}
|
|
158
|
+
const db = await createDB(config, config.validationOptions, true);
|
|
159
|
+
const entityName = db.getEntityNameOfInstanceId(id);
|
|
160
|
+
if (entityName === undefined) {
|
|
161
|
+
throw new Error("An instance with the specified ID does not exist in the database.");
|
|
162
|
+
}
|
|
163
|
+
const displayName = db.getDisplayName(entityName, id);
|
|
164
|
+
console.log(displayName ?? styleText(["underline"], "no display name available"));
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "list": {
|
|
168
|
+
debug(`running command: list`);
|
|
169
|
+
const entity = passedArguments.command.options?.entity;
|
|
170
|
+
if (entity === undefined) {
|
|
171
|
+
throw new Error("No entity specified for list command.");
|
|
172
|
+
}
|
|
173
|
+
const db = await createDB(config, config.validationOptions, true);
|
|
174
|
+
if (!db.schema.isEntityName(entity)) {
|
|
175
|
+
throw new Error("An entity with the specified name does not exist in the database.");
|
|
176
|
+
}
|
|
177
|
+
const instances = db.getAllInstanceOverviewsOfEntity(entity);
|
|
178
|
+
if (instances.length === 0) {
|
|
179
|
+
console.log(styleText(["italic"], "no instances found"));
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
console.log(styleText(["bold"], "UUID Display Name"));
|
|
183
|
+
for (const instance of instances) {
|
|
184
|
+
console.log(`${styleText(["yellow"], instance.id)} ${instance.displayName}`);
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
129
188
|
}
|
|
130
189
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Output } from "../shared/output.ts";
|
|
2
|
-
import type { ValidationOptions } from "./index.ts";
|
|
2
|
+
import type { DefaultTSONDBTypes, ValidationOptions } from "./index.ts";
|
|
3
3
|
import type { EntityDecl } from "./schema/dsl/index.ts";
|
|
4
4
|
import type { Schema } from "./schema/index.ts";
|
|
5
5
|
/**
|
|
6
6
|
* The main configuration type for TSONDB.
|
|
7
7
|
*/
|
|
8
|
-
export type Config = {
|
|
8
|
+
export type Config<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> = {
|
|
9
9
|
serverOptions?: ServerOptions;
|
|
10
|
-
schema: Schema
|
|
10
|
+
schema: Schema<T>;
|
|
11
11
|
outputs?: Output[];
|
|
12
12
|
/**
|
|
13
13
|
* Default locales for the server/editor and locales used for testing via CLI.
|
package/dist/src/node/index.d.ts
CHANGED
|
@@ -83,8 +83,11 @@ export declare class TSONDB<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> {
|
|
|
83
83
|
private constructor();
|
|
84
84
|
/**
|
|
85
85
|
* Creates a new TSONDB instance with the specified options.
|
|
86
|
+
*
|
|
87
|
+
* @param options The options for creating the TSONDB instance.
|
|
88
|
+
* @param skipReferenceCache If set to `true`, the reference cache will not be created during initialization. This can speed up the initialization process, but referential integrity checks will not work correctly until the cache is created. This should only be set to `true` if you plan to call `sync()` immediately after initialization.
|
|
86
89
|
*/
|
|
87
|
-
static create<Types extends DefaultTSONDBTypes = DefaultTSONDBTypes>(options: TSONDBOptions<Types
|
|
90
|
+
static create<Types extends DefaultTSONDBTypes = DefaultTSONDBTypes>(options: TSONDBOptions<Types>, skipReferenceCache?: boolean): Promise<TSONDB<Types>>;
|
|
88
91
|
/**
|
|
89
92
|
* Generates outputs based on the provided schema and output configurations.
|
|
90
93
|
*/
|
|
@@ -218,6 +221,10 @@ export declare class TSONDB<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> {
|
|
|
218
221
|
queryLowerCase: string;
|
|
219
222
|
}) => number;
|
|
220
223
|
}>): [string, InstanceContainerOverview][];
|
|
224
|
+
/**
|
|
225
|
+
* Retrieves the name of the entity for a given instance ID. If no instance with the provided ID is found, `undefined` is returned.
|
|
226
|
+
*/
|
|
227
|
+
getEntityNameOfInstanceId(id: string): string | undefined;
|
|
221
228
|
/**
|
|
222
229
|
* Compresses the entire database into a single JSON file at the specified path.
|
|
223
230
|
*
|
package/dist/src/node/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import { checkCustomConstraintsForAllEntities } from "./utils/customConstraints.
|
|
|
21
21
|
import { DatabaseInMemory } from "./utils/databaseInMemory.js";
|
|
22
22
|
import { applyStepsToDisk } from "./utils/databaseOnDisk.js";
|
|
23
23
|
import { getAllInstanceOverviewsByEntityName, getDisplayNameFromEntityInstance, getInstanceOverview, getInstanceOverviewsByEntityName, } from "./utils/displayName.js";
|
|
24
|
-
import { countError, countErrors, getErrorMessageForDisplay, wrapErrorsIfAny, } from "./utils/error.js";
|
|
24
|
+
import { countError, countErrors, getErrorMessageForDisplay, HTTPError, wrapErrorsIfAny, } from "./utils/error.js";
|
|
25
25
|
import { getFileNameForId, writeInstance } from "./utils/files.js";
|
|
26
26
|
import { attachGitStatusToDatabaseInMemory } from "./utils/git.js";
|
|
27
27
|
import { getReferencesToInstances, isReferencedByOtherInstances, } from "./utils/references.js";
|
|
@@ -55,7 +55,7 @@ const getGit = async (dataRootPath) => {
|
|
|
55
55
|
return { git };
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
|
-
const initData = async (dataRootPath, schema, locales, git, gitStatus) => {
|
|
58
|
+
const initData = async (dataRootPath, schema, locales, git, gitStatus, skipReferenceCache) => {
|
|
59
59
|
debug("loading database into memory ...");
|
|
60
60
|
let data = await DatabaseInMemory.load(dataRootPath, schema.entities);
|
|
61
61
|
debug("done");
|
|
@@ -65,9 +65,16 @@ const initData = async (dataRootPath, schema, locales, git, gitStatus) => {
|
|
|
65
65
|
throw new Error("All provided locales must exist in the database.");
|
|
66
66
|
}
|
|
67
67
|
const serializedDeclarationsByName = Object.fromEntries(schema.resolvedDeclarations.map(decl => [decl.name, serializeNode(decl)]));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
let referencesToInstances;
|
|
69
|
+
if (!skipReferenceCache) {
|
|
70
|
+
debug("creating references cache ...");
|
|
71
|
+
referencesToInstances = await getReferencesToInstances(data, serializedDeclarationsByName);
|
|
72
|
+
debug("done");
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
debug("skipping references cache creation");
|
|
76
|
+
referencesToInstances = {};
|
|
77
|
+
}
|
|
71
78
|
if (git) {
|
|
72
79
|
const status = gitStatus ?? (await git.client.status());
|
|
73
80
|
data = attachGitStatusToDatabaseInMemory(data, dataRootPath, git.root, status);
|
|
@@ -87,6 +94,7 @@ export class TSONDB {
|
|
|
87
94
|
#referencesToInstances;
|
|
88
95
|
#validationOptions;
|
|
89
96
|
#gitWrapper;
|
|
97
|
+
#locked = false;
|
|
90
98
|
constructor(options) {
|
|
91
99
|
this.#dataRootPath = options.dataRootPath;
|
|
92
100
|
this.#data = options.data;
|
|
@@ -103,8 +111,11 @@ export class TSONDB {
|
|
|
103
111
|
}
|
|
104
112
|
/**
|
|
105
113
|
* Creates a new TSONDB instance with the specified options.
|
|
114
|
+
*
|
|
115
|
+
* @param options The options for creating the TSONDB instance.
|
|
116
|
+
* @param skipReferenceCache If set to `true`, the reference cache will not be created during initialization. This can speed up the initialization process, but referential integrity checks will not work correctly until the cache is created. This should only be set to `true` if you plan to call `sync()` immediately after initialization.
|
|
106
117
|
*/
|
|
107
|
-
static async create(options) {
|
|
118
|
+
static async create(options, skipReferenceCache = false) {
|
|
108
119
|
const entities = options.schema.entities;
|
|
109
120
|
debug("using data root path: %s", options.dataRootPath);
|
|
110
121
|
await prepareFolders(options.dataRootPath, entities);
|
|
@@ -121,7 +132,7 @@ export class TSONDB {
|
|
|
121
132
|
throw new Error("Schema does not contain a locale entity, locales cannot be provided.");
|
|
122
133
|
}
|
|
123
134
|
const git = gitRoot ? { client: gitClient, root: gitRoot } : undefined;
|
|
124
|
-
const { data, referencesToInstances } = await initData(options.dataRootPath, options.schema, options.locales ?? [], git, gitStatus);
|
|
135
|
+
const { data, referencesToInstances } = await initData(options.dataRootPath, options.schema, options.locales ?? [], git, gitStatus, skipReferenceCache);
|
|
125
136
|
return new TSONDB({
|
|
126
137
|
...options,
|
|
127
138
|
data,
|
|
@@ -266,7 +277,7 @@ export class TSONDB {
|
|
|
266
277
|
* Reloads the data from disk into memory.
|
|
267
278
|
*/
|
|
268
279
|
async sync() {
|
|
269
|
-
const { data, referencesToInstances } = await initData(this.#dataRootPath, this.#schema, this.#locales, this.#git);
|
|
280
|
+
const { data, referencesToInstances } = await initData(this.#dataRootPath, this.#schema, this.#locales, this.#git, undefined, false);
|
|
270
281
|
this.#data = data;
|
|
271
282
|
this.#referencesToInstances = referencesToInstances;
|
|
272
283
|
}
|
|
@@ -277,35 +288,46 @@ export class TSONDB {
|
|
|
277
288
|
* @throws {Error} when the transaction could not be completed
|
|
278
289
|
*/
|
|
279
290
|
async runTransaction(fn) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
data: this.#data,
|
|
283
|
-
getEntity,
|
|
284
|
-
referencesToInstances: this.#referencesToInstances,
|
|
285
|
-
validate: this.#strucurallyValidateInstance.bind(this),
|
|
286
|
-
localeEntity: this.#schema.localeEntity,
|
|
287
|
-
steps: [],
|
|
288
|
-
}));
|
|
289
|
-
const txtResult = txn.getResult();
|
|
290
|
-
const { data: newData, referencesToInstances: newRefs, steps } = txtResult;
|
|
291
|
-
const errors = this.#validate(newData, true);
|
|
292
|
-
if (errors.length > 0) {
|
|
293
|
-
throw new AggregateError(errors, "Validation errors occurred");
|
|
294
|
-
}
|
|
295
|
-
const diskResult = await applyStepsToDisk(this.#dataRootPath, steps);
|
|
296
|
-
if (isError(diskResult)) {
|
|
297
|
-
throw diskResult.error;
|
|
291
|
+
if (this.#locked) {
|
|
292
|
+
throw new HTTPError(503, "Another transaction is currently running. Transactions cannot be run concurrently.");
|
|
298
293
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
294
|
+
this.#locked = true;
|
|
295
|
+
try {
|
|
296
|
+
const getEntity = this.#schema.getEntity.bind(this.#schema);
|
|
297
|
+
const [txn, res] = fn(new Transaction({
|
|
298
|
+
data: this.#data,
|
|
299
|
+
getEntity,
|
|
300
|
+
referencesToInstances: this.#referencesToInstances,
|
|
301
|
+
validate: this.#strucurallyValidateInstance.bind(this),
|
|
302
|
+
localeEntity: this.#schema.localeEntity,
|
|
303
|
+
steps: [],
|
|
304
|
+
}));
|
|
305
|
+
const txtResult = txn.getResult();
|
|
306
|
+
const { data: newData, referencesToInstances: newRefs, steps } = txtResult;
|
|
307
|
+
const errors = this.#validate(newData, true);
|
|
308
|
+
if (errors.length > 0) {
|
|
309
|
+
throw new AggregateError(errors, "Validation errors occurred");
|
|
310
|
+
}
|
|
311
|
+
const diskResult = await applyStepsToDisk(this.#dataRootPath, steps);
|
|
312
|
+
if (isError(diskResult)) {
|
|
313
|
+
throw diskResult.error;
|
|
314
|
+
}
|
|
315
|
+
if (this.#git) {
|
|
316
|
+
const status = await this.#git.client.status();
|
|
317
|
+
const newDbWithUpdatedGit = attachGitStatusToDatabaseInMemory(newData, this.#dataRootPath, this.#git.root, status);
|
|
318
|
+
this.#data = newDbWithUpdatedGit;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
this.#data = newData;
|
|
322
|
+
}
|
|
323
|
+
this.#referencesToInstances = newRefs;
|
|
324
|
+
this.#locked = false;
|
|
325
|
+
return res;
|
|
303
326
|
}
|
|
304
|
-
|
|
305
|
-
this.#
|
|
327
|
+
catch (error) {
|
|
328
|
+
this.#locked = false;
|
|
329
|
+
throw error;
|
|
306
330
|
}
|
|
307
|
-
this.#referencesToInstances = newRefs;
|
|
308
|
-
return res;
|
|
309
331
|
}
|
|
310
332
|
/**
|
|
311
333
|
* Creates a new instance in the database.
|
|
@@ -495,6 +517,12 @@ export class TSONDB {
|
|
|
495
517
|
})
|
|
496
518
|
.toSorted(reduceCompare(on(item => item[1].relevance, compareNumber), (a, b) => a[1].displayName.localeCompare(b[1].displayName, a[1].displayNameLocaleId)));
|
|
497
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Retrieves the name of the entity for a given instance ID. If no instance with the provided ID is found, `undefined` is returned.
|
|
522
|
+
*/
|
|
523
|
+
getEntityNameOfInstanceId(id) {
|
|
524
|
+
return this.#data.getEntityNameOfInstanceId(id);
|
|
525
|
+
}
|
|
498
526
|
/**
|
|
499
527
|
* Compresses the entire database into a single JSON file at the specified path.
|
|
500
528
|
*
|
|
@@ -21,4 +21,5 @@ export declare class DatabaseInMemory<EM extends AnyEntityMap = RegisteredEntity
|
|
|
21
21
|
setInstanceContainerOfEntityById(entityName: Extract<keyof EM, string>, instance: InstanceContainer): [DatabaseInMemory<EM>, oldInstance: InstanceContent | undefined];
|
|
22
22
|
deleteInstanceContainerOfEntityById(entityName: Extract<keyof EM, string>, instanceId: string): [DatabaseInMemory<EM>, oldInstance: InstanceContent | undefined];
|
|
23
23
|
map(fn: (instances: Dictionary<InstanceContainer>, entityName: Extract<keyof EM, string>) => Dictionary<InstanceContainer>): DatabaseInMemory<EM>;
|
|
24
|
+
getEntityNameOfInstanceId(id: string): Extract<keyof EM, string> | undefined;
|
|
24
25
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsondb",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Lukas Obermann",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"typescript-eslint": "^8.54.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@elyukai/utils": "^0.2.
|
|
51
|
+
"@elyukai/utils": "^0.2.1",
|
|
52
52
|
"@preact/signals": "^2.7.0",
|
|
53
53
|
"debug": "^4.4.3",
|
|
54
54
|
"express": "^5.2.1",
|