tsondb 0.21.5 → 0.21.10
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/bin/tsondb.js +10 -4
- package/dist/src/node/index.d.ts +12 -3
- package/dist/src/node/index.js +47 -20
- package/dist/src/node/renderers/jsonschema/index.d.ts +1 -1
- package/dist/src/node/renderers/jsonschema/index.js +2 -2
- package/dist/src/node/renderers/jsonschema/render.js +4 -6
- package/dist/src/node/renderers/ts/index.d.ts +1 -1
- package/dist/src/node/renderers/ts/index.js +2 -2
- package/dist/src/node/renderers/ts/render.js +11 -21
- package/dist/src/node/schema/detached.d.ts +1 -1
- package/dist/src/node/schema/detached.js +0 -1
- package/dist/src/node/schema/dsl/declarations/Decl.d.ts +3 -3
- package/dist/src/node/schema/dsl/declarations/Decl.js +1 -1
- package/dist/src/node/schema/dsl/declarations/EntityDecl.d.ts +2 -2
- package/dist/src/node/schema/dsl/declarations/EntityDecl.js +1 -1
- package/dist/src/node/schema/dsl/declarations/EnumDecl.d.ts +2 -3
- package/dist/src/node/schema/dsl/declarations/SingletonEntityDecl.d.ts +1 -1
- package/dist/src/node/schema/dsl/declarations/TypeAliasDecl.d.ts +1 -1
- package/dist/src/node/schema/dsl/index.d.ts +1 -1
- package/dist/src/node/schema/dsl/types/ChildEntitiesType.d.ts +1 -1
- package/dist/src/node/schema/dsl/types/ChildEntitiesType.js +0 -1
- package/dist/src/node/schema/dsl/types/IncludeIdentifierType.d.ts +1 -1
- package/dist/src/node/schema/dsl/types/IncludeIdentifierType.js +0 -1
- package/dist/src/node/schema/dsl/types/NestedEntityMapType.d.ts +1 -1
- package/dist/src/node/schema/dsl/types/NestedEntityMapType.js +0 -1
- package/dist/src/node/schema/dsl/types/ObjectType.js +1 -1
- package/dist/src/node/schema/helpers.d.ts +4 -4
- package/dist/src/node/schema/helpers.js +10 -12
- package/dist/src/node/schema/index.d.ts +2 -2
- package/dist/src/node/schema/index.js +4 -3
- package/dist/src/node/schema/treeOperations/format.js +2 -1
- package/dist/src/node/schema/treeOperations/serialization.d.ts +19 -19
- package/dist/src/node/schema/treeOperations/serialization.js +0 -18
- package/dist/src/node/schema/treeOperations/typeResolution.d.ts +9 -11
- package/dist/src/node/schema/treeOperations/typeResolution.js +0 -7
- package/dist/src/node/schema/treeOperations/validation.d.ts +1 -1
- package/dist/src/node/schema/treeOperations/validation.js +5 -6
- package/dist/src/node/schema/validation/options.js +2 -1
- package/dist/src/node/server/api/declarations.js +2 -2
- package/dist/src/node/server/api/git.js +2 -2
- package/dist/src/node/server/index.d.ts +2 -2
- package/dist/src/node/server/index.js +2 -2
- package/dist/src/node/server/utils/query.js +1 -1
- package/dist/src/node/transaction.d.ts +2 -2
- package/dist/src/node/transaction.js +0 -2
- package/dist/src/node/utils/childInstances.d.ts +1 -1
- package/dist/src/node/utils/childInstances.js +3 -2
- package/dist/src/node/utils/customConstraints.d.ts +1 -1
- package/dist/src/node/utils/customConstraints.js +0 -1
- package/dist/src/node/utils/databaseInMemory.js +16 -18
- package/dist/src/node/utils/databaseOnDisk.js +8 -0
- package/dist/src/node/utils/displayName.d.ts +1 -1
- package/dist/src/node/utils/displayName.js +24 -29
- package/dist/src/node/utils/files.d.ts +1 -1
- package/dist/src/node/utils/files.js +1 -2
- package/dist/src/node/utils/path.js +2 -1
- package/dist/src/node/utils/references.d.ts +1 -1
- package/dist/src/node/utils/references.js +6 -5
- package/dist/src/node/utils/unique.d.ts +1 -1
- package/dist/src/node/utils/unique.js +8 -17
- package/dist/src/shared/schema/declarations/Declaration.d.ts +1 -1
- package/dist/src/shared/schema/declarations/Declaration.js +0 -1
- package/dist/src/shared/schema/declarations/EnumDecl.js +5 -7
- package/dist/src/shared/schema/types/EnumType.js +2 -2
- package/dist/src/shared/schema/types/NestedEntityMapType.d.ts +1 -2
- package/dist/src/shared/schema/utils/keyPath.js +14 -14
- package/dist/src/shared/schema/utils/sortOrder.js +1 -1
- package/dist/src/shared/utils/git.js +6 -1
- package/dist/src/shared/utils/render.js +3 -3
- package/dist/src/shared/utils/translation.js +15 -16
- package/dist/src/shared/utils/validation.js +4 -4
- package/dist/src/shared/validation/date.js +2 -2
- package/dist/src/shared/validation/identifier.js +1 -1
- package/dist/src/shared/validation/number.js +3 -3
- package/dist/src/shared/validation/object.js +1 -1
- package/dist/src/shared/validation/string.js +1 -1
- package/dist/src/web/web.js +92 -76
- package/package.json +9 -12
|
@@ -8,6 +8,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
8
8
|
return path;
|
|
9
9
|
};
|
|
10
10
|
import { omitUndefinedKeys } from "@elyukai/utils/object";
|
|
11
|
+
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
11
12
|
import Debug from "debug";
|
|
12
13
|
import { access, constants } from "node:fs/promises";
|
|
13
14
|
import { join } from "node:path";
|
|
@@ -87,12 +88,14 @@ const config = await (async () => {
|
|
|
87
88
|
for (const configName of possibleConfigNames) {
|
|
88
89
|
const fullPath = join(cwd(), configName);
|
|
89
90
|
try {
|
|
91
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
90
92
|
await access(fullPath, constants.R_OK);
|
|
91
93
|
}
|
|
92
94
|
catch {
|
|
93
95
|
debug(`could not use config file ${fullPath}, not found`);
|
|
94
96
|
continue;
|
|
95
97
|
}
|
|
98
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
96
99
|
const foundConfigModule = (await import(__rewriteRelativeImportExtension(pathToFileURL(fullPath).toString())));
|
|
97
100
|
if ("default" in foundConfigModule) {
|
|
98
101
|
debug(`config file ${fullPath} found with config`);
|
|
@@ -110,18 +113,19 @@ if (config === undefined) {
|
|
|
110
113
|
if (passedArguments.command === undefined) {
|
|
111
114
|
throw new Error("No command has been specified. Possible commands are: generate, serve, validate.");
|
|
112
115
|
}
|
|
113
|
-
const createDB = (
|
|
114
|
-
...
|
|
116
|
+
const createDB = (dataConfig, validationOptions, skipReferenceCache) => TSONDB.create({
|
|
117
|
+
...dataConfig,
|
|
115
118
|
validationOptions,
|
|
116
119
|
}, skipReferenceCache);
|
|
117
|
-
|
|
120
|
+
const commandName = passedArguments.command.name;
|
|
121
|
+
if (commandName === "generate") {
|
|
118
122
|
debug(`running command: generate`);
|
|
119
123
|
validateConfigForGeneration(config);
|
|
120
124
|
await TSONDB.generateOutputs(config);
|
|
121
125
|
}
|
|
122
126
|
else {
|
|
123
127
|
validateConfigForData(config);
|
|
124
|
-
switch (
|
|
128
|
+
switch (commandName) {
|
|
125
129
|
case "serve": {
|
|
126
130
|
debug(`running command: serve`);
|
|
127
131
|
validateConfigForServer(config);
|
|
@@ -200,5 +204,7 @@ else {
|
|
|
200
204
|
}
|
|
201
205
|
break;
|
|
202
206
|
}
|
|
207
|
+
default:
|
|
208
|
+
assertExhaustive(commandName);
|
|
203
209
|
}
|
|
204
210
|
}
|
package/dist/src/node/index.d.ts
CHANGED
|
@@ -2,9 +2,8 @@ import type { InstanceContainer, InstanceContainerOverview, InstanceContent, Sin
|
|
|
2
2
|
import type { FormatterOptions } from "./config.ts";
|
|
3
3
|
import { Git } from "./git.js";
|
|
4
4
|
import type { Output } from "./output.ts";
|
|
5
|
-
import {
|
|
6
|
-
import type { AnyChildEntityMap, AnyEntityMap, AnyEnumMap, AnySingletonEntityMap, AnyTypeAliasMap } from "./schema/generatedTypeHelpers.ts";
|
|
7
|
-
import { type IdArgsVariant } from "./schema/generatedTypeHelpers.ts";
|
|
5
|
+
import type { EntityDecl } from "./schema/dsl/index.ts";
|
|
6
|
+
import type { AnyChildEntityMap, AnyEntityMap, AnyEnumMap, AnySingletonEntityMap, AnyTypeAliasMap, IdArgsVariant } from "./schema/generatedTypeHelpers.ts";
|
|
8
7
|
import type { Schema } from "./schema/index.ts";
|
|
9
8
|
import { Transaction } from "./transaction.ts";
|
|
10
9
|
export interface DefaultTSONDBTypes {
|
|
@@ -104,6 +103,7 @@ export interface TranslationValidationOptions {
|
|
|
104
103
|
*/
|
|
105
104
|
matchParametersInKeys?: boolean;
|
|
106
105
|
}
|
|
106
|
+
export type HookTrigger = "transaction:before" | "transaction:after";
|
|
107
107
|
/**
|
|
108
108
|
* The main class for managing a typed JSON database.
|
|
109
109
|
*/
|
|
@@ -318,8 +318,17 @@ export declare class TSONDB<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> {
|
|
|
318
318
|
* @param path The file path where the compressed database will be saved.
|
|
319
319
|
*/
|
|
320
320
|
compressToSingleFile(path: string): Promise<void>;
|
|
321
|
+
/**
|
|
322
|
+
* Registers a new hook for the specified trigger. The callback will be invoked whenever the trigger is activated.
|
|
323
|
+
*/
|
|
324
|
+
registerHook(trigger: HookTrigger, callback: (db: TSONDB<T>) => void): void;
|
|
325
|
+
/**
|
|
326
|
+
* Unregisters a hook for the specified trigger.
|
|
327
|
+
*/
|
|
328
|
+
unregisterHook(trigger: HookTrigger, callback: (db: TSONDB<T>) => void): void;
|
|
321
329
|
}
|
|
322
330
|
export type TSONDBWithGit<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> = TSONDB<T> & {
|
|
323
331
|
git: Git<T>;
|
|
324
332
|
};
|
|
325
333
|
export type { Config } from "./config.ts";
|
|
334
|
+
export { createServer } from "./server/index.ts";
|
package/dist/src/node/index.js
CHANGED
|
@@ -15,8 +15,6 @@ import { countError, countErrors, getErrorMessageForDisplay, HTTPError, wrapErro
|
|
|
15
15
|
import { parallelizeErrors } from "../shared/utils/validation.js";
|
|
16
16
|
import { Git } from "./git.js";
|
|
17
17
|
import { getDisplayName, getDisplayNameWithId } from "./schema/detached.js";
|
|
18
|
-
import {} from "./schema/dsl/index.js";
|
|
19
|
-
import {} from "./schema/generatedTypeHelpers.js";
|
|
20
18
|
import { isEntityDecl, isEntityDeclWithParentReference } from "./schema/guards.js";
|
|
21
19
|
import { serializeNode } from "./schema/treeOperations/serialization.js";
|
|
22
20
|
import { createEntityNameValidator, createReferenceValidator, validateDeclReferentialIntegrity, validateDeclStructuralIntegrity, } from "./schema/treeOperations/validation.js";
|
|
@@ -32,15 +30,10 @@ import { checkUniqueConstraintsForAllEntities } from "./utils/unique.js";
|
|
|
32
30
|
const debug = Debug("tsondb:class");
|
|
33
31
|
const prepareFolders = async (dataRootPath, entities) => {
|
|
34
32
|
await mkdir(dataRootPath, { recursive: true });
|
|
35
|
-
|
|
36
|
-
const entityDir = join(dataRootPath, entity.name);
|
|
37
|
-
await mkdir(entityDir, { recursive: true });
|
|
38
|
-
}
|
|
33
|
+
await Promise.all(entities.map(entity => mkdir(join(dataRootPath, entity.name), { recursive: true })));
|
|
39
34
|
};
|
|
40
35
|
const generateOutputs = async (schema, outputs) => {
|
|
41
|
-
|
|
42
|
-
await output.run(schema);
|
|
43
|
-
}
|
|
36
|
+
await Promise.all(outputs.map(output => output.run(schema)));
|
|
44
37
|
};
|
|
45
38
|
const getGit = async (dataRootPath) => {
|
|
46
39
|
const git = simpleGit({ baseDir: dataRootPath });
|
|
@@ -59,7 +52,7 @@ const getGit = async (dataRootPath) => {
|
|
|
59
52
|
}
|
|
60
53
|
};
|
|
61
54
|
const checkLocales = (schema, data, locales) => {
|
|
62
|
-
const localeEntity = schema
|
|
55
|
+
const { localeEntity } = schema;
|
|
63
56
|
if (localeEntity &&
|
|
64
57
|
!locales.every(locale => data.hasInstanceOfEntityById(localeEntity.name, locale))) {
|
|
65
58
|
throw new Error("All provided locales must exist in the database.");
|
|
@@ -71,7 +64,7 @@ const initData = async (dataRootPath, schema, locales, git, gitStatus, skipRefer
|
|
|
71
64
|
debug("done");
|
|
72
65
|
checkLocales(schema, data, locales);
|
|
73
66
|
const serializedDeclarationsByName = Object.fromEntries(schema.resolvedDeclarations.map(decl => [decl.name, serializeNode(decl)]));
|
|
74
|
-
let referencesToInstances;
|
|
67
|
+
let referencesToInstances = {};
|
|
75
68
|
if (!skipReferenceCache) {
|
|
76
69
|
debug("creating references cache ...");
|
|
77
70
|
referencesToInstances = await getReferencesToInstances(data, serializedDeclarationsByName);
|
|
@@ -79,7 +72,6 @@ const initData = async (dataRootPath, schema, locales, git, gitStatus, skipRefer
|
|
|
79
72
|
}
|
|
80
73
|
else {
|
|
81
74
|
debug("skipping references cache creation");
|
|
82
|
-
referencesToInstances = {};
|
|
83
75
|
}
|
|
84
76
|
if (git) {
|
|
85
77
|
const status = gitStatus ?? (await git.client.status());
|
|
@@ -102,6 +94,7 @@ export class TSONDB {
|
|
|
102
94
|
#formatterOptions;
|
|
103
95
|
#gitWrapper;
|
|
104
96
|
#locked = false;
|
|
97
|
+
#hooks = {};
|
|
105
98
|
constructor(options) {
|
|
106
99
|
this.#dataRootPath = options.dataRootPath;
|
|
107
100
|
this.#data = options.data;
|
|
@@ -124,7 +117,7 @@ export class TSONDB {
|
|
|
124
117
|
* @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.
|
|
125
118
|
*/
|
|
126
119
|
static async create(options, skipReferenceCache = false) {
|
|
127
|
-
const entities = options.schema
|
|
120
|
+
const { entities } = options.schema;
|
|
128
121
|
debug("using data root path: %s", options.dataRootPath);
|
|
129
122
|
await prepareFolders(options.dataRootPath, entities);
|
|
130
123
|
debug("prepared folders");
|
|
@@ -135,7 +128,7 @@ export class TSONDB {
|
|
|
135
128
|
else {
|
|
136
129
|
debug("no git repository found");
|
|
137
130
|
}
|
|
138
|
-
const localeEntity = options.schema
|
|
131
|
+
const { localeEntity } = options.schema;
|
|
139
132
|
if (!localeEntity && options.locales) {
|
|
140
133
|
throw new Error("Schema does not contain a locale entity, locales cannot be provided.");
|
|
141
134
|
}
|
|
@@ -193,6 +186,11 @@ export class TSONDB {
|
|
|
193
186
|
useStyling: true,
|
|
194
187
|
};
|
|
195
188
|
const errors = [];
|
|
189
|
+
const summary = [];
|
|
190
|
+
const debugAndAddToSummary = (message) => {
|
|
191
|
+
debug(message);
|
|
192
|
+
summary.push(message);
|
|
193
|
+
};
|
|
196
194
|
if (!skipStructuralValidation) {
|
|
197
195
|
debug("Checking structural integrity ...");
|
|
198
196
|
const structureErrors = onlyEntities
|
|
@@ -216,7 +214,7 @@ export class TSONDB {
|
|
|
216
214
|
.toSorted((a, b) => a.message.localeCompare(b.message));
|
|
217
215
|
if (structureErrors.length > 0) {
|
|
218
216
|
const errorCount = countErrors(structureErrors);
|
|
219
|
-
|
|
217
|
+
debugAndAddToSummary(`${errorCount.toString()} structural integrity violation${errorCount === 1 ? "" : "s"} found`);
|
|
220
218
|
errors.push(...structureErrors);
|
|
221
219
|
}
|
|
222
220
|
else {
|
|
@@ -250,7 +248,7 @@ export class TSONDB {
|
|
|
250
248
|
.toSorted((a, b) => a.message.localeCompare(b.message));
|
|
251
249
|
if (referenceErrors.length > 0) {
|
|
252
250
|
const errorCount = countErrors(referenceErrors);
|
|
253
|
-
|
|
251
|
+
debugAndAddToSummary(`${errorCount.toString()} referential integrity violation${errorCount === 1 ? "" : "s"} found`);
|
|
254
252
|
errors.push(...referenceErrors);
|
|
255
253
|
}
|
|
256
254
|
else {
|
|
@@ -266,7 +264,7 @@ export class TSONDB {
|
|
|
266
264
|
const uniqueConstraintResult = checkUniqueConstraintsForAllEntities(data, (changedEntities ?? onlyEntities).filter(e => isEntityDecl(e)), instanceOverviewsByEntityName);
|
|
267
265
|
if (isError(uniqueConstraintResult)) {
|
|
268
266
|
const errorCount = countError(uniqueConstraintResult.error);
|
|
269
|
-
|
|
267
|
+
debugAndAddToSummary(`${errorCount.toString()} unique constraint violation${errorCount === 1 ? "" : "s"} found`);
|
|
270
268
|
errors.push(uniqueConstraintResult.error);
|
|
271
269
|
}
|
|
272
270
|
else {
|
|
@@ -276,7 +274,7 @@ export class TSONDB {
|
|
|
276
274
|
const customConstraintResult = checkCustomConstraintsForAllEntities((...args) => getDisplayName(getEntity, this.#locales, data, ...args), (...args) => getDisplayNameWithId(getEntity, this.#locales, data, ...args), (...args) => getInstanceOverview(data.getInstanceContainerOfEntityById.bind(data), data.getAllChildInstanceContainersForParent.bind(data, getEntity), getEntity, this.#locales, args), getEntity, data, entities);
|
|
277
275
|
if (isError(customConstraintResult)) {
|
|
278
276
|
const errorCount = countError(customConstraintResult.error);
|
|
279
|
-
|
|
277
|
+
debugAndAddToSummary(`${errorCount.toString()} custom constraint violation${errorCount === 1 ? "" : "s"} found`);
|
|
280
278
|
errors.push(customConstraintResult.error);
|
|
281
279
|
}
|
|
282
280
|
else {
|
|
@@ -284,7 +282,7 @@ export class TSONDB {
|
|
|
284
282
|
}
|
|
285
283
|
}
|
|
286
284
|
else {
|
|
287
|
-
|
|
285
|
+
debugAndAddToSummary("Skipping further checks due to previous structural integrity errors");
|
|
288
286
|
}
|
|
289
287
|
if (logToConsole) {
|
|
290
288
|
console.log(`${data.totalSize.toString()} instance${data.totalSize === 1 ? "" : "s"} checked`);
|
|
@@ -293,7 +291,14 @@ export class TSONDB {
|
|
|
293
291
|
}
|
|
294
292
|
else {
|
|
295
293
|
const errorCount = countErrors(errors);
|
|
296
|
-
console.error(styleText("red", `${
|
|
294
|
+
console.error(styleText("red", `${errors.map(err => getErrorMessageForDisplay(err)).join("\n\n")}\n`, {
|
|
295
|
+
stream: stderr,
|
|
296
|
+
}));
|
|
297
|
+
console.error(styleText(["red", "bold"], "Summary", { stream: stderr }));
|
|
298
|
+
console.error(styleText("red", [
|
|
299
|
+
`${errorCount.toString()} validation error${errorCount === 1 ? "" : "s"} found`,
|
|
300
|
+
...summary,
|
|
301
|
+
].join("\n"), { stream: stderr }));
|
|
297
302
|
}
|
|
298
303
|
}
|
|
299
304
|
return errors;
|
|
@@ -386,6 +391,7 @@ export class TSONDB {
|
|
|
386
391
|
throw new HTTPError(503, "Another transaction is currently running. Transactions cannot be run concurrently.");
|
|
387
392
|
}
|
|
388
393
|
debug("Starting transaction, locking database ...");
|
|
394
|
+
this.#hooks["transaction:before"]?.forEach(hook => hook(this));
|
|
389
395
|
this.#locked = true;
|
|
390
396
|
try {
|
|
391
397
|
const getEntity = this.#schema.getEntity.bind(this.#schema);
|
|
@@ -434,6 +440,7 @@ export class TSONDB {
|
|
|
434
440
|
this.#referencesToInstances = newRefs;
|
|
435
441
|
this.#locked = false;
|
|
436
442
|
debug("Transaction successful, released lock");
|
|
443
|
+
this.#hooks["transaction:after"]?.forEach(hook => hook(this));
|
|
437
444
|
return res;
|
|
438
445
|
}
|
|
439
446
|
catch (error) {
|
|
@@ -732,4 +739,24 @@ export class TSONDB {
|
|
|
732
739
|
async compressToSingleFile(path) {
|
|
733
740
|
await writeFile(path, JSON.stringify(this.#data), "utf-8");
|
|
734
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* Registers a new hook for the specified trigger. The callback will be invoked whenever the trigger is activated.
|
|
744
|
+
*/
|
|
745
|
+
registerHook(trigger, callback) {
|
|
746
|
+
;
|
|
747
|
+
(this.#hooks[trigger] ??= []).push(callback);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Unregisters a hook for the specified trigger.
|
|
751
|
+
*/
|
|
752
|
+
unregisterHook(trigger, callback) {
|
|
753
|
+
const callbacks = this.#hooks[trigger];
|
|
754
|
+
if (callbacks) {
|
|
755
|
+
const index = callbacks.indexOf(callback);
|
|
756
|
+
if (index !== -1) {
|
|
757
|
+
callbacks.splice(index, 1);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
735
761
|
}
|
|
762
|
+
export { createServer } from "./server/index.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Output } from "../../output.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type JsonSchemaRendererOptions } from "./render.ts";
|
|
3
3
|
export declare const JsonSchemaOutput: (options: {
|
|
4
4
|
targetPath: string;
|
|
5
5
|
rendererOptions?: Partial<JsonSchemaRendererOptions>;
|
|
@@ -17,7 +17,7 @@ export const JsonSchemaOutput = (options) => ({
|
|
|
17
17
|
const sourceRootPath = fileURLToPath(commonPrefix(...Object.keys(declarationsBySourceUrl)));
|
|
18
18
|
debug("common source root path: %s", sourceRootPath);
|
|
19
19
|
if (sourceRootPath) {
|
|
20
|
-
|
|
20
|
+
await Promise.all(Object.entries(declarationsBySourceUrl).map(async ([sourceUrl, decls]) => {
|
|
21
21
|
const sourcePath = fileURLToPath(sourceUrl);
|
|
22
22
|
const relativePath = dirname(relative(sourceRootPath, sourcePath));
|
|
23
23
|
const newDir = join(options.targetPath, relativePath);
|
|
@@ -26,7 +26,7 @@ export const JsonSchemaOutput = (options) => ({
|
|
|
26
26
|
await writeFile(newPath, render(options.rendererOptions, decls ?? []), {
|
|
27
27
|
encoding: "utf-8",
|
|
28
28
|
});
|
|
29
|
-
}
|
|
29
|
+
}));
|
|
30
30
|
debug("emitted declaration files to %s", options.targetPath);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -5,8 +5,8 @@ import { NodeKind } from "../../../shared/schema/Node.js";
|
|
|
5
5
|
import { asDecl } from "../../schema/dsl/declarations/Decl.js";
|
|
6
6
|
import { addEphemeralUUIDToType, createEntityIdentifierTypeAsDecl, isEntityDecl, } from "../../schema/dsl/declarations/EntityDecl.js";
|
|
7
7
|
import { TypeAliasDecl } from "../../schema/dsl/declarations/TypeAliasDecl.js";
|
|
8
|
-
import { isNestedEntityMapType } from "../../schema/dsl/types/NestedEntityMapType.js";
|
|
9
|
-
import { getTypeOfKey } from "../../schema/dsl/types/TranslationObjectType.js";
|
|
8
|
+
import { isNestedEntityMapType, } from "../../schema/dsl/types/NestedEntityMapType.js";
|
|
9
|
+
import { getTypeOfKey, } from "../../schema/dsl/types/TranslationObjectType.js";
|
|
10
10
|
import { flatMapAuxiliaryDecls, isFinalChildEntitiesType } from "../../schema/helpers.js";
|
|
11
11
|
import { ensureSpecialDirStart } from "../../utils/path.js";
|
|
12
12
|
const defaultIndentation = 2;
|
|
@@ -24,7 +24,7 @@ const renderArrayType = (options, type) => ({
|
|
|
24
24
|
const renderObjectType = (options, type) => ({
|
|
25
25
|
type: "object",
|
|
26
26
|
properties: Object.fromEntries(Object.entries(type.properties)
|
|
27
|
-
.filter(([
|
|
27
|
+
.filter(([_key, config]) => !isFinalChildEntitiesType(config.type))
|
|
28
28
|
.map(([name, config]) => [
|
|
29
29
|
name,
|
|
30
30
|
{
|
|
@@ -192,9 +192,7 @@ export const render = (options = defaultOptions, declarations) => {
|
|
|
192
192
|
$defs: renderDeclarations(finalOptions, flatMapAuxiliaryDecls((parentNodes, node, existingDecls) => {
|
|
193
193
|
if (isNestedEntityMapType(node)) {
|
|
194
194
|
if (existingDecls.some(decl => decl.name === node.name)) {
|
|
195
|
-
|
|
196
|
-
// TODO: circumvent by defining the nested entity declaration outside the generic declaration
|
|
197
|
-
return undefined;
|
|
195
|
+
throw new Error("A nested entity map type with the same name has already been defined in this schema. Make sure the nested entity map type is defined only once in the schema. If the nested entity map type is defined in a generic declaration, consider defining it outside the generic declaration to avoid this issue.");
|
|
198
196
|
}
|
|
199
197
|
return TypeAliasDecl(asDecl(parentNodes[0])?.sourceUrl ?? "", {
|
|
200
198
|
name: node.name,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Output } from "../../output.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type TypeScriptRendererOptions } from "./render.ts";
|
|
3
3
|
export declare const TypeScriptOutput: (options: {
|
|
4
4
|
targetPath: string;
|
|
5
5
|
rendererOptions?: Partial<TypeScriptRendererOptions>;
|
|
@@ -18,7 +18,7 @@ export const TypeScriptOutput = (options) => ({
|
|
|
18
18
|
const sourceRootPath = fileURLToPath(commonPrefix(...Object.keys(declarationsBySourceUrl)));
|
|
19
19
|
debug("common source root path: %s", sourceRootPath);
|
|
20
20
|
if (sourceRootPath) {
|
|
21
|
-
|
|
21
|
+
await Promise.all(Object.entries(declarationsBySourceUrl).map(async ([sourceUrl, decls]) => {
|
|
22
22
|
const sourcePath = fileURLToPath(sourceUrl);
|
|
23
23
|
const relativePath = dirname(relative(sourceRootPath, sourcePath));
|
|
24
24
|
const newDir = join(options.targetPath, relativePath);
|
|
@@ -27,7 +27,7 @@ export const TypeScriptOutput = (options) => ({
|
|
|
27
27
|
await writeFile(newPath, pragma + render(options.rendererOptions, decls ?? []), {
|
|
28
28
|
encoding: "utf-8",
|
|
29
29
|
});
|
|
30
|
-
}
|
|
30
|
+
}));
|
|
31
31
|
debug("emitted declaration files to %s", options.targetPath);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -12,9 +12,9 @@ import { addEphemeralUUIDToType, createEntityIdentifierTypeAsDecl, isEntityDecl,
|
|
|
12
12
|
import { isEnumDecl } from "../../schema/dsl/declarations/EnumDecl.js";
|
|
13
13
|
import { isSingletonEntityDecl, } from "../../schema/dsl/declarations/SingletonEntityDecl.js";
|
|
14
14
|
import { isTypeAliasDecl, TypeAliasDecl } from "../../schema/dsl/declarations/TypeAliasDecl.js";
|
|
15
|
-
import { isNestedEntityMapType } from "../../schema/dsl/types/NestedEntityMapType.js";
|
|
16
|
-
import { isObjectType } from "../../schema/dsl/types/ObjectType.js";
|
|
17
|
-
import { getTypeOfKey } from "../../schema/dsl/types/TranslationObjectType.js";
|
|
15
|
+
import { isNestedEntityMapType, } from "../../schema/dsl/types/NestedEntityMapType.js";
|
|
16
|
+
import { isObjectType, } from "../../schema/dsl/types/ObjectType.js";
|
|
17
|
+
import { getTypeOfKey, } from "../../schema/dsl/types/TranslationObjectType.js";
|
|
18
18
|
import { flatMapAuxiliaryDecls, isFinalChildEntitiesType } from "../../schema/helpers.js";
|
|
19
19
|
import { ensureSpecialDirStart } from "../../utils/path.js";
|
|
20
20
|
const defaultOptions = {
|
|
@@ -52,11 +52,9 @@ const renderArrayType = (options, type) => type.minItems !== undefined && type.m
|
|
|
52
52
|
])
|
|
53
53
|
: syntax `${renderType(options, type.items)}[]`;
|
|
54
54
|
const wrapAsObject = (options, str) => syntax `{${EOL}${indent(options.indentation, 1, str)}${EOL}}`;
|
|
55
|
-
const renderObjectType = (options, type) =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.map(([name, config]) => syntax `${renderDocumentation(config.comment, config.isDeprecated)}${name}${config.isRequired ? "" : "?"}: ${renderType(options, config.type)}`), Object.values(type.properties).some(prop => prop.comment !== undefined) ? EOL + EOL : EOL));
|
|
59
|
-
};
|
|
55
|
+
const renderObjectType = (options, type) => wrapAsObject(options, combineSyntaxes(Object.entries(type.properties)
|
|
56
|
+
.filter(([_key, config]) => !isFinalChildEntitiesType(config.type))
|
|
57
|
+
.map(([name, config]) => syntax `${renderDocumentation(config.comment, config.isDeprecated)}${name}${config.isRequired ? "" : "?"}: ${renderType(options, config.type)}`), Object.values(type.properties).some(prop => prop.comment !== undefined) ? EOL + EOL : EOL));
|
|
60
58
|
const renderBooleanType = (_options, _type) => syntax `boolean`;
|
|
61
59
|
const renderDateType = (options, _type) => options.dateType === "Date" ? syntax `Date` : syntax `string`;
|
|
62
60
|
const renderFloatType = (_options, _type) => syntax `number`;
|
|
@@ -64,8 +62,8 @@ const renderIntegerType = (_options, _type) => syntax `number`;
|
|
|
64
62
|
const renderStringType = (_options, _type) => syntax `string`;
|
|
65
63
|
const renderTypeArgumentType = (_options, type) => syntax `${type.argument.name}`;
|
|
66
64
|
const renderReferenceIdentifierType = (_options, type) => [
|
|
67
|
-
{ [type.entity.sourceUrl]: [type.entity.name
|
|
68
|
-
type.entity.name
|
|
65
|
+
{ [type.entity.sourceUrl]: [`${type.entity.name}_ID`] },
|
|
66
|
+
`${type.entity.name}_ID`,
|
|
69
67
|
];
|
|
70
68
|
const renderIncludeIdentifierType = (options, type) => combineSyntaxes([
|
|
71
69
|
[{ [type.reference.sourceUrl]: [type.reference.name] }, type.reference.name],
|
|
@@ -111,9 +109,7 @@ const renderTranslationParameterBrand = (options, params) => {
|
|
|
111
109
|
}
|
|
112
110
|
return syntax ` & { __params: { ${entries.map(([name, type]) => `"${name}": ${type}`).join("; ")} } }`;
|
|
113
111
|
};
|
|
114
|
-
const renderTranslationObjectType = (options, type) => {
|
|
115
|
-
return wrapAsObject(options, combineSyntaxes(Object.entries(type.properties).map(([name, config]) => syntax `"${name.replace('"', '\\"')}"${type.allKeysAreRequired ? "" : "?"}: ${renderType(options, getTypeOfKey(config, type))}${renderTranslationParameterBrand(options, mapParameterTypeNames(extractParameterTypeNamesFromMessage(name), typeName => mapTypeNameToType(options, typeName)))}`), EOL));
|
|
116
|
-
};
|
|
112
|
+
const renderTranslationObjectType = (options, type) => wrapAsObject(options, combineSyntaxes(Object.entries(type.properties).map(([name, config]) => syntax `"${name.replace('"', '\\"')}"${type.allKeysAreRequired ? "" : "?"}: ${renderType(options, getTypeOfKey(config, type))}${renderTranslationParameterBrand(options, mapParameterTypeNames(extractParameterTypeNamesFromMessage(name), typeName => mapTypeNameToType(options, typeName)))}`), EOL));
|
|
117
113
|
const renderType = (options, type) => {
|
|
118
114
|
switch (type.kind) {
|
|
119
115
|
case NodeKind.ArrayType:
|
|
@@ -192,7 +188,7 @@ const renderMapHelperType = (options, declarations, key, defaultName, filterFn,
|
|
|
192
188
|
.filter(filterFn)
|
|
193
189
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
194
190
|
.map(renderKeyFn ??
|
|
195
|
-
(decl => `${decl.name}: ${decl.name}${decl.parameters.length > 0 ?
|
|
191
|
+
(decl => `${decl.name}: ${decl.name}${decl.parameters.length > 0 ? `<${decl.parameters.map(param => (param.constraint ? renderType(options, param.constraint)[1] : "unknown")).join(", ")}>` : ""}`))
|
|
196
192
|
.join(EOL))}${EOL}}${EOL + EOL}`
|
|
197
193
|
: "";
|
|
198
194
|
};
|
|
@@ -205,13 +201,7 @@ const renderStringableTranslationParameterType = (options) => options.inferTrans
|
|
|
205
201
|
(options.inferTranslationParameters.defaultTranslationParameterType === undefined ||
|
|
206
202
|
options.inferTranslationParameters.defaultTranslationParameterType ===
|
|
207
203
|
"StringableTranslationParameter")
|
|
208
|
-
?
|
|
209
|
-
EOL +
|
|
210
|
-
prefixLines(getIndentation(options.indentation, 1), "toString(): string") +
|
|
211
|
-
EOL +
|
|
212
|
-
"}" +
|
|
213
|
-
EOL +
|
|
214
|
-
EOL
|
|
204
|
+
? `export type StringableTranslationParameter = {${EOL}${prefixLines(getIndentation(options.indentation, 1), "toString(): string")}${EOL}}${EOL}${EOL}`
|
|
215
205
|
: "";
|
|
216
206
|
// const renderGetterConstruct = (
|
|
217
207
|
// config: GetterOptions | undefined,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Standalone functions for database operations without needing a full TSONDB instance.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { DatabaseInMemory } from "../utils/databaseInMemory.ts";
|
|
5
5
|
import { type AnyEntityMap, type GetEntityByName, type IdArgsVariant } from "./generatedTypeHelpers.ts";
|
|
6
6
|
export declare const getDisplayName: <EM extends AnyEntityMap>(getEntity: GetEntityByName<EM>, locales: string[], data: DatabaseInMemory<EM>, ...args: IdArgsVariant<EM>) => string | undefined;
|
|
7
7
|
export declare const getDisplayNameWithId: <EM extends AnyEntityMap>(getEntity: GetEntityByName<EM>, locales: string[], data: DatabaseInMemory<EM>, ...args: IdArgsVariant<EM>) => string;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Standalone functions for database operations without needing a full TSONDB instance.
|
|
3
3
|
*/
|
|
4
|
-
import {} from "../utils/databaseInMemory.js";
|
|
5
4
|
import { getDisplayNameFromEntityInstance } from "../utils/displayName.js";
|
|
6
5
|
import { normalizedIdArgs, } from "./generatedTypeHelpers.js";
|
|
7
6
|
export const getDisplayName = (getEntity, locales, data, ...args) => {
|
|
@@ -3,10 +3,10 @@ import type { Node } from "../index.ts";
|
|
|
3
3
|
import type { TypeParameter } from "../TypeParameter.ts";
|
|
4
4
|
import type { EnumCaseDecl } from "../types/EnumType.ts";
|
|
5
5
|
import type { Type } from "../types/Type.ts";
|
|
6
|
-
import type
|
|
7
|
-
import type
|
|
6
|
+
import { type EntityDecl } from "./EntityDecl.ts";
|
|
7
|
+
import { type EnumDecl } from "./EnumDecl.ts";
|
|
8
8
|
import { type SingletonEntityDecl } from "./SingletonEntityDecl.ts";
|
|
9
|
-
import type
|
|
9
|
+
import { type TypeAliasDecl } from "./TypeAliasDecl.ts";
|
|
10
10
|
export type TypeArguments<Params extends TypeParameter[]> = {
|
|
11
11
|
[K in keyof Params]: Params[K] extends TypeParameter<string, infer T> ? T : Type;
|
|
12
12
|
};
|
|
@@ -10,7 +10,7 @@ export const getTypeArgumentsRecord = (decl, args) => Object.fromEntries(
|
|
|
10
10
|
args.slice(0, decl.parameters.length).map((arg, i) => [decl.parameters[i].name, arg]));
|
|
11
11
|
export const isDecl = (node) => isEntityDecl(node) || isSingletonEntityDecl(node) || isEnumDecl(node) || isTypeAliasDecl(node);
|
|
12
12
|
export const asDecl = (node) => node && isDecl(node) ? node : undefined;
|
|
13
|
-
const declNamePattern = /^[a-zA-Z_][a-zA-Z0-9_]
|
|
13
|
+
const declNamePattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/u;
|
|
14
14
|
export const validateDeclName = (name) => {
|
|
15
15
|
if (!declNamePattern.test(name)) {
|
|
16
16
|
throw new Error(`Invalid declaration name "${name}". Declaration names must match the pattern ${declNamePattern.toString()}.`);
|
|
@@ -7,9 +7,9 @@ import type { CustomConstraint, TypedCustomConstraint } from "../../../utils/cus
|
|
|
7
7
|
import type { DisplayNameCustomizer, TypedDisplayNameCustomizer } from "../../../utils/displayName.ts";
|
|
8
8
|
import type { Node } from "../index.ts";
|
|
9
9
|
import type { NestedEntityMapType } from "../types/NestedEntityMapType.ts";
|
|
10
|
-
import type
|
|
10
|
+
import { type MemberDecl, type ObjectType } from "../types/ObjectType.ts";
|
|
11
11
|
import { StringType } from "../types/StringType.ts";
|
|
12
|
-
import type
|
|
12
|
+
import { type BaseDecl } from "./Decl.ts";
|
|
13
13
|
import { TypeAliasDecl } from "./TypeAliasDecl.ts";
|
|
14
14
|
export type EntityDisplayName<T extends TConstraint> = PathTo<T, StringType> | {
|
|
15
15
|
/**
|
|
@@ -41,6 +41,6 @@ export const addEphemeralUUIDToType = (decl) => ({
|
|
|
41
41
|
export const createEntityIdentifierType = () => StringType();
|
|
42
42
|
export const createEntityIdentifierTypeAsDecl = (decl) => TypeAliasDecl(decl.sourceUrl, {
|
|
43
43
|
comment: createEntityIdentifierComment(),
|
|
44
|
-
name:
|
|
44
|
+
name: `${decl.name}_ID`,
|
|
45
45
|
type: createEntityIdentifierType,
|
|
46
46
|
});
|
|
@@ -4,9 +4,8 @@ import { NodeKind } from "../../../../shared/schema/Node.ts";
|
|
|
4
4
|
import type { NestedCustomConstraint, TypedNestedCustomConstraint } from "../../../utils/customConstraints.ts";
|
|
5
5
|
import type { Node } from "../index.ts";
|
|
6
6
|
import type { TypeParameter } from "../TypeParameter.ts";
|
|
7
|
-
import type
|
|
8
|
-
import {
|
|
9
|
-
import type { BaseDecl } from "./Decl.ts";
|
|
7
|
+
import { type EnumCaseDecl, EnumType } from "../types/EnumType.ts";
|
|
8
|
+
import { type BaseDecl } from "./Decl.ts";
|
|
10
9
|
type TConstraint = Record<string, EnumCaseDecl>;
|
|
11
10
|
export interface EnumDecl<Name extends string = string, T extends TConstraint = TConstraint, Params extends TypeParameter[] = TypeParameter[]> extends BaseDecl<Name, Params> {
|
|
12
11
|
kind: NodeKind["EnumDecl"];
|
|
@@ -4,7 +4,7 @@ import type { SortOrder } from "../../../../shared/schema/utils/sortOrder.ts";
|
|
|
4
4
|
import type { CustomConstraint, TypedCustomConstraint } from "../../../utils/customConstraints.ts";
|
|
5
5
|
import type { Node } from "../index.ts";
|
|
6
6
|
import type { MemberDecl, ObjectType } from "../types/ObjectType.ts";
|
|
7
|
-
import type
|
|
7
|
+
import { type BaseDecl } from "./Decl.ts";
|
|
8
8
|
type TConstraint = Record<string, MemberDecl>;
|
|
9
9
|
export interface SingletonEntityDecl<Name extends string = string, T extends TConstraint = TConstraint> extends BaseDecl<Name, []> {
|
|
10
10
|
kind: NodeKind["SingletonEntityDecl"];
|
|
@@ -4,7 +4,7 @@ import type { NestedCustomConstraint, TypedNestedCustomConstraint } from "../../
|
|
|
4
4
|
import type { Node } from "../index.ts";
|
|
5
5
|
import type { TypeParameter } from "../TypeParameter.ts";
|
|
6
6
|
import type { Type } from "../types/Type.ts";
|
|
7
|
-
import type
|
|
7
|
+
import { type BaseDecl } from "./Decl.ts";
|
|
8
8
|
export interface TypeAliasDecl<Name extends string = string, T extends Type = Type, Params extends TypeParameter[] = TypeParameter[]> extends BaseDecl<Name, Params> {
|
|
9
9
|
kind: NodeKind["TypeAliasDecl"];
|
|
10
10
|
type: Lazy<T>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TypeParameter } from "./TypeParameter.ts";
|
|
2
1
|
import type { Decl } from "./declarations/Decl.ts";
|
|
2
|
+
import type { TypeParameter } from "./TypeParameter.ts";
|
|
3
3
|
import type { Type } from "./types/Type.ts";
|
|
4
4
|
export { Param, TypeParameter } from "./TypeParameter.ts";
|
|
5
5
|
export { type Decl, type DeclP, type IncludableDeclP, type SecondaryDecl, } from "./declarations/Decl.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NodeKind } from "../../../../shared/schema/Node.ts";
|
|
2
|
-
import {
|
|
2
|
+
import type { EntityDecl } from "../declarations/EntityDecl.ts";
|
|
3
3
|
import type { Node } from "../index.ts";
|
|
4
4
|
import type { MemberDecl } from "./ObjectType.ts";
|
|
5
5
|
import type { BaseType } from "./Type.ts";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NodeKind } from "../../../../shared/schema/Node.ts";
|
|
2
2
|
import type { IncludableDeclP, TypeArguments } from "../declarations/Decl.ts";
|
|
3
3
|
import type { EnumDecl } from "../declarations/EnumDecl.ts";
|
|
4
|
-
import {
|
|
4
|
+
import type { TypeAliasDecl } from "../declarations/TypeAliasDecl.ts";
|
|
5
5
|
import type { Node } from "../index.ts";
|
|
6
6
|
import type { TypeParameter } from "../TypeParameter.ts";
|
|
7
7
|
import type { EnumCaseDecl } from "./EnumType.ts";
|
|
@@ -4,7 +4,7 @@ import type { EntityDecl } from "../declarations/EntityDecl.ts";
|
|
|
4
4
|
import type { TypeAliasDecl } from "../declarations/TypeAliasDecl.ts";
|
|
5
5
|
import type { Node } from "../index.ts";
|
|
6
6
|
import type { TypeParameter } from "../TypeParameter.ts";
|
|
7
|
-
import {
|
|
7
|
+
import type { IncludeIdentifier } from "./IncludeIdentifierType.ts";
|
|
8
8
|
import type { MemberDecl, ObjectType } from "./ObjectType.ts";
|
|
9
9
|
import type { BaseType } from "./Type.ts";
|
|
10
10
|
type TConstraint = Record<string, MemberDecl>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NodeKind } from "../../../../shared/schema/Node.js";
|
|
2
2
|
import { validateOption } from "../../validation/options.js";
|
|
3
|
-
const keyPattern = /^[a-zA-Z0-9][a-zA-Z0-9_]
|
|
3
|
+
const keyPattern = /^[a-zA-Z0-9][a-zA-Z0-9_]*$/u;
|
|
4
4
|
export const ObjectType = (properties, options = {}) => {
|
|
5
5
|
const type = {
|
|
6
6
|
...options,
|