tsondb 0.13.2 → 0.15.0

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 (43) hide show
  1. package/README.md +7 -0
  2. package/dist/src/bin/tsondb.js +2 -2
  3. package/dist/src/node/config.d.ts +6 -2
  4. package/dist/src/node/config.js +5 -2
  5. package/dist/src/node/index.d.ts +4 -4
  6. package/dist/src/node/index.js +35 -18
  7. package/dist/src/node/schema/Schema.d.ts +1 -0
  8. package/dist/src/node/schema/declarations/EntityDecl.d.ts +8 -4
  9. package/dist/src/node/schema/declarations/EntityDecl.js +2 -0
  10. package/dist/src/node/schema/externalTypes.d.ts +20 -0
  11. package/dist/src/node/schema/externalTypes.js +1 -0
  12. package/dist/src/node/schema/helpers.d.ts +4 -0
  13. package/dist/src/node/schema/helpers.js +1 -0
  14. package/dist/src/node/schema/index.d.ts +1 -0
  15. package/dist/src/node/schema/types/references/NestedEntityMapType.d.ts +1 -1
  16. package/dist/src/node/server/api/declarations.js +4 -3
  17. package/dist/src/node/server/api/git.js +4 -3
  18. package/dist/src/node/server/api/instances.js +2 -19
  19. package/dist/src/node/server/api/search.js +4 -3
  20. package/dist/src/node/server/index.d.ts +0 -6
  21. package/dist/src/node/server/init.js +1 -12
  22. package/dist/src/node/server/utils/childInstances.d.ts +3 -2
  23. package/dist/src/node/server/utils/childInstances.js +4 -4
  24. package/dist/src/node/server/utils/instanceOperations.js +3 -3
  25. package/dist/src/node/utils/customConstraints.d.ts +46 -0
  26. package/dist/src/node/utils/customConstraints.js +58 -0
  27. package/dist/src/node/utils/databaseInMemory.d.ts +10 -1
  28. package/dist/src/node/utils/databaseInMemory.js +14 -2
  29. package/dist/src/node/utils/databaseTransactions.d.ts +1 -1
  30. package/dist/src/node/utils/databaseTransactions.js +11 -4
  31. package/dist/src/node/utils/displayName.d.ts +17 -6
  32. package/dist/src/node/utils/displayName.js +22 -1
  33. package/dist/src/node/utils/error.d.ts +1 -1
  34. package/dist/src/node/utils/error.js +8 -4
  35. package/dist/src/node/utils/unique.d.ts +7 -3
  36. package/dist/src/node/utils/unique.js +22 -11
  37. package/dist/src/shared/api.d.ts +1 -1
  38. package/dist/src/shared/utils/dictionary.d.ts +1 -0
  39. package/dist/src/shared/utils/dictionary.js +1 -0
  40. package/dist/src/shared/utils/instances.d.ts +2 -2
  41. package/dist/src/web/components/typeInputs/NestedEntityMapTypeInput.js +3 -3
  42. package/dist/src/web/components/typeInputs/ReferenceIdentifierTypeInput.js +1 -1
  43. package/package.json +1 -1
package/README.md CHANGED
@@ -48,6 +48,13 @@ export default {
48
48
 
49
49
  See the following sections for details on how the different parts come together.
50
50
 
51
+ ## Terms
52
+
53
+ The following list will define common terms used in TSONDB.
54
+
55
+ - **Entity:** The type of group of values, e.g. employees.
56
+ - **Instance:** A singular group of values of an *entity*, e.g. Jane Doe.
57
+
51
58
  ## Define a Schema
52
59
 
53
60
  You define a schema by declarations that consist of types.
@@ -92,7 +92,7 @@ switch (passedArguments.command.name) {
92
92
  case "serve":
93
93
  debug(`running command: serve`);
94
94
  validateConfigForServer(config);
95
- await serve(config.schema, config.dataRootPath, config.defaultLocales, config.homeLayoutSections, config.serverOptions, config.validationOptions, config.customStylesheetPath);
95
+ await serve(config.schema, config.dataRootPath, config.locales, config.homeLayoutSections, config.serverOptions, config.validationOptions, config.customStylesheetPath);
96
96
  break;
97
97
  case "validate":
98
98
  debug(`running command: validate`);
@@ -104,7 +104,7 @@ switch (passedArguments.command.name) {
104
104
  const entities = passedArguments.command.options.checkOnlyEntities;
105
105
  debug(`only check the following entities: ${entities.join(", ")}`);
106
106
  }
107
- await validate(config.schema, config.dataRootPath, {
107
+ await validate(config.schema, config.dataRootPath, config.locales, {
108
108
  ...config.validationOptions,
109
109
  ...omitUndefinedKeys(passedArguments.command.options ?? {}),
110
110
  });
@@ -9,7 +9,10 @@ export type Config = {
9
9
  serverOptions?: ServerOptions;
10
10
  schema: Schema;
11
11
  outputs?: Output[];
12
- defaultLocales?: string[];
12
+ /**
13
+ * Default locales for the server/editor and locales used for testing via CLI.
14
+ */
15
+ locales?: string[];
13
16
  dataRootPath?: string;
14
17
  homeLayoutSections?: HomeLayoutSection[];
15
18
  customStylesheetPath?: string;
@@ -43,7 +46,7 @@ export type DataConfig = {
43
46
  */
44
47
  export type ServerConfig = DataConfig & {
45
48
  serverOptions?: ServerOptions;
46
- defaultLocales: string[];
49
+ locales: string[];
47
50
  homeLayoutSections?: HomeLayoutSection[];
48
51
  validationOptions?: Partial<ValidationOptions>;
49
52
  customStylesheetPath?: string;
@@ -52,6 +55,7 @@ export type ServerConfig = DataConfig & {
52
55
  * The configuration type required for validating the contents of the database.
53
56
  */
54
57
  export type TestingConfig = DataConfig & {
58
+ locales: string[];
55
59
  validationOptions?: Partial<ValidationOptions>;
56
60
  };
57
61
  /**
@@ -4,14 +4,17 @@ export const validateConfigForGeneration = config => {
4
4
  }
5
5
  };
6
6
  export const validateConfigForServer = config => {
7
- if ((config.defaultLocales?.length ?? 0) === 0) {
8
- throw new Error("At least one default locale must be specified in the config.");
7
+ if ((config.locales?.length ?? 0) === 0) {
8
+ throw new Error("At least one locale must be specified in the config.");
9
9
  }
10
10
  if (config.dataRootPath === undefined) {
11
11
  throw new Error("A data root path must be specified in the config.");
12
12
  }
13
13
  };
14
14
  export const validateConfigForTesting = config => {
15
+ if ((config.locales?.length ?? 0) === 0) {
16
+ throw new Error("At least one locale must be specified in the config.");
17
+ }
15
18
  if (config.dataRootPath === undefined) {
16
19
  throw new Error("A data root path must be specified in the config.");
17
20
  }
@@ -14,8 +14,8 @@ export type ValidationOptions = {
14
14
  matchParametersInKeys?: boolean;
15
15
  };
16
16
  };
17
- export declare const validate: (schema: Schema, dataRootPath: string, options?: Partial<ValidationOptions>) => Promise<void>;
18
- export declare const generateAndValidate: (schema: Schema, outputs: Output[], dataRootPath: string, validationOptions?: Partial<ValidationOptions>) => Promise<void>;
19
- export declare const serve: (schema: Schema, dataRootPath: string, defaultLocales: string[], homeLayoutSections?: HomeLayoutSection[], serverOptions?: Partial<ServerOptions>, validationOptions?: Partial<ValidationOptions>, customStylesheetPath?: string) => Promise<void>;
20
- export declare const generateValidateAndServe: (schema: Schema, outputs: Output[], dataRootPath: string, defaultLocales: string[], homeLayoutSections?: HomeLayoutSection[], serverOptions?: Partial<ServerOptions>, validationOptions?: Partial<ValidationOptions>, customStylesheetPath?: string) => Promise<void>;
17
+ export declare const validate: (schema: Schema, dataRootPath: string, locales: string[], options?: Partial<ValidationOptions>) => Promise<void>;
18
+ export declare const generateAndValidate: (schema: Schema, outputs: Output[], dataRootPath: string, locales: string[], validationOptions?: Partial<ValidationOptions>) => Promise<void>;
19
+ export declare const serve: (schema: Schema, dataRootPath: string, locales: string[], homeLayoutSections?: HomeLayoutSection[], serverOptions?: Partial<ServerOptions>, validationOptions?: Partial<ValidationOptions>, customStylesheetPath?: string) => Promise<void>;
20
+ export declare const generateValidateAndServe: (schema: Schema, outputs: Output[], dataRootPath: string, locales: string[], homeLayoutSections?: HomeLayoutSection[], serverOptions?: Partial<ServerOptions>, validationOptions?: Partial<ValidationOptions>, customStylesheetPath?: string) => Promise<void>;
21
21
  export declare const format: (schema: Schema, dataRootPath: string) => Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import Debug from "debug";
2
2
  import { mkdir } from "fs/promises";
3
3
  import { join, sep } from "path";
4
+ import { stderr } from "process";
4
5
  import { styleText } from "util";
5
6
  import { isError } from "../shared/utils/result.js";
6
7
  import { parallelizeErrors } from "../shared/utils/validation.js";
@@ -8,7 +9,9 @@ import { validateEntityDecl } from "./schema/declarations/EntityDecl.js";
8
9
  import { createValidationContext } from "./schema/Node.js";
9
10
  import { getEntities } from "./schema/Schema.js";
10
11
  import { createServer } from "./server/index.js";
11
- import { asyncForEachInstanceInDatabaseInMemory, createDatabaseInMemory, getInstancesOfEntityFromDatabaseInMemory, } from "./utils/databaseInMemory.js";
12
+ import { checkCustomConstraintsForAllEntities } from "./utils/customConstraints.js";
13
+ import { asyncForEachInstanceInDatabaseInMemory, countInstancesInDatabaseInMemory, createDatabaseInMemory, getInstancesOfEntityFromDatabaseInMemory, } from "./utils/databaseInMemory.js";
14
+ import { getAllInstanceOverviewsByEntityName } from "./utils/displayName.js";
12
15
  import { countError, countErrors, getErrorMessageForDisplay, wrapErrorsIfAny, } from "./utils/error.js";
13
16
  import { getFileNameForId, writeInstance } from "./utils/files.js";
14
17
  import { checkUniqueConstraintsForAllEntities } from "./utils/unique.js";
@@ -25,7 +28,7 @@ export const generateOutputs = async (schema, outputs) => {
25
28
  await output.run(schema);
26
29
  }
27
30
  };
28
- const _validate = (dataRootPath, entities, databaseInMemory, options = {}) => {
31
+ const _validate = (dataRootPath, entities, databaseInMemory, locales, options = {}) => {
29
32
  const { checkReferentialIntegrity = true, checkOnlyEntities = [] } = options;
30
33
  for (const onlyEntity of checkOnlyEntities) {
31
34
  if (!entities.find(entity => entity.name === onlyEntity)) {
@@ -48,45 +51,59 @@ const _validate = (dataRootPath, entities, databaseInMemory, options = {}) => {
48
51
  }
49
52
  if (errors.length === 0) {
50
53
  debug("Checking unique constraints ...");
51
- const constraintResult = checkUniqueConstraintsForAllEntities(databaseInMemory, entities);
52
- if (isError(constraintResult)) {
53
- const errorCount = countError(constraintResult.error);
54
+ const entitiesByName = Object.fromEntries(entities.map(entity => [entity.name, entity]));
55
+ const instanceOverviewsByEntityName = getAllInstanceOverviewsByEntityName(entitiesByName, databaseInMemory, locales);
56
+ const uniqueConstraintResult = checkUniqueConstraintsForAllEntities(databaseInMemory, entitiesByName, instanceOverviewsByEntityName);
57
+ if (isError(uniqueConstraintResult)) {
58
+ const errorCount = countError(uniqueConstraintResult.error);
54
59
  debug(`${errorCount.toString()} unique constraint violation${errorCount === 1 ? "" : "s"} found`);
55
- errors.push(constraintResult.error);
60
+ errors.push(uniqueConstraintResult.error);
56
61
  }
57
62
  else {
58
63
  debug("No unique constraint violations found");
59
64
  }
65
+ debug("Checking custom constraints ...");
66
+ const customConstraintResult = checkCustomConstraintsForAllEntities(databaseInMemory, entitiesByName, instanceOverviewsByEntityName);
67
+ if (isError(customConstraintResult)) {
68
+ const errorCount = countError(customConstraintResult.error);
69
+ debug(`${errorCount.toString()} custom constraint violation${errorCount === 1 ? "" : "s"} found`);
70
+ errors.push(customConstraintResult.error);
71
+ }
72
+ else {
73
+ debug("No custom constraint violations found");
74
+ }
60
75
  }
61
76
  else {
62
77
  debug("Skipping unique constraint checks due to previous structural integrity errors");
63
78
  }
79
+ const totalInstanceCount = countInstancesInDatabaseInMemory(databaseInMemory);
80
+ console.log(`${totalInstanceCount.toString()} instance${totalInstanceCount === 1 ? "" : "s"} checked`);
64
81
  if (errors.length === 0) {
65
- console.log("All entities are valid");
82
+ console.log(styleText("green", "All instances are valid"));
66
83
  return true;
67
84
  }
68
85
  else {
69
86
  const errorCount = countErrors(errors);
70
- console.error(`${errorCount.toString()} validation error${errorCount === 1 ? "" : "s"} found\n\n${errors.map(err => getErrorMessageForDisplay(err)).join("\n\n")}`);
87
+ console.error(styleText("red", `${errorCount.toString()} validation error${errorCount === 1 ? "" : "s"} found\n\n${errors.map(err => getErrorMessageForDisplay(err)).join("\n\n")}`, { stream: stderr }));
71
88
  process.exitCode = 1;
72
89
  return false;
73
90
  }
74
91
  };
75
- export const validate = async (schema, dataRootPath, options) => {
92
+ export const validate = async (schema, dataRootPath, locales, options) => {
76
93
  const entities = getEntities(schema);
77
94
  await prepareFolders(dataRootPath, entities);
78
95
  const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
79
- _validate(dataRootPath, entities, databaseInMemory, options);
96
+ _validate(dataRootPath, entities, databaseInMemory, locales, options);
80
97
  };
81
- export const generateAndValidate = async (schema, outputs, dataRootPath, validationOptions) => {
98
+ export const generateAndValidate = async (schema, outputs, dataRootPath, locales, validationOptions) => {
82
99
  await generateOutputs(schema, outputs);
83
100
  const entities = getEntities(schema);
84
101
  await prepareFolders(dataRootPath, entities);
85
102
  const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
86
- _validate(dataRootPath, entities, databaseInMemory, validationOptions);
103
+ _validate(dataRootPath, entities, databaseInMemory, locales, validationOptions);
87
104
  };
88
- export const serve = async (schema, dataRootPath, defaultLocales, homeLayoutSections, serverOptions, validationOptions, customStylesheetPath) => {
89
- if (defaultLocales.length === 0) {
105
+ export const serve = async (schema, dataRootPath, locales, homeLayoutSections, serverOptions, validationOptions, customStylesheetPath) => {
106
+ if (locales.length === 0) {
90
107
  throw new Error("At least one default locale must be specified to start the server.");
91
108
  }
92
109
  const entities = getEntities(schema);
@@ -94,16 +111,16 @@ export const serve = async (schema, dataRootPath, defaultLocales, homeLayoutSect
94
111
  debug("prepared folders");
95
112
  const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
96
113
  debug("loaded instances");
97
- await createServer(schema, dataRootPath, databaseInMemory, defaultLocales, homeLayoutSections, serverOptions, validationOptions, customStylesheetPath);
114
+ await createServer(schema, dataRootPath, databaseInMemory, locales, homeLayoutSections, serverOptions, validationOptions, customStylesheetPath);
98
115
  };
99
- export const generateValidateAndServe = async (schema, outputs, dataRootPath, defaultLocales, homeLayoutSections, serverOptions, validationOptions, customStylesheetPath) => {
116
+ export const generateValidateAndServe = async (schema, outputs, dataRootPath, locales, homeLayoutSections, serverOptions, validationOptions, customStylesheetPath) => {
100
117
  await generateOutputs(schema, outputs);
101
118
  const entities = getEntities(schema);
102
119
  await prepareFolders(dataRootPath, entities);
103
120
  const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
104
- const isValid = _validate(dataRootPath, entities, databaseInMemory, validationOptions);
121
+ const isValid = _validate(dataRootPath, entities, databaseInMemory, locales, validationOptions);
105
122
  if (isValid) {
106
- await createServer(schema, dataRootPath, databaseInMemory, defaultLocales, homeLayoutSections, serverOptions, validationOptions, customStylesheetPath);
123
+ await createServer(schema, dataRootPath, databaseInMemory, locales, homeLayoutSections, serverOptions, validationOptions, customStylesheetPath);
107
124
  }
108
125
  else {
109
126
  console.error("Not starting server due to invalid database");
@@ -6,3 +6,4 @@ export interface Schema {
6
6
  }
7
7
  export declare const Schema: (declarations: Decl[], localeEntity?: EntityDecl) => Schema;
8
8
  export declare const getEntities: (schema: Schema) => EntityDecl[];
9
+ export type * from "./externalTypes.ts";
@@ -1,6 +1,7 @@
1
1
  import type { UniqueConstraints } from "../../../shared/schema/declarations/EntityDecl.ts";
2
2
  import { Lazy } from "../../../shared/utils/lazy.ts";
3
- import type { DisplayNameCustomizer } from "../../utils/displayName.ts";
3
+ import type { CustomConstraint, TypedCustomConstraint } from "../../utils/customConstraints.ts";
4
+ import type { DisplayNameCustomizer, TypedDisplayNameCustomizer } from "../../utils/displayName.ts";
4
5
  import type { GetNestedDeclarations, GetReferences, Predicate, Serialized, TypeArgumentsResolver, Validator } from "../Node.ts";
5
6
  import { NodeKind } from "../Node.ts";
6
7
  import type { MemberDecl, ObjectType } from "../types/generic/ObjectType.ts";
@@ -35,9 +36,10 @@ export interface EntityDecl<Name extends string = string, T extends TConstraint
35
36
  * @default "name"
36
37
  */
37
38
  displayName?: GenericEntityDisplayName;
38
- displayNameCustomizer?: DisplayNameCustomizer<ObjectType<T>>;
39
+ displayNameCustomizer?: DisplayNameCustomizer;
39
40
  isDeprecated?: boolean;
40
41
  uniqueConstraints?: UniqueConstraints;
42
+ customConstraint?: CustomConstraint;
41
43
  }
42
44
  export interface EntityDeclWithParentReference<Name extends string = string, T extends TConstraint = TConstraint, FK extends Extract<keyof T, string> = Extract<keyof T, string>> extends EntityDecl<Name, T, FK> {
43
45
  }
@@ -51,9 +53,10 @@ export declare const EntityDecl: {
51
53
  * @default "name"
52
54
  */
53
55
  displayName?: EntityDisplayName<T>;
54
- displayNameCustomizer?: DisplayNameCustomizer<ObjectType<T>>;
56
+ displayNameCustomizer?: TypedDisplayNameCustomizer<Name>;
55
57
  isDeprecated?: boolean;
56
58
  uniqueConstraints?: UniqueConstraints;
59
+ customConstraint?: TypedCustomConstraint<Name>;
57
60
  }): EntityDecl<Name, T, undefined>;
58
61
  <Name extends string, T extends TConstraint, FK extends Extract<keyof T, string>>(sourceUrl: string, options: {
59
62
  name: Name;
@@ -65,9 +68,10 @@ export declare const EntityDecl: {
65
68
  * @default "name"
66
69
  */
67
70
  displayName?: EntityDisplayName<T>;
68
- displayNameCustomizer?: DisplayNameCustomizer<ObjectType<T>>;
71
+ displayNameCustomizer?: TypedDisplayNameCustomizer<Name>;
69
72
  isDeprecated?: boolean;
70
73
  uniqueConstraints?: UniqueConstraints;
74
+ customConstraint?: TypedCustomConstraint<Name>;
71
75
  }): EntityDecl<Name, T, FK>;
72
76
  };
73
77
  export { EntityDecl as Entity };
@@ -8,6 +8,8 @@ export const EntityDecl = (sourceUrl, options) => {
8
8
  validateDeclName(options.name);
9
9
  return {
10
10
  ...options,
11
+ displayNameCustomizer: options.displayNameCustomizer, // ignore contravariance of registered entity type
12
+ customConstraint: options.customConstraint, // ignore contravariance of registered entity type
11
13
  kind: NodeKind.EntityDecl,
12
14
  sourceUrl,
13
15
  parameters: [],
@@ -0,0 +1,20 @@
1
+ import type { InstanceContent } from "../../shared/utils/instances.ts";
2
+ export interface Register {
3
+ }
4
+ export type AnyEntityMap = Record<string, InstanceContent>;
5
+ export type RegisteredEntityMap<T = Register> = T extends {
6
+ entityMap: AnyEntityMap;
7
+ } ? T["entityMap"] : AnyEntityMap;
8
+ export type RegisteredEntity<Name extends string, T = Register> = T extends {
9
+ entityMap: {
10
+ [K in Name]: InstanceContent;
11
+ };
12
+ } ? T["entityMap"][Name] : InstanceContent;
13
+ export type AnyChildEntityMap = Record<string, [
14
+ content: InstanceContent,
15
+ parentKey: string,
16
+ parentValue: string | object
17
+ ]>;
18
+ export type RegisteredChildEntityMap<T = Register> = T extends {
19
+ childEntityMap: AnyChildEntityMap;
20
+ } ? T["childEntityMap"] : AnyChildEntityMap;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { RegisteredChildEntityMap, RegisteredEntityMap } from "./externalTypes.ts";
2
+ export type GetInstanceById = <U extends keyof RegisteredEntityMap>(entity: U, id: string) => RegisteredEntityMap[U] | undefined;
3
+ export type GetAllInstances = <U extends keyof RegisteredEntityMap>(entity: U) => RegisteredEntityMap[U][];
4
+ export type GetAllChildInstancesForParent = <U extends keyof RegisteredChildEntityMap>(entity: U, parentId: RegisteredChildEntityMap[U][2]) => RegisteredChildEntityMap[U][0][];
@@ -0,0 +1 @@
1
+ export {};
@@ -2,6 +2,7 @@ export * from "./declarations/Declaration.ts";
2
2
  export * from "./declarations/EntityDecl.ts";
3
3
  export * from "./declarations/EnumDecl.ts";
4
4
  export * from "./declarations/TypeAliasDecl.ts";
5
+ export type * from "./helpers.ts";
5
6
  export * from "./Node.ts";
6
7
  export * from "./TypeParameter.ts";
7
8
  export * from "./types/generic/ArrayType.ts";
@@ -28,7 +28,7 @@ export declare const NestedEntityMapType: <Name extends string, T extends TConst
28
28
  * @default "name"
29
29
  */
30
30
  displayName?: EntityDisplayName<T>;
31
- displayNameCustomizer?: DisplayNameCustomizer<ObjectType<T>>;
31
+ displayNameCustomizer?: DisplayNameCustomizer;
32
32
  isDeprecated?: boolean;
33
33
  }) => NestedEntityMapType<Name, T>;
34
34
  export { NestedEntityMapType as NestedEntityMap };
@@ -7,7 +7,7 @@ import { isEnumDecl } from "../../schema/declarations/EnumDecl.js";
7
7
  import { isTypeAliasDecl } from "../../schema/declarations/TypeAliasDecl.js";
8
8
  import { serializeNode } from "../../schema/index.js";
9
9
  import { getChildInstances } from "../../utils/childInstances.js";
10
- import { countInstancesOfEntityInDatabaseInMemory, getInstanceOfEntityFromDatabaseInMemory, getInstancesOfEntityFromDatabaseInMemory, } from "../../utils/databaseInMemory.js";
10
+ import { countInstancesOfEntityInDatabaseInMemory, createInstanceFromDatabaseInMemoryGetter, getInstanceOfEntityFromDatabaseInMemory, getInstancesOfEntityFromDatabaseInMemory, } from "../../utils/databaseInMemory.js";
11
11
  import { HTTPError } from "../../utils/error.js";
12
12
  import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
13
13
  import { createInstance, deleteInstance, updateInstance } from "../utils/instanceOperations.js";
@@ -64,10 +64,11 @@ declarationsApi.get("/:name/instances", (req, res) => {
64
64
  res.status(400).send(`Declaration "${decl.name}" is not an entity`);
65
65
  return;
66
66
  }
67
- const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
67
+ const getInstanceById = createInstanceFromDatabaseInMemoryGetter(req.databaseInMemory, req.entitiesByName);
68
+ const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req.entitiesByName, req.databaseInMemory);
68
69
  const body = {
69
70
  instances: getInstancesOfEntityFromDatabaseInMemory(req.databaseInMemory, decl.name)
70
- .map(instanceContainer => getInstanceContainerOverview(decl, instanceContainer, req.getInstanceById, getChildInstancesForInstanceId, req.locales))
71
+ .map(instanceContainer => getInstanceContainerOverview(decl, instanceContainer, getInstanceById, getChildInstancesForInstanceId, req.locales))
71
72
  .toSorted((a, b) => a.displayName.localeCompare(b.displayName, undefined, { numeric: true })),
72
73
  isLocaleEntity: decl === req.localeEntity,
73
74
  };
@@ -3,7 +3,7 @@ import express, {} from "express";
3
3
  import { join } from "node:path";
4
4
  import { hasFileChanges, splitBranchName } from "../../../shared/utils/git.js";
5
5
  import { getInstanceContainerOverview } from "../../../shared/utils/instances.js";
6
- import { getGroupedInstancesFromDatabaseInMemory } from "../../utils/databaseInMemory.js";
6
+ import { createInstanceFromDatabaseInMemoryGetter, getGroupedInstancesFromDatabaseInMemory, } from "../../utils/databaseInMemory.js";
7
7
  import { attachGitStatusToDatabaseInMemory } from "../../utils/git.js";
8
8
  import { reinit } from "../init.js";
9
9
  import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
@@ -28,7 +28,8 @@ gitApi.get("/status", async (req, res) => {
28
28
  req.setLocal("databaseInMemory", attachGitStatusToDatabaseInMemory(req.databaseInMemory, req.dataRoot,
29
29
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
30
30
  req.gitRoot, status));
31
- const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
31
+ const getInstanceById = createInstanceFromDatabaseInMemoryGetter(req.databaseInMemory, req.entitiesByName);
32
+ const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req.entitiesByName, req.databaseInMemory);
32
33
  const body = {
33
34
  currentBranch: status.current,
34
35
  trackingBranch: status.tracking,
@@ -40,7 +41,7 @@ gitApi.get("/status", async (req, res) => {
40
41
  .filter(instance => hasFileChanges(instance.gitStatus))
41
42
  .map(instance => getInstanceContainerOverview(
42
43
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
43
- req.entitiesByName[entityName], instance, req.getInstanceById, getChildInstancesForInstanceId, req.locales)),
44
+ req.entitiesByName[entityName], instance, getInstanceById, getChildInstancesForInstanceId, req.locales)),
44
45
  ])),
45
46
  latestCommit: await req.git.revparse(["HEAD"]),
46
47
  };
@@ -1,8 +1,6 @@
1
1
  import Debug from "debug";
2
2
  import express from "express";
3
- import { getGroupedInstancesFromDatabaseInMemory } from "../../utils/databaseInMemory.js";
4
- import { getDisplayNameFromEntityInstance } from "../../utils/displayName.js";
5
- import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
3
+ import { getAllInstanceOverviewsByEntityName } from "../../utils/displayName.js";
6
4
  const debug = Debug("tsondb:server:api:instances");
7
5
  export const instancesApi = express.Router();
8
6
  instancesApi.use((req, _res, next) => {
@@ -10,23 +8,8 @@ instancesApi.use((req, _res, next) => {
10
8
  next();
11
9
  });
12
10
  instancesApi.get("/", (req, res) => {
13
- const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
14
11
  const body = {
15
- instances: Object.fromEntries(getGroupedInstancesFromDatabaseInMemory(req.databaseInMemory).map(([entityName, instances]) => [
16
- entityName,
17
- instances
18
- .map(instance => {
19
- const { name, localeId } = getDisplayNameFromEntityInstance(
20
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21
- req.entitiesByName[entityName], instance, req.getInstanceById, getChildInstancesForInstanceId, req.locales);
22
- return {
23
- id: instance.id,
24
- name,
25
- displayNameLocaleId: localeId,
26
- };
27
- })
28
- .toSorted((a, b) => a.name.localeCompare(b.name, a.displayNameLocaleId)),
29
- ])),
12
+ instances: getAllInstanceOverviewsByEntityName(req.entitiesByName, req.databaseInMemory, req.locales),
30
13
  };
31
14
  res.json(body);
32
15
  });
@@ -1,7 +1,7 @@
1
1
  import Debug from "debug";
2
2
  import express from "express";
3
3
  import { isEntityDeclWithParentReference } from "../../schema/index.js";
4
- import { getGroupedInstancesFromDatabaseInMemory } from "../../utils/databaseInMemory.js";
4
+ import { createInstanceFromDatabaseInMemoryGetter, getGroupedInstancesFromDatabaseInMemory, } from "../../utils/databaseInMemory.js";
5
5
  import { getDisplayNameFromEntityInstance } from "../../utils/displayName.js";
6
6
  import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
7
7
  import { getQueryParamString } from "../utils/query.js";
@@ -10,7 +10,8 @@ export const searchApi = express.Router();
10
10
  searchApi.get("/", (req, res) => {
11
11
  const query = getQueryParamString(req.query, "q")?.toLowerCase() ?? "";
12
12
  debug('search for items containing "%s"', query);
13
- const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
13
+ const getInstanceById = createInstanceFromDatabaseInMemoryGetter(req.databaseInMemory, req.entitiesByName);
14
+ const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req.entitiesByName, req.databaseInMemory);
14
15
  const body = {
15
16
  query,
16
17
  results: query.length === 0
@@ -25,7 +26,7 @@ searchApi.get("/", (req, res) => {
25
26
  .map((instance) => {
26
27
  const { name, localeId } = getDisplayNameFromEntityInstance(
27
28
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
28
- req.entitiesByName[entityName], instance, req.getInstanceById, getChildInstancesForInstanceId, req.locales);
29
+ req.entitiesByName[entityName], instance, getInstanceById, getChildInstancesForInstanceId, req.locales);
29
30
  const searchableName = name.toLowerCase();
30
31
  return [
31
32
  entityName,
@@ -1,6 +1,5 @@
1
1
  import type { SimpleGit } from "simple-git";
2
2
  import type { SerializedDecl } from "../../shared/schema/declarations/Declaration.ts";
3
- import type { InstanceContainer } from "../../shared/utils/instances.ts";
4
3
  import type { HomeLayoutSection } from "../config.ts";
5
4
  import type { ValidationOptions } from "../index.ts";
6
5
  import type { Decl } from "../schema/declarations/Declaration.ts";
@@ -26,13 +25,8 @@ export interface TSONDBRequestLocals {
26
25
  locales: string[];
27
26
  homeLayoutSections?: HomeLayoutSection[];
28
27
  validationOptions: Partial<ValidationOptions>;
29
- getInstanceById: GetInstanceById;
30
28
  setLocal: <K extends keyof Omit<TSONDBRequestLocals, "setLocal">>(key: K, value: TSONDBRequestLocals[K]) => void;
31
29
  }
32
- export type GetInstanceById = (id: string) => {
33
- entity: EntityDecl;
34
- instance: InstanceContainer;
35
- } | undefined;
36
30
  declare global {
37
31
  namespace Express {
38
32
  interface Request extends TSONDBRequestLocals {
@@ -2,7 +2,7 @@ import Debug from "debug";
2
2
  import { simpleGit } from "simple-git";
3
3
  import { isEntityDecl } from "../schema/declarations/EntityDecl.js";
4
4
  import { resolveTypeArgumentsInDecls, serializeNode } from "../schema/index.js";
5
- import { createDatabaseInMemory, getInstanceFromDatabaseInMemory, } from "../utils/databaseInMemory.js";
5
+ import { createDatabaseInMemory } from "../utils/databaseInMemory.js";
6
6
  import { attachGitStatusToDatabaseInMemory } from "../utils/git.js";
7
7
  import { getReferencesToInstances } from "../utils/references.js";
8
8
  const debug = Debug("tsondb:server:init");
@@ -36,16 +36,6 @@ export const init = async (schema, dataRootPath, databaseInMemory, defaultLocale
36
36
  databaseInMemory = attachGitStatusToDatabaseInMemory(databaseInMemory, dataRootPath, gitRoot, gitStatus);
37
37
  debug("retrieved git status to instances");
38
38
  }
39
- const getInstanceById = id => {
40
- const res = getInstanceFromDatabaseInMemory(requestLocals.databaseInMemory, id);
41
- if (res) {
42
- const [entityName, instance] = res;
43
- if (requestLocals.entitiesByName[entityName]) {
44
- return { entity: requestLocals.entitiesByName[entityName], instance };
45
- }
46
- }
47
- return undefined;
48
- };
49
39
  const requestLocals = {
50
40
  git: git,
51
41
  gitRoot: gitRoot,
@@ -56,7 +46,6 @@ export const init = async (schema, dataRootPath, databaseInMemory, defaultLocale
56
46
  entitiesByName: entitiesByName,
57
47
  serializedDeclarationsByName,
58
48
  localeEntity: schema.localeEntity,
59
- getInstanceById,
60
49
  referencesToInstances,
61
50
  defaultLocales,
62
51
  locales: defaultLocales,
@@ -1,3 +1,4 @@
1
+ import { type EntityDecl } from "../../schema/index.ts";
2
+ import type { DatabaseInMemory } from "../../utils/databaseInMemory.ts";
1
3
  import type { GetChildInstancesForInstanceId } from "../../utils/displayName.ts";
2
- import type { TSONDBRequestLocals } from "../index.ts";
3
- export declare const createChildInstancesForInstanceIdGetter: (locals: TSONDBRequestLocals) => GetChildInstancesForInstanceId;
4
+ export declare const createChildInstancesForInstanceIdGetter: (entitiesByName: Record<string, EntityDecl>, databaseInMemory: DatabaseInMemory) => GetChildInstancesForInstanceId;
@@ -1,12 +1,12 @@
1
1
  import { isEntityDeclWithParentReference } from "../../schema/index.js";
2
2
  import { getChildInstancesFromEntity } from "../../utils/childInstances.js";
3
- export const createChildInstancesForInstanceIdGetter = (locals) => (parentEntityName, parentId, childEntityName) => {
4
- const parentEntity = locals.entitiesByName[parentEntityName];
5
- const childEntity = locals.entitiesByName[childEntityName];
3
+ export const createChildInstancesForInstanceIdGetter = (entitiesByName, databaseInMemory) => (parentEntityName, parentId, childEntityName) => {
4
+ const parentEntity = entitiesByName[parentEntityName];
5
+ const childEntity = entitiesByName[childEntityName];
6
6
  if (parentEntity === undefined ||
7
7
  childEntity === undefined ||
8
8
  !isEntityDeclWithParentReference(childEntity)) {
9
9
  return [];
10
10
  }
11
- return getChildInstancesFromEntity(locals.databaseInMemory, parentEntity, parentId, childEntity);
11
+ return getChildInstancesFromEntity(databaseInMemory, parentEntity, parentId, childEntity);
12
12
  };
@@ -8,7 +8,7 @@ export const createInstance = async (locals, instance, idQueryParam) => {
8
8
  if (entity === undefined) {
9
9
  return error(new HTTPError(400, "Entity not found"));
10
10
  }
11
- const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.validationOptions, locals.entitiesByName, undefined, undefined, locals.localeEntity, instance.entityName, undefined, instance, idQueryParam, res));
11
+ const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, locals.locales, res => saveInstanceTree(locals.validationOptions, locals.entitiesByName, undefined, undefined, locals.localeEntity, instance.entityName, undefined, instance, idQueryParam, res));
12
12
  if (isError(databaseTransactionResult)) {
13
13
  return databaseTransactionResult;
14
14
  }
@@ -27,7 +27,7 @@ export const updateInstance = async (locals, instance) => {
27
27
  return error(new HTTPError(400, "Entity not found"));
28
28
  }
29
29
  const oldChildInstances = getChildInstances(locals.databaseInMemory, entity, instance.id, true);
30
- const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.validationOptions, locals.entitiesByName, undefined, undefined, locals.localeEntity, instance.entityName, {
30
+ const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, locals.locales, res => saveInstanceTree(locals.validationOptions, locals.entitiesByName, undefined, undefined, locals.localeEntity, instance.entityName, {
31
31
  id: instance.id,
32
32
  content: instanceContainer.content,
33
33
  childInstances: oldChildInstances,
@@ -51,7 +51,7 @@ export const deleteInstance = async (locals, entityName, instanceId) => {
51
51
  return error(new HTTPError(400, "Entity not found"));
52
52
  }
53
53
  const oldChildInstances = getChildInstances(locals.databaseInMemory, entity, instanceId, true);
54
- const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.validationOptions, locals.entitiesByName, undefined, undefined, locals.localeEntity, entityName, {
54
+ const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, locals.locales, res => saveInstanceTree(locals.validationOptions, locals.entitiesByName, undefined, undefined, locals.localeEntity, entityName, {
55
55
  id: instanceId,
56
56
  content: instanceContainer.content,
57
57
  childInstances: oldChildInstances,
@@ -0,0 +1,46 @@
1
+ import type { InstanceContainerOverview, InstanceContent } from "../../shared/utils/instances.ts";
2
+ import { type Result } from "../../shared/utils/result.ts";
3
+ import type { RegisteredEntity } from "../schema/externalTypes.ts";
4
+ import type { GetAllChildInstancesForParent, GetAllInstances, GetInstanceById } from "../schema/helpers.ts";
5
+ import type { EntityDecl } from "../schema/index.ts";
6
+ import { type DatabaseInMemory } from "./databaseInMemory.ts";
7
+ /**
8
+ * A constraint that can be defined on an entity to enforce custom validation logic.
9
+ *
10
+ * The constraint function receives the instance to validate and helper functions
11
+ * to retrieve other instances from the database.
12
+ *
13
+ * It should return an array of strings describing the parts of the constraint
14
+ * that were violated. If the array is empty, the instance is considered valid.
15
+ */
16
+ export type CustomConstraint = (params: {
17
+ instanceId: string;
18
+ instanceContent: InstanceContent;
19
+ getInstanceById: GetInstanceById;
20
+ getAllInstance: GetAllInstances;
21
+ getAllChildInstancesForParent: GetAllChildInstancesForParent;
22
+ }) => string[];
23
+ /**
24
+ * A constraint that can be defined on an entity to enforce custom validation logic.
25
+ *
26
+ * The constraint function receives the instance to validate and helper functions
27
+ * to retrieve other instances from the database.
28
+ *
29
+ * It should return an array of strings describing the parts of the constraint
30
+ * that were violated. If the array is empty, the instance is considered valid.
31
+ */
32
+ export type TypedCustomConstraint<Name extends string> = (params: {
33
+ instanceId: string;
34
+ instanceContent: RegisteredEntity<Name>;
35
+ getInstanceById: GetInstanceById;
36
+ getAllInstance: GetAllInstances;
37
+ getAllChildInstancesForParent: GetAllChildInstancesForParent;
38
+ }) => string[];
39
+ /**
40
+ * Checks all custom constraints for all provided entities and their instances.
41
+ *
42
+ * Returns `Ok` when no violations have been found and an `Error` with a list of
43
+ * `AggregateError`s for each entity if there are any violations of any custom
44
+ * constraint.
45
+ */
46
+ export declare const checkCustomConstraintsForAllEntities: (db: DatabaseInMemory, entitiesByName: Record<string, EntityDecl>, instanceOverviewsByEntityName: Record<string, InstanceContainerOverview[]>) => Result<void, AggregateError>;
@@ -0,0 +1,58 @@
1
+ import { deepEqual } from "../../shared/utils/compare.js";
2
+ import { error, isError, mapError, ok } from "../../shared/utils/result.js";
3
+ import { getInstanceOfEntityFromDatabaseInMemory, getInstancesOfEntityFromDatabaseInMemory, } from "./databaseInMemory.js";
4
+ /**
5
+ * Checks all custom constraints for all provided entities and their instances.
6
+ *
7
+ * Returns `Ok` when no violations have been found and an `Error` with a list of
8
+ * `AggregateError`s for each entity if there are any violations of any custom
9
+ * constraint.
10
+ */
11
+ export const checkCustomConstraintsForAllEntities = (db, entitiesByName, instanceOverviewsByEntityName) => {
12
+ const accessors = {
13
+ getInstanceById: (entityName, id) => getInstanceOfEntityFromDatabaseInMemory(db, entityName, id),
14
+ getAllInstance: entityName => getInstancesOfEntityFromDatabaseInMemory(db, entityName),
15
+ getAllChildInstancesForParent: (entityName, parentId) => {
16
+ const entity = entitiesByName[entityName];
17
+ if (!entity || !entity.parentReferenceKey) {
18
+ return [];
19
+ }
20
+ const parentKey = entity.parentReferenceKey;
21
+ return getInstancesOfEntityFromDatabaseInMemory(db, entityName).filter(instance => deepEqual(instance.content[parentKey], parentId));
22
+ },
23
+ };
24
+ return mapError(Object.values(entitiesByName).reduce((acc, entity) => {
25
+ const constraintFn = entity.customConstraint;
26
+ if (!constraintFn) {
27
+ return acc;
28
+ }
29
+ const errors = getInstancesOfEntityFromDatabaseInMemory(db, entity.name)
30
+ .map((instance) => [
31
+ instance,
32
+ constraintFn({
33
+ ...accessors,
34
+ instanceId: instance.id,
35
+ instanceContent: instance.content,
36
+ }),
37
+ ])
38
+ .filter(([, violations]) => violations.length > 0)
39
+ .map(([instance, violations]) => {
40
+ const instanceOverview = instanceOverviewsByEntityName[entity.name]?.find(o => o.id === instance.id);
41
+ const name = instanceOverview
42
+ ? `"${instanceOverview.displayName}" (${instance.id})`
43
+ : instance.id;
44
+ return new AggregateError(violations.map(violation => new Error(violation)), `in instance ${name}`);
45
+ });
46
+ const aggregate = errors.length > 0 ? new AggregateError(errors, `in entity "${entity.name}"`) : undefined;
47
+ if (isError(acc)) {
48
+ if (aggregate) {
49
+ return error([...acc.error, aggregate]);
50
+ }
51
+ return acc;
52
+ }
53
+ if (aggregate) {
54
+ return error([aggregate]);
55
+ }
56
+ return acc;
57
+ }, ok()), errors => new AggregateError(errors.toSorted((a, b) => a.message.localeCompare(b.message)), "at least one custom constraint has been violated"));
58
+ };
@@ -4,7 +4,15 @@ import type { EntityDecl } from "../schema/index.ts";
4
4
  export type DatabaseInMemory = Dictionary<InstancesInMemory>;
5
5
  export type InstancesInMemory = Dictionary<InstanceContainer>;
6
6
  export declare const emptyDatabaseInMemory: DatabaseInMemory;
7
- export declare const getInstanceFromDatabaseInMemory: (db: DatabaseInMemory, instanceId: string) => [entityName: string, InstanceContainer] | undefined;
7
+ export declare const getInstanceFromDatabaseInMemory: (db: DatabaseInMemory, instanceId: string) => {
8
+ entityName: string;
9
+ instance: InstanceContainer;
10
+ } | undefined;
11
+ export type InstanceFromDatabaseInMemoryGetter = (instanceId: string) => {
12
+ entity: EntityDecl;
13
+ instance: InstanceContainer;
14
+ } | undefined;
15
+ export declare const createInstanceFromDatabaseInMemoryGetter: (db: DatabaseInMemory, entitiesByName: Record<string, EntityDecl>) => InstanceFromDatabaseInMemoryGetter;
8
16
  export declare const getInstanceOfEntityFromDatabaseInMemory: (db: DatabaseInMemory, entityName: string, instanceId: string) => InstanceContainer | undefined;
9
17
  export declare const getInstancesOfEntityFromDatabaseInMemory: (db: DatabaseInMemory, entityName: string) => InstanceContainer[];
10
18
  export declare const getGroupedInstancesFromDatabaseInMemory: (db: DatabaseInMemory) => [entityName: string, InstanceContainer[]][];
@@ -12,6 +20,7 @@ export declare const forEachInstanceOfEntityInDatabaseInMemory: (db: DatabaseInM
12
20
  export declare const asyncForEachInstanceOfEntityInDatabaseInMemory: (db: DatabaseInMemory, entityName: string, fn: (instance: InstanceContainer) => Promise<void>) => Promise<void>;
13
21
  export declare const forEachInstanceInDatabaseInMemory: (db: DatabaseInMemory, fn: (entityName: string, instance: InstanceContainer) => void) => void;
14
22
  export declare const asyncForEachInstanceInDatabaseInMemory: (db: DatabaseInMemory, fn: (entityName: string, instance: InstanceContainer) => Promise<void>) => Promise<void>;
23
+ export declare const countInstancesInDatabaseInMemory: (db: DatabaseInMemory) => number;
15
24
  export declare const countInstancesOfEntityInDatabaseInMemory: (db: DatabaseInMemory, entityName: string) => number;
16
25
  export declare const createDatabaseInMemory: (dataRoot: string, entities: readonly EntityDecl[]) => Promise<DatabaseInMemory>;
17
26
  export declare const setInstanceInDatabaseInMemory: (db: DatabaseInMemory, entityName: string, instance: InstanceContainer) => [DatabaseInMemory, oldInstance: InstanceContent | undefined];
@@ -4,15 +4,26 @@ import { basename, extname, join } from "node:path";
4
4
  import { platform } from "node:process";
5
5
  import { promisify } from "node:util";
6
6
  import { mapAsync } from "../../shared/utils/async.js";
7
- import { emptyD, forEachAsyncD, forEachD, fromEntriesD, getD, getMapD, hasD, mapFirstD, removeD, setD, sizeD, toEntriesD, toValuesD, } from "../../shared/utils/dictionary.js";
7
+ import { emptyD, forEachAsyncD, forEachD, fromEntriesD, getD, getMapD, hasD, mapFirstD, reduceD, removeD, setD, sizeD, toEntriesD, toValuesD, } from "../../shared/utils/dictionary.js";
8
8
  export const emptyDatabaseInMemory = emptyD;
9
9
  export const getInstanceFromDatabaseInMemory = (db, instanceId) => mapFirstD(db, (instances, entityName) => {
10
10
  const instance = getD(instances, instanceId);
11
11
  if (instance) {
12
- return [entityName, instance];
12
+ return { entityName, instance };
13
13
  }
14
14
  return undefined;
15
15
  });
16
+ export const createInstanceFromDatabaseInMemoryGetter = (db, entitiesByName) => instanceId => {
17
+ const res = getInstanceFromDatabaseInMemory(db, instanceId);
18
+ if (res) {
19
+ const { entityName, instance } = res;
20
+ const entity = entitiesByName[entityName];
21
+ if (entity) {
22
+ return { entity, instance };
23
+ }
24
+ }
25
+ return undefined;
26
+ };
16
27
  export const getInstanceOfEntityFromDatabaseInMemory = (db, entityName, instanceId) => getMapD(db, entityName, instances => getD(instances, instanceId));
17
28
  export const getInstancesOfEntityFromDatabaseInMemory = (db, entityName) => getMapD(db, entityName, toValuesD) ?? [];
18
29
  export const getGroupedInstancesFromDatabaseInMemory = (db) => toEntriesD(db).map(([entityName, instances]) => [entityName, Object.values(instances[0])]);
@@ -34,6 +45,7 @@ export const forEachInstanceInDatabaseInMemory = (db, fn) => {
34
45
  });
35
46
  };
36
47
  export const asyncForEachInstanceInDatabaseInMemory = async (db, fn) => forEachAsyncD(db, (instances, entityName) => forEachAsyncD(instances, instance => fn(entityName, instance)));
48
+ export const countInstancesInDatabaseInMemory = (db) => reduceD(db, (sum, instances) => sum + sizeD(instances), 0);
37
49
  export const countInstancesOfEntityInDatabaseInMemory = (db, entityName) => getMapD(db, entityName, sizeD) ?? 0;
38
50
  const exec = promisify(child_process.exec);
39
51
  const ulimit = platform === "win32" ? 2048 : Number.parseInt((await exec("ulimit -n")).stdout);
@@ -25,7 +25,7 @@ export type TransactionResult<A extends object = object> = Result<{
25
25
  /**
26
26
  * Run a transaction on the database in memory, applying all changes to disk if successful.
27
27
  */
28
- export declare const runDatabaseTransaction: (root: string, git: SimpleGit | undefined, entitiesByName: Record<string, EntityDecl>, database: DatabaseInMemory, references: ReferencesToInstances, transactionFn: (db: TransactionResult) => TransactionResult<{
28
+ export declare const runDatabaseTransaction: (root: string, git: SimpleGit | undefined, entitiesByName: Record<string, EntityDecl>, database: DatabaseInMemory, references: ReferencesToInstances, locales: string[], transactionFn: (db: TransactionResult) => TransactionResult<{
29
29
  instanceContainer: InstanceContainer;
30
30
  }>) => Promise<Result<{
31
31
  db: DatabaseInMemory;
@@ -1,13 +1,15 @@
1
1
  import { isError, map, ok } from "../../shared/utils/result.js";
2
+ import { checkCustomConstraintsForAllEntities } from "./customConstraints.js";
2
3
  import { deleteInstanceInDatabaseInMemory, setInstanceInDatabaseInMemory, } from "./databaseInMemory.js";
3
4
  import { applyStepsToDisk } from "./databaseOnDisk.js";
5
+ import { getAllInstanceOverviewsByEntityName } from "./displayName.js";
4
6
  import { attachGitStatusToDatabaseInMemory } from "./git.js";
5
7
  import { updateReferencesToInstances } from "./references.js";
6
8
  import { checkUniqueConstraintsForAllEntities } from "./unique.js";
7
9
  /**
8
10
  * Run a transaction on the database in memory, applying all changes to disk if successful.
9
11
  */
10
- export const runDatabaseTransaction = async (root, git, entitiesByName, database, references, transactionFn) => {
12
+ export const runDatabaseTransaction = async (root, git, entitiesByName, database, references, locales, transactionFn) => {
11
13
  const result = transactionFn(ok({
12
14
  db: database,
13
15
  entitiesByName,
@@ -18,9 +20,14 @@ export const runDatabaseTransaction = async (root, git, entitiesByName, database
18
20
  return result;
19
21
  }
20
22
  const { db: newDb, refs: newRefs, steps, instanceContainer } = result.value;
21
- const constraintResult = checkUniqueConstraintsForAllEntities(newDb, Object.values(entitiesByName));
22
- if (isError(constraintResult)) {
23
- return constraintResult;
23
+ const instanceOverviewsByEntityName = getAllInstanceOverviewsByEntityName(entitiesByName, newDb, locales);
24
+ const uniqueConstraintResult = checkUniqueConstraintsForAllEntities(newDb, entitiesByName, instanceOverviewsByEntityName);
25
+ if (isError(uniqueConstraintResult)) {
26
+ return uniqueConstraintResult;
27
+ }
28
+ const customConstraintResult = checkCustomConstraintsForAllEntities(newDb, entitiesByName, instanceOverviewsByEntityName);
29
+ if (isError(customConstraintResult)) {
30
+ return customConstraintResult;
24
31
  }
25
32
  const diskResult = await applyStepsToDisk(root, steps);
26
33
  if (isError(diskResult)) {
@@ -1,14 +1,14 @@
1
- import type { GetInstanceById } from "../../node/server/index.ts";
2
1
  import { type DisplayNameResult } from "../../shared/utils/displayName.ts";
3
- import type { InstanceContainer, InstanceContent } from "../../shared/utils/instances.ts";
2
+ import type { InstanceContainer, InstanceContainerOverview, InstanceContent } from "../../shared/utils/instances.ts";
4
3
  import { type EntityDecl } from "../schema/declarations/EntityDecl.ts";
5
- import type { AsDeepType, Type } from "../schema/types/Type.ts";
4
+ import type { RegisteredEntity } from "../schema/externalTypes.ts";
5
+ import { type DatabaseInMemory, type InstanceFromDatabaseInMemoryGetter } from "./databaseInMemory.ts";
6
6
  export type GetChildInstancesForInstanceId = (parentEntityName: string, parentId: string, childEntityName: string) => {
7
7
  id: string;
8
8
  content: InstanceContent;
9
9
  }[];
10
- export type DisplayNameCustomizer<T extends Type> = (params: {
11
- instance: AsDeepType<T>;
10
+ export type DisplayNameCustomizer = (params: {
11
+ instance: unknown;
12
12
  instanceId: string;
13
13
  instanceDisplayName: string;
14
14
  instanceDisplayNameLocaleId: string | undefined;
@@ -17,4 +17,15 @@ export type DisplayNameCustomizer<T extends Type> = (params: {
17
17
  getDisplayNameForInstanceId: (id: string) => DisplayNameResult | undefined;
18
18
  getChildInstancesForInstanceId: GetChildInstancesForInstanceId;
19
19
  }) => DisplayNameResult;
20
- export declare const getDisplayNameFromEntityInstance: (entity: EntityDecl, instanceContainer: InstanceContainer, getInstanceById: GetInstanceById, getChildInstancesForInstanceId: GetChildInstancesForInstanceId, locales: string[], defaultName?: string, useCustomizer?: boolean) => DisplayNameResult;
20
+ export type TypedDisplayNameCustomizer<Name extends string> = (params: {
21
+ instance: RegisteredEntity<Name>;
22
+ instanceId: string;
23
+ instanceDisplayName: string;
24
+ instanceDisplayNameLocaleId: string | undefined;
25
+ locales: string[];
26
+ getInstanceById: (id: string) => InstanceContent | undefined;
27
+ getDisplayNameForInstanceId: (id: string) => DisplayNameResult | undefined;
28
+ getChildInstancesForInstanceId: GetChildInstancesForInstanceId;
29
+ }) => DisplayNameResult;
30
+ export declare const getDisplayNameFromEntityInstance: (entity: EntityDecl, instanceContainer: InstanceContainer, getInstanceById: InstanceFromDatabaseInMemoryGetter, getChildInstancesForInstanceId: GetChildInstancesForInstanceId, locales: string[], defaultName?: string, useCustomizer?: boolean) => DisplayNameResult;
31
+ export declare const getAllInstanceOverviewsByEntityName: (entitiesByName: Record<string, EntityDecl>, databaseInMemory: DatabaseInMemory, locales: string[]) => Record<string, InstanceContainerOverview[]>;
@@ -1,10 +1,11 @@
1
1
  import { getSerializedDisplayNameFromEntityInstance, } from "../../shared/utils/displayName.js";
2
2
  import { serializeEntityDecl } from "../schema/declarations/EntityDecl.js";
3
+ import { createChildInstancesForInstanceIdGetter } from "../server/utils/childInstances.js";
4
+ import { createInstanceFromDatabaseInMemoryGetter, getGroupedInstancesFromDatabaseInMemory, } from "./databaseInMemory.js";
3
5
  export const getDisplayNameFromEntityInstance = (entity, instanceContainer, getInstanceById, getChildInstancesForInstanceId, locales, defaultName = "", useCustomizer = true) => {
4
6
  if (useCustomizer && entity.displayNameCustomizer) {
5
7
  const calculatedName = getDisplayNameFromEntityInstance(entity, instanceContainer, getInstanceById, getChildInstancesForInstanceId, locales, defaultName, false);
6
8
  return entity.displayNameCustomizer({
7
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment -- otherwise type instiatiation too deep
8
9
  instance: instanceContainer.content,
9
10
  instanceId: instanceContainer.id,
10
11
  instanceDisplayName: calculatedName.name,
@@ -28,3 +29,23 @@ export const getDisplayNameFromEntityInstance = (entity, instanceContainer, getI
28
29
  return getSerializedDisplayNameFromEntityInstance(serializeEntityDecl(entity), instanceContainer.content, defaultName, locales);
29
30
  }
30
31
  };
32
+ export const getAllInstanceOverviewsByEntityName = (entitiesByName, databaseInMemory, locales) => {
33
+ const getInstanceById = createInstanceFromDatabaseInMemoryGetter(databaseInMemory, entitiesByName);
34
+ const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(entitiesByName, databaseInMemory);
35
+ return Object.fromEntries(getGroupedInstancesFromDatabaseInMemory(databaseInMemory).map(([entityName, instances]) => [
36
+ entityName,
37
+ instances
38
+ .map((instance) => {
39
+ const { name, localeId } = getDisplayNameFromEntityInstance(
40
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
41
+ entitiesByName[entityName], instance, getInstanceById, getChildInstancesForInstanceId, locales);
42
+ return {
43
+ id: instance.id,
44
+ displayName: name,
45
+ displayNameLocaleId: localeId,
46
+ gitStatus: instance.gitStatus,
47
+ };
48
+ })
49
+ .toSorted((a, b) => a.displayName.localeCompare(b.displayName, a.displayNameLocaleId)),
50
+ ]));
51
+ };
@@ -1,4 +1,4 @@
1
- export declare const getErrorMessageForDisplay: (error: Error) => string;
1
+ export declare const getErrorMessageForDisplay: (error: Error, indentation?: number) => string;
2
2
  export declare const countError: (error: Error) => number;
3
3
  export declare const countErrors: (errors: Error[]) => number;
4
4
  export declare const wrapErrorsIfAny: (message: string, errors: Error[]) => AggregateError | undefined;
@@ -1,13 +1,17 @@
1
1
  import { applyIndentation } from "./render.js";
2
- export const getErrorMessageForDisplay = (error) => {
2
+ import { UniqueConstraintError } from "./unique.js";
3
+ export const getErrorMessageForDisplay = (error, indentation = 2) => {
3
4
  if (error instanceof AggregateError) {
4
5
  return `${error.message}\n${applyIndentation(1, error.errors
5
6
  .filter(subError => subError instanceof Error)
6
- .map(subError => getErrorMessageForDisplay(subError))
7
- .join("\n"), 2)}`;
7
+ .map(subError => getErrorMessageForDisplay(subError, indentation))
8
+ .join("\n"), indentation)}`;
8
9
  }
9
10
  else if (error.cause instanceof Error) {
10
- return `${error.message}\n${applyIndentation(1, getErrorMessageForDisplay(error.cause), 2)}`;
11
+ return `${error.message}\n${applyIndentation(1, getErrorMessageForDisplay(error.cause, indentation), indentation)}`;
12
+ }
13
+ else if (error instanceof UniqueConstraintError) {
14
+ return `${error.message}\n${applyIndentation(1, error.parts.join("\n"), indentation)}`;
11
15
  }
12
16
  else {
13
17
  return error.message;
@@ -1,14 +1,18 @@
1
- import type { InstanceContainer } from "../../shared/utils/instances.ts";
1
+ import type { InstanceContainer, InstanceContainerOverview } from "../../shared/utils/instances.ts";
2
2
  import { type Result } from "../../shared/utils/result.ts";
3
3
  import type { EntityDecl } from "../schema/index.ts";
4
4
  import { type DatabaseInMemory } from "./databaseInMemory.ts";
5
+ export declare class UniqueConstraintError extends Error {
6
+ readonly parts: string[];
7
+ constructor(message: string, parts: string[]);
8
+ }
5
9
  /**
6
10
  * Checks all unique constraints for the provided entity and its instances.
7
11
  *
8
12
  * Returns `Ok` when no violations have been found and an `Error` with an
9
13
  * `AggregateError` if there are any violations of any unique constraint.
10
14
  */
11
- export declare const checkUniqueConstraintsForEntity: (entity: EntityDecl, instances: InstanceContainer[]) => Result<void, AggregateError>;
15
+ export declare const checkUniqueConstraintsForEntity: (entity: EntityDecl, instances: InstanceContainer[], instanceOverviews: InstanceContainerOverview[]) => Result<void, AggregateError>;
12
16
  /**
13
17
  * Checks all unique constraints for all provided entities and their instances.
14
18
  *
@@ -16,4 +20,4 @@ export declare const checkUniqueConstraintsForEntity: (entity: EntityDecl, insta
16
20
  * `AggregateError`s for each entity if there are any violations of any unique
17
21
  * constraint.
18
22
  */
19
- export declare const checkUniqueConstraintsForAllEntities: (db: DatabaseInMemory, entities: EntityDecl[]) => Result<void, AggregateError>;
23
+ export declare const checkUniqueConstraintsForAllEntities: (db: DatabaseInMemory, entitiesByName: Record<string, EntityDecl>, instanceOverviewsByEntityName: Record<string, InstanceContainerOverview[]>) => Result<void, AggregateError>;
@@ -3,13 +3,21 @@ import { anySameIndices, flatCombine } from "../../shared/utils/array.js";
3
3
  import { deepEqual } from "../../shared/utils/compare.js";
4
4
  import { error, isError, mapError, ok } from "../../shared/utils/result.js";
5
5
  import { getInstancesOfEntityFromDatabaseInMemory, } from "./databaseInMemory.js";
6
- const listFormatter = new Intl.ListFormat("en-US", { type: "conjunction" });
7
- const printUniqueConstraint = (constraint) => (Array.isArray(constraint) ? constraint : [constraint])
8
- .map(elem => "keyPath" in elem
6
+ export class UniqueConstraintError extends Error {
7
+ parts;
8
+ constructor(message, parts) {
9
+ super(message);
10
+ this.parts = parts;
11
+ }
12
+ }
13
+ const printUniqueConstraint = (constraint, values) => (Array.isArray(constraint) ? constraint : [constraint])
14
+ .map((elem, i) => "keyPath" in elem
9
15
  ? renderKeyPath(elem.keyPath) +
10
16
  (elem.keyPathFallback ? "|" + renderKeyPath(elem.keyPathFallback) : "")
11
17
  : renderKeyPath(elem.entityMapKeyPath) +
12
- "[...]." +
18
+ "[" +
19
+ (Array.isArray(values[i]) ? values[i][0] : "...") +
20
+ "]." +
13
21
  (elem.keyPathInEntityMapFallback
14
22
  ? "(" +
15
23
  renderKeyPath(elem.keyPathInEntityMap) +
@@ -31,7 +39,7 @@ const unsafeGetValueAtKeyPath = (value, keyPath) => {
31
39
  * Returns `Ok` when no violations have been found and an `Error` with an
32
40
  * `AggregateError` if there are any violations of any unique constraint.
33
41
  */
34
- export const checkUniqueConstraintsForEntity = (entity, instances) => {
42
+ export const checkUniqueConstraintsForEntity = (entity, instances, instanceOverviews) => {
35
43
  const constraintErrors = [];
36
44
  const constraints = entity.uniqueConstraints ?? [];
37
45
  for (const [constraintIndex, constraint] of constraints.entries()) {
@@ -61,14 +69,17 @@ export const checkUniqueConstraintsForEntity = (entity, instances) => {
61
69
  constraintErrors.push([
62
70
  constraintIndex,
63
71
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Indices returned by anySameIndices must exist
64
- duplicates.map(duplicateSet => duplicateSet.map(rowIndex => index[rowIndex][0])),
72
+ duplicates.map(duplicateSet => duplicateSet.map(rowIndex => index[rowIndex])),
65
73
  ]);
66
74
  }
67
75
  }
68
76
  if (constraintErrors.length > 0) {
69
- return error(new AggregateError(constraintErrors.map(([constraintIndex, constraintErrors]) => new AggregateError(constraintErrors.map(error => new Error(`instances ${listFormatter.format(error)} contain duplicate values`)),
77
+ return error(new AggregateError(constraintErrors.flatMap(([constraintIndex, constraintErrors]) => constraintErrors.map(error => new UniqueConstraintError(
70
78
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- constraint must be present
71
- `in unique constraint ${printUniqueConstraint(constraints[constraintIndex])}`)), `in entity "${entity.name}"`));
79
+ `for unique constraint ${printUniqueConstraint(constraints[constraintIndex], error[0][1])}:`, error.map(row => {
80
+ const instanceOverview = instanceOverviews.find(o => o.id === row[0]);
81
+ return instanceOverview ? `"${instanceOverview.displayName}" (${row[0]})` : row[0];
82
+ })))), `in entity "${entity.name}"`));
72
83
  }
73
84
  return ok();
74
85
  };
@@ -79,8 +90,8 @@ export const checkUniqueConstraintsForEntity = (entity, instances) => {
79
90
  * `AggregateError`s for each entity if there are any violations of any unique
80
91
  * constraint.
81
92
  */
82
- export const checkUniqueConstraintsForAllEntities = (db, entities) => mapError(entities.reduce((acc, entity) => {
83
- const resultForEntity = checkUniqueConstraintsForEntity(entity, getInstancesOfEntityFromDatabaseInMemory(db, entity.name));
93
+ export const checkUniqueConstraintsForAllEntities = (db, entitiesByName, instanceOverviewsByEntityName) => mapError(Object.values(entitiesByName).reduce((acc, entity) => {
94
+ const resultForEntity = checkUniqueConstraintsForEntity(entity, getInstancesOfEntityFromDatabaseInMemory(db, entity.name), instanceOverviewsByEntityName[entity.name] ?? []);
84
95
  if (isError(acc)) {
85
96
  if (isError(resultForEntity)) {
86
97
  return error([...acc.error, resultForEntity.error]);
@@ -91,4 +102,4 @@ export const checkUniqueConstraintsForAllEntities = (db, entities) => mapError(e
91
102
  return error([resultForEntity.error]);
92
103
  }
93
104
  return acc;
94
- }, ok()), errors => new AggregateError(errors, "at least one unique constraint has been violated"));
105
+ }, ok()), errors => new AggregateError(errors.toSorted((a, b) => a.message.localeCompare(b.message)), "at least one unique constraint has been violated"));
@@ -48,7 +48,7 @@ export interface GetAllInstancesResponseBody {
48
48
  instances: {
49
49
  [entity: string]: {
50
50
  id: string;
51
- name: string;
51
+ displayName: string;
52
52
  displayNameLocaleId?: string;
53
53
  }[];
54
54
  };
@@ -15,3 +15,4 @@ export declare const modifyD: <T>(dict: Dictionary<T>, key: string, modifyFn: (c
15
15
  export declare const findD: <T>(dict: Dictionary<T>, predicate: (value: T, key: string) => boolean) => T | undefined;
16
16
  export declare const mapFirstD: <T, U>(dict: Dictionary<T>, mapFn: (value: T, key: string) => U | undefined) => U | undefined;
17
17
  export declare const mapD: <T, U>(dict: Dictionary<T>, mapFn: (value: T, key: string) => U) => Dictionary<U>;
18
+ export declare const reduceD: <T, U>(dict: Dictionary<T>, reducer: (acc: U, value: T, key: string) => U, initialValue: U) => U;
@@ -74,3 +74,4 @@ export const mapD = (dict, mapFn) => {
74
74
  }
75
75
  return [newRecord, dict[1]];
76
76
  };
77
+ export const reduceD = (dict, reducer, initialValue) => Object.entries(dict[0]).reduce((acc, [key, item]) => reducer(acc, item, key), initialValue);
@@ -1,5 +1,5 @@
1
1
  import type { EntityDecl } from "../../node/schema/index.ts";
2
- import type { GetInstanceById } from "../../node/server/index.ts";
2
+ import type { InstanceFromDatabaseInMemoryGetter } from "../../node/utils/databaseInMemory.ts";
3
3
  import { type GetChildInstancesForInstanceId } from "../../node/utils/displayName.ts";
4
4
  import type { GitFileStatus } from "./git.ts";
5
5
  export type InstanceContent = object;
@@ -14,4 +14,4 @@ export interface InstanceContainerOverview {
14
14
  displayName: string;
15
15
  displayNameLocaleId?: string;
16
16
  }
17
- export declare const getInstanceContainerOverview: (entity: EntityDecl, instanceContainer: InstanceContainer, getInstanceById: GetInstanceById, getChildInstancesForInstanceId: GetChildInstancesForInstanceId, locales: string[]) => InstanceContainerOverview;
17
+ export declare const getInstanceContainerOverview: (entity: EntityDecl, instanceContainer: InstanceContainer, getInstanceById: InstanceFromDatabaseInMemoryGetter, getChildInstancesForInstanceId: GetChildInstancesForInstanceId, locales: string[]) => InstanceContainerOverview;
@@ -16,11 +16,11 @@ export const NestedEntityMapTypeInput = props => {
16
16
  const secondaryInstances = (instanceNamesByEntity[type.secondaryEntity] ?? [])
17
17
  .slice()
18
18
  .filter(instance => !existingKeys.includes(instance.id))
19
- .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
19
+ .sort((a, b) => a.displayName.localeCompare(b.displayName, undefined, { numeric: true }));
20
20
  const isLocaleEntity = checkIsLocaleEntity(type.secondaryEntity);
21
21
  return (_jsxs("div", { class: "field field--container field--nestedentitymap", children: [existingKeys.length > 0 && (_jsx("ul", { children: Object.entries(value).map(([key, item]) => {
22
22
  const name = instanceNamesByEntity[type.secondaryEntity]?.find(instance => instance.id === key)
23
- ?.name ?? key;
23
+ ?.displayName ?? key;
24
24
  return (_jsxs("li", { class: "container-item dict-item", ...(isLocaleEntity ? { lang: key } : {}), children: [_jsxs("div", { className: "container-item-header", children: [_jsx("div", { className: "container-item-title", children: _jsxs("span", { children: [_jsx("strong", { children: name }), " ", _jsx("span", { className: "id", children: key })] }) }), _jsx("div", { className: "btns", children: _jsxs("button", { class: "destructive", onClick: () => {
25
25
  const newObj = { ...value };
26
26
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
@@ -31,7 +31,7 @@ export const NestedEntityMapTypeInput = props => {
31
31
  } })] }, key));
32
32
  }) })), _jsxs("div", { class: "add-item-container", children: [_jsxs(Select, { value: newKey, onInput: event => {
33
33
  setNewKey(event.currentTarget.value);
34
- }, disabled: disabled || secondaryInstances.length === 0, children: [secondaryInstances.length === 0 ? (_jsx("option", { value: "", disabled: true, children: "No instances available" })) : (_jsx("option", { value: "", disabled: true, children: "No selected instance" })), secondaryInstances.map(instance => (_jsx("option", { value: instance.id, children: instance.name }, instance.id)))] }), _jsxs("button", { onClick: () => {
34
+ }, disabled: disabled || secondaryInstances.length === 0, children: [secondaryInstances.length === 0 ? (_jsx("option", { value: "", disabled: true, children: "No instances available" })) : (_jsx("option", { value: "", disabled: true, children: "No selected instance" })), secondaryInstances.map(instance => (_jsx("option", { value: instance.id, children: instance.displayName }, instance.id)))] }), _jsxs("button", { onClick: () => {
35
35
  onChange(sortObjectKeysAlphabetically({
36
36
  ...value,
37
37
  [newKey]: createTypeSkeleton(getDeclFromDeclName, type.type),
@@ -9,5 +9,5 @@ export const ReferenceIdentifierTypeInput = ({ type, value, instanceNamesByEntit
9
9
  const instances = instanceNamesByEntity[type.entity] ?? [];
10
10
  return (_jsxs("div", { class: "field", children: [_jsxs(Select, { value: value, onInput: event => {
11
11
  onChange(event.currentTarget.value);
12
- }, disabled: disabled || instances.length === 0, "aria-invalid": !value, children: [instances.length === 0 ? (_jsx("option", { value: "", disabled: true, children: "No instances available" })) : (_jsx("option", { value: "", disabled: true, children: "No selected instance" })), instances.map(instance => (_jsx("option", { value: instance.id, children: instance.name }, instance.id)))] }), _jsx(ValidationErrors, { disabled: disabled, errors: !value ? [ReferenceError("no reference provided")] : [] })] }));
12
+ }, disabled: disabled || instances.length === 0, "aria-invalid": !value, children: [instances.length === 0 ? (_jsx("option", { value: "", disabled: true, children: "No instances available" })) : (_jsx("option", { value: "", disabled: true, children: "No selected instance" })), instances.map(instance => (_jsx("option", { value: instance.id, children: instance.displayName }, instance.id)))] }), _jsx(ValidationErrors, { disabled: disabled, errors: !value ? [ReferenceError("no reference provided")] : [] })] }));
13
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsondb",
3
- "version": "0.13.2",
3
+ "version": "0.15.0",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "Lukas Obermann",