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.
Files changed (78) hide show
  1. package/dist/src/node/bin/tsondb.js +10 -4
  2. package/dist/src/node/index.d.ts +12 -3
  3. package/dist/src/node/index.js +47 -20
  4. package/dist/src/node/renderers/jsonschema/index.d.ts +1 -1
  5. package/dist/src/node/renderers/jsonschema/index.js +2 -2
  6. package/dist/src/node/renderers/jsonschema/render.js +4 -6
  7. package/dist/src/node/renderers/ts/index.d.ts +1 -1
  8. package/dist/src/node/renderers/ts/index.js +2 -2
  9. package/dist/src/node/renderers/ts/render.js +11 -21
  10. package/dist/src/node/schema/detached.d.ts +1 -1
  11. package/dist/src/node/schema/detached.js +0 -1
  12. package/dist/src/node/schema/dsl/declarations/Decl.d.ts +3 -3
  13. package/dist/src/node/schema/dsl/declarations/Decl.js +1 -1
  14. package/dist/src/node/schema/dsl/declarations/EntityDecl.d.ts +2 -2
  15. package/dist/src/node/schema/dsl/declarations/EntityDecl.js +1 -1
  16. package/dist/src/node/schema/dsl/declarations/EnumDecl.d.ts +2 -3
  17. package/dist/src/node/schema/dsl/declarations/SingletonEntityDecl.d.ts +1 -1
  18. package/dist/src/node/schema/dsl/declarations/TypeAliasDecl.d.ts +1 -1
  19. package/dist/src/node/schema/dsl/index.d.ts +1 -1
  20. package/dist/src/node/schema/dsl/types/ChildEntitiesType.d.ts +1 -1
  21. package/dist/src/node/schema/dsl/types/ChildEntitiesType.js +0 -1
  22. package/dist/src/node/schema/dsl/types/IncludeIdentifierType.d.ts +1 -1
  23. package/dist/src/node/schema/dsl/types/IncludeIdentifierType.js +0 -1
  24. package/dist/src/node/schema/dsl/types/NestedEntityMapType.d.ts +1 -1
  25. package/dist/src/node/schema/dsl/types/NestedEntityMapType.js +0 -1
  26. package/dist/src/node/schema/dsl/types/ObjectType.js +1 -1
  27. package/dist/src/node/schema/helpers.d.ts +4 -4
  28. package/dist/src/node/schema/helpers.js +10 -12
  29. package/dist/src/node/schema/index.d.ts +2 -2
  30. package/dist/src/node/schema/index.js +4 -3
  31. package/dist/src/node/schema/treeOperations/format.js +2 -1
  32. package/dist/src/node/schema/treeOperations/serialization.d.ts +19 -19
  33. package/dist/src/node/schema/treeOperations/serialization.js +0 -18
  34. package/dist/src/node/schema/treeOperations/typeResolution.d.ts +9 -11
  35. package/dist/src/node/schema/treeOperations/typeResolution.js +0 -7
  36. package/dist/src/node/schema/treeOperations/validation.d.ts +1 -1
  37. package/dist/src/node/schema/treeOperations/validation.js +5 -6
  38. package/dist/src/node/schema/validation/options.js +2 -1
  39. package/dist/src/node/server/api/declarations.js +2 -2
  40. package/dist/src/node/server/api/git.js +2 -2
  41. package/dist/src/node/server/index.d.ts +2 -2
  42. package/dist/src/node/server/index.js +2 -2
  43. package/dist/src/node/server/utils/query.js +1 -1
  44. package/dist/src/node/transaction.d.ts +2 -2
  45. package/dist/src/node/transaction.js +0 -2
  46. package/dist/src/node/utils/childInstances.d.ts +1 -1
  47. package/dist/src/node/utils/childInstances.js +3 -2
  48. package/dist/src/node/utils/customConstraints.d.ts +1 -1
  49. package/dist/src/node/utils/customConstraints.js +0 -1
  50. package/dist/src/node/utils/databaseInMemory.js +16 -18
  51. package/dist/src/node/utils/databaseOnDisk.js +8 -0
  52. package/dist/src/node/utils/displayName.d.ts +1 -1
  53. package/dist/src/node/utils/displayName.js +24 -29
  54. package/dist/src/node/utils/files.d.ts +1 -1
  55. package/dist/src/node/utils/files.js +1 -2
  56. package/dist/src/node/utils/path.js +2 -1
  57. package/dist/src/node/utils/references.d.ts +1 -1
  58. package/dist/src/node/utils/references.js +6 -5
  59. package/dist/src/node/utils/unique.d.ts +1 -1
  60. package/dist/src/node/utils/unique.js +8 -17
  61. package/dist/src/shared/schema/declarations/Declaration.d.ts +1 -1
  62. package/dist/src/shared/schema/declarations/Declaration.js +0 -1
  63. package/dist/src/shared/schema/declarations/EnumDecl.js +5 -7
  64. package/dist/src/shared/schema/types/EnumType.js +2 -2
  65. package/dist/src/shared/schema/types/NestedEntityMapType.d.ts +1 -2
  66. package/dist/src/shared/schema/utils/keyPath.js +14 -14
  67. package/dist/src/shared/schema/utils/sortOrder.js +1 -1
  68. package/dist/src/shared/utils/git.js +6 -1
  69. package/dist/src/shared/utils/render.js +3 -3
  70. package/dist/src/shared/utils/translation.js +15 -16
  71. package/dist/src/shared/utils/validation.js +4 -4
  72. package/dist/src/shared/validation/date.js +2 -2
  73. package/dist/src/shared/validation/identifier.js +1 -1
  74. package/dist/src/shared/validation/number.js +3 -3
  75. package/dist/src/shared/validation/object.js +1 -1
  76. package/dist/src/shared/validation/string.js +1 -1
  77. package/dist/src/web/web.js +92 -76
  78. 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 = (config, validationOptions, skipReferenceCache) => TSONDB.create({
114
- ...config,
116
+ const createDB = (dataConfig, validationOptions, skipReferenceCache) => TSONDB.create({
117
+ ...dataConfig,
115
118
  validationOptions,
116
119
  }, skipReferenceCache);
117
- if (passedArguments.command.name === "generate") {
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 (passedArguments.command.name) {
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
  }
@@ -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 { type EntityDecl } from "./schema/dsl/index.ts";
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";
@@ -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
- for (const entity of entities) {
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
- for (const output of outputs) {
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.localeEntity;
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.entities;
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.localeEntity;
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
- debug(`${errorCount.toString()} structural integrity violation${errorCount === 1 ? "" : "s"} found`);
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
- debug(`${errorCount.toString()} referential integrity violation${errorCount === 1 ? "" : "s"} found`);
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
- debug(`${errorCount.toString()} unique constraint violation${errorCount === 1 ? "" : "s"} found`);
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
- debug(`${errorCount.toString()} custom constraint violation${errorCount === 1 ? "" : "s"} found`);
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
- debug("Skipping further checks due to previous structural integrity errors");
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", `${errorCount.toString()} validation error${errorCount === 1 ? "" : "s"} found\n\n${errors.map(err => getErrorMessageForDisplay(err)).join("\n\n")}`, { stream: stderr }));
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 { JsonSchemaRendererOptions } from "./render.ts";
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
- for (const [sourceUrl, decls] of Object.entries(declarationsBySourceUrl)) {
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(([_, config]) => !isFinalChildEntitiesType(config.type))
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
- // this may happen when a nested entity map is defined in a generic declaration and the generic declaration is used multiple times
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 { TypeScriptRendererOptions } from "./render.ts";
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
- for (const [sourceUrl, decls] of Object.entries(declarationsBySourceUrl)) {
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
- return wrapAsObject(options, combineSyntaxes(Object.entries(type.properties)
57
- .filter(([_, config]) => !isFinalChildEntitiesType(config.type))
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 + "_ID"] },
68
- type.entity.name + "_ID",
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 ? "<" + decl.parameters.map(param => (param.constraint ? renderType(options, param.constraint)[1] : "unknown")).join(", ") + ">" : ""}`))
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
- ? "export type StringableTranslationParameter = {" +
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 { type DatabaseInMemory } from "../utils/databaseInMemory.ts";
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 { EntityDecl } from "./EntityDecl.ts";
7
- import type { EnumDecl } from "./EnumDecl.ts";
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 { TypeAliasDecl } from "./TypeAliasDecl.ts";
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 { MemberDecl, ObjectType } from "../types/ObjectType.ts";
10
+ import { type MemberDecl, type ObjectType } from "../types/ObjectType.ts";
11
11
  import { StringType } from "../types/StringType.ts";
12
- import type { BaseDecl } from "./Decl.ts";
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: (decl.name + "_ID"),
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 { EnumCaseDecl } from "../types/EnumType.ts";
8
- import { EnumType } from "../types/EnumType.ts";
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 { BaseDecl } from "./Decl.ts";
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 { BaseDecl } from "./Decl.ts";
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 { type EntityDecl } from "../declarations/EntityDecl.ts";
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,5 +1,4 @@
1
1
  import { NodeKind } from "../../../../shared/schema/Node.js";
2
- import {} from "../declarations/EntityDecl.js";
3
2
  export const ChildEntitiesType = (entity) => ({
4
3
  kind: NodeKind.ChildEntitiesType,
5
4
  entity,
@@ -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 { type TypeAliasDecl } from "../declarations/TypeAliasDecl.ts";
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";
@@ -1,5 +1,4 @@
1
1
  import { NodeKind } from "../../../../shared/schema/Node.js";
2
- import {} from "../declarations/TypeAliasDecl.js";
3
2
  export const GenIncludeIdentifierType = (reference, args) => ({
4
3
  kind: NodeKind.IncludeIdentifierType,
5
4
  reference,
@@ -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 { type IncludeIdentifier } from "./IncludeIdentifierType.ts";
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,5 @@
1
1
  import { Lazy } from "@elyukai/utils/lazy";
2
2
  import { NodeKind } from "../../../../shared/schema/Node.js";
3
- import {} from "./IncludeIdentifierType.js";
4
3
  export const NestedEntityMapType = (options) => {
5
4
  const nestedEntityMapType = {
6
5
  ...options,
@@ -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,