tsondb 0.12.2 → 0.12.5

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 (57) hide show
  1. package/dist/src/node/index.js +17 -20
  2. package/dist/src/node/schema/Node.d.ts +2 -2
  3. package/dist/src/node/schema/Node.js +3 -5
  4. package/dist/src/node/schema/declarations/EntityDecl.d.ts +4 -4
  5. package/dist/src/node/schema/types/references/IncludeIdentifierType.js +0 -3
  6. package/dist/src/node/server/api/declarations.js +27 -10
  7. package/dist/src/node/server/api/git.js +3 -5
  8. package/dist/src/node/server/api/instances.js +6 -5
  9. package/dist/src/node/server/index.d.ts +5 -3
  10. package/dist/src/node/server/index.js +5 -2
  11. package/dist/src/node/server/init.d.ts +2 -2
  12. package/dist/src/node/server/init.js +15 -13
  13. package/dist/src/node/server/utils/childInstances.d.ts +0 -3
  14. package/dist/src/node/server/utils/childInstances.js +2 -43
  15. package/dist/src/node/server/utils/instanceOperations.d.ts +3 -4
  16. package/dist/src/node/server/utils/instanceOperations.js +44 -84
  17. package/dist/src/node/utils/childInstances.d.ts +11 -10
  18. package/dist/src/node/utils/childInstances.js +60 -130
  19. package/dist/src/node/utils/databaseInMemory.d.ts +18 -0
  20. package/dist/src/node/utils/databaseInMemory.js +86 -0
  21. package/dist/src/node/utils/databaseOnDisk.d.ts +2 -0
  22. package/dist/src/node/utils/databaseOnDisk.js +62 -0
  23. package/dist/src/node/utils/databaseTransactions.d.ts +46 -0
  24. package/dist/src/node/utils/databaseTransactions.js +105 -0
  25. package/dist/src/node/utils/displayName.d.ts +3 -3
  26. package/dist/src/node/utils/error.d.ts +4 -0
  27. package/dist/src/node/utils/error.js +8 -0
  28. package/dist/src/node/utils/files.d.ts +4 -2
  29. package/dist/src/node/utils/files.js +2 -1
  30. package/dist/src/node/utils/git.d.ts +3 -1
  31. package/dist/src/node/utils/git.js +8 -2
  32. package/dist/src/node/utils/instanceTransactionSteps.d.ts +9 -0
  33. package/dist/src/node/utils/instanceTransactionSteps.js +40 -0
  34. package/dist/src/node/utils/references.d.ts +4 -3
  35. package/dist/src/node/utils/references.js +3 -2
  36. package/dist/src/shared/api.d.ts +1 -0
  37. package/dist/src/shared/schema/declarations/EntityDecl.d.ts +2 -2
  38. package/dist/src/shared/utils/dictionary.d.ts +17 -0
  39. package/dist/src/shared/utils/dictionary.js +76 -0
  40. package/dist/src/shared/utils/instances.d.ts +2 -2
  41. package/dist/src/shared/utils/result.d.ts +4 -0
  42. package/dist/src/shared/utils/result.js +4 -0
  43. package/dist/src/web/components/InstanceRouteSkeleton.d.ts +9 -8
  44. package/dist/src/web/components/InstanceRouteSkeleton.js +5 -1
  45. package/dist/src/web/components/typeInputs/ChildEntitiesTypeInput.js +1 -1
  46. package/dist/src/web/components/typeInputs/ReferenceIdentifierTypeInput.js +1 -3
  47. package/dist/src/web/hooks/useGitClient.d.ts +1 -0
  48. package/dist/src/web/hooks/useGitClient.js +4 -0
  49. package/dist/src/web/hooks/useMappedAPIResource.js +2 -4
  50. package/dist/src/web/routes/Entity.js +9 -2
  51. package/dist/src/web/utils/typeSkeleton.d.ts +2 -2
  52. package/dist/src/web/utils/typeSkeleton.js +1 -1
  53. package/package.json +3 -3
  54. package/dist/src/node/utils/instanceOperations.d.ts +0 -14
  55. package/dist/src/node/utils/instanceOperations.js +0 -88
  56. package/dist/src/node/utils/instances.d.ts +0 -6
  57. package/dist/src/node/utils/instances.js +0 -29
@@ -7,9 +7,9 @@ import { validateEntityDecl } from "./schema/declarations/EntityDecl.js";
7
7
  import { createValidators } from "./schema/Node.js";
8
8
  import { getEntities } from "./schema/Schema.js";
9
9
  import { createServer } from "./server/index.js";
10
+ import { asyncForEachInstanceInDatabaseInMemory, createDatabaseInMemory, getInstancesOfEntityFromDatabaseInMemory, } from "./utils/databaseInMemory.js";
10
11
  import { countErrors, getErrorMessageForDisplay, wrapErrorsIfAny } from "./utils/error.js";
11
12
  import { getFileNameForId, writeInstance } from "./utils/files.js";
12
- import { getInstancesByEntityName } from "./utils/instances.js";
13
13
  const debug = Debug("tsondb:jsapi");
14
14
  const prepareFolders = async (dataRootPath, entities) => {
15
15
  await mkdir(dataRootPath, { recursive: true });
@@ -23,18 +23,18 @@ export const generateOutputs = async (schema, outputs) => {
23
23
  await output.run(schema);
24
24
  }
25
25
  };
26
- const _validate = (dataRootPath, entities, instancesByEntityName, options = {}) => {
26
+ const _validate = (dataRootPath, entities, databaseInMemory, options = {}) => {
27
27
  const { checkReferentialIntegrity = true, checkOnlyEntities = [] } = options;
28
28
  for (const onlyEntity of checkOnlyEntities) {
29
29
  if (!entities.find(entity => entity.name === onlyEntity)) {
30
30
  throw new Error(`Entity "${onlyEntity}" not found in schema`);
31
31
  }
32
32
  }
33
- const validationHelpers = createValidators(instancesByEntityName, true, checkReferentialIntegrity);
33
+ const validationHelpers = createValidators(databaseInMemory, true, checkReferentialIntegrity);
34
34
  const errors = (checkOnlyEntities.length > 0
35
35
  ? entities.filter(entity => checkOnlyEntities.includes(entity.name))
36
36
  : entities)
37
- .flatMap(entity => parallelizeErrors(instancesByEntityName[entity.name]?.map(instance => wrapErrorsIfAny(`in file ${styleText("white", `"${dataRootPath}${sep}${styleText("bold", join(entity.name, getFileNameForId(instance.id)))}"`)}`, validateEntityDecl(validationHelpers, [], entity, instance.content))) ?? []))
37
+ .flatMap(entity => parallelizeErrors(getInstancesOfEntityFromDatabaseInMemory(databaseInMemory, entity.name).map(instance => wrapErrorsIfAny(`in file ${styleText("white", `"${dataRootPath}${sep}${styleText("bold", join(entity.name, getFileNameForId(instance.id)))}"`)}`, validateEntityDecl(validationHelpers, [], entity, instance.content)))))
38
38
  .toSorted((a, b) => a.message.localeCompare(b.message));
39
39
  if (errors.length === 0) {
40
40
  debug("All entities are valid");
@@ -47,15 +47,15 @@ const _validate = (dataRootPath, entities, instancesByEntityName, options = {})
47
47
  export const validate = async (schema, dataRootPath, options) => {
48
48
  const entities = getEntities(schema);
49
49
  await prepareFolders(dataRootPath, entities);
50
- const instancesByEntityName = await getInstancesByEntityName(dataRootPath, entities);
51
- _validate(dataRootPath, entities, instancesByEntityName, options);
50
+ const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
51
+ _validate(dataRootPath, entities, databaseInMemory, options);
52
52
  };
53
53
  export const generateAndValidate = async (schema, outputs, dataRootPath, validationOptions) => {
54
54
  await generateOutputs(schema, outputs);
55
55
  const entities = getEntities(schema);
56
56
  await prepareFolders(dataRootPath, entities);
57
- const instancesByEntityName = await getInstancesByEntityName(dataRootPath, entities);
58
- _validate(dataRootPath, entities, instancesByEntityName, validationOptions);
57
+ const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
58
+ _validate(dataRootPath, entities, databaseInMemory, validationOptions);
59
59
  };
60
60
  export const serve = async (schema, dataRootPath, defaultLocales, homeLayoutSections, serverOptions, customStylesheetPath) => {
61
61
  if (defaultLocales.length === 0) {
@@ -64,28 +64,25 @@ export const serve = async (schema, dataRootPath, defaultLocales, homeLayoutSect
64
64
  const entities = getEntities(schema);
65
65
  await prepareFolders(dataRootPath, entities);
66
66
  debug("prepared folders");
67
- const instancesByEntityName = await getInstancesByEntityName(dataRootPath, entities);
67
+ const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
68
68
  debug("loaded instances");
69
- await createServer(schema, dataRootPath, instancesByEntityName, defaultLocales, homeLayoutSections, serverOptions, customStylesheetPath);
69
+ await createServer(schema, dataRootPath, databaseInMemory, defaultLocales, homeLayoutSections, serverOptions, customStylesheetPath);
70
70
  };
71
71
  export const generateValidateAndServe = async (schema, outputs, dataRootPath, defaultLocales, homeLayoutSections, serverOptions, validationOptions) => {
72
72
  await generateOutputs(schema, outputs);
73
73
  const entities = getEntities(schema);
74
74
  await prepareFolders(dataRootPath, entities);
75
- const instancesByEntityName = await getInstancesByEntityName(dataRootPath, entities);
76
- _validate(dataRootPath, entities, instancesByEntityName, validationOptions);
77
- await createServer(schema, dataRootPath, instancesByEntityName, defaultLocales, homeLayoutSections, serverOptions);
75
+ const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
76
+ _validate(dataRootPath, entities, databaseInMemory, validationOptions);
77
+ await createServer(schema, dataRootPath, databaseInMemory, defaultLocales, homeLayoutSections, serverOptions);
78
78
  };
79
79
  export const format = async (schema, dataRootPath) => {
80
80
  const entities = getEntities(schema);
81
81
  await prepareFolders(dataRootPath, entities);
82
- const instancesByEntityName = await getInstancesByEntityName(dataRootPath, entities);
83
- for (const entityName in instancesByEntityName) {
82
+ const databaseInMemory = await createDatabaseInMemory(dataRootPath, entities);
83
+ await asyncForEachInstanceInDatabaseInMemory(databaseInMemory, async (entityName, instance) => {
84
84
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
85
85
  const entity = entities.find(entity => entity.name === entityName);
86
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
87
- for (const instance of instancesByEntityName[entityName]) {
88
- await writeInstance(dataRootPath, entity, instance.id, instance.content);
89
- }
90
- }
86
+ await writeInstance(dataRootPath, entity, instance.id, instance.content);
87
+ });
91
88
  };
@@ -17,7 +17,7 @@ import type { SerializedReferenceIdentifierType } from "../../shared/schema/type
17
17
  import type { SerializedStringType } from "../../shared/schema/types/StringType.ts";
18
18
  import type { SerializedTranslationObjectType } from "../../shared/schema/types/TranslationObjectType.ts";
19
19
  import type { SerializedTypeArgumentType } from "../../shared/schema/types/TypeArgumentType.ts";
20
- import type { InstancesByEntityName } from "../../shared/utils/instances.ts";
20
+ import { type DatabaseInMemory } from "../utils/databaseInMemory.ts";
21
21
  import { type Decl, type IncludableDeclP } from "./declarations/Declaration.ts";
22
22
  import { type EntityDecl } from "./declarations/EntityDecl.ts";
23
23
  import { type EnumDecl } from "./declarations/EnumDecl.ts";
@@ -62,7 +62,7 @@ export interface Validators {
62
62
  useStyling: boolean;
63
63
  checkReferentialIntegrity: (identifier: IdentifierToCheck) => Error[];
64
64
  }
65
- export declare const createValidators: (instancesByEntityName: InstancesByEntityName, useStyling: boolean, checkReferentialIntegrity?: boolean) => Validators;
65
+ export declare const createValidators: (databaseInMemory: DatabaseInMemory, useStyling: boolean, checkReferentialIntegrity?: boolean) => Validators;
66
66
  export type Predicate<T extends Node> = (node: Node) => node is T;
67
67
  export type GetNestedDeclarations<T extends Node = Node> = (addedDecls: NestedDecl[], node: T, parentDecl: Decl | undefined) => NestedDecl[];
68
68
  export declare const getNestedDeclarations: GetNestedDeclarations;
@@ -1,5 +1,6 @@
1
1
  import { NodeKind } from "../../shared/schema/Node.js";
2
2
  import { assertExhaustive } from "../../shared/utils/typeSafety.js";
3
+ import { getInstancesOfEntityFromDatabaseInMemory, } from "../utils/databaseInMemory.js";
3
4
  import { entity, json } from "../utils/errorFormatting.js";
4
5
  import { isDecl } from "./declarations/Declaration.js";
5
6
  import { getNestedDeclarationsInEntityDecl, getReferencesForEntityDecl, resolveTypeArgumentsInEntityDecl, serializeEntityDecl, validateEntityDecl, } from "./declarations/EntityDecl.js";
@@ -80,13 +81,10 @@ export const flatMapAuxiliaryDecls = (callbackFn, declarations) => reduceNodes((
80
81
  });
81
82
  return declsWithCurrentDecl.concat(normalizedResult);
82
83
  }, declarations);
83
- export const createValidators = (instancesByEntityName, useStyling, checkReferentialIntegrity = true) => ({
84
+ export const createValidators = (databaseInMemory, useStyling, checkReferentialIntegrity = true) => ({
84
85
  useStyling,
85
86
  checkReferentialIntegrity: checkReferentialIntegrity
86
- ? ({ name, value }) => instancesByEntityName[name]?.some(instance => typeof instance.content === "object" &&
87
- instance.content !== null &&
88
- !Array.isArray(instance.content) &&
89
- instance.id === value)
87
+ ? ({ name, value }) => getInstancesOfEntityFromDatabaseInMemory(databaseInMemory, name).some(instance => instance.id === value)
90
88
  ? []
91
89
  : [
92
90
  ReferenceError(`Invalid reference to instance of entity ${entity(`"${name}"`, useStyling)} with identifier ${json(value, useStyling)}`),
@@ -23,7 +23,7 @@ export type EntityDisplayName<T extends TConstraint> = Leaves<AsType<ObjectType<
23
23
  pathInLocaleMap?: string;
24
24
  } | null;
25
25
  type TConstraint = Record<string, MemberDecl>;
26
- export interface EntityDecl<Name extends string = string, T extends TConstraint = TConstraint, FK extends (keyof T & string) | undefined = (keyof T & string) | undefined> extends BaseDecl<Name, []> {
26
+ export interface EntityDecl<Name extends string = string, T extends TConstraint = TConstraint, FK extends Extract<keyof T, string> | undefined = Extract<keyof T, string> | undefined> extends BaseDecl<Name, []> {
27
27
  kind: NodeKind["EntityDecl"];
28
28
  namePlural: string;
29
29
  type: Lazy<ObjectType<T>>;
@@ -35,7 +35,7 @@ export interface EntityDecl<Name extends string = string, T extends TConstraint
35
35
  displayNameCustomizer?: DisplayNameCustomizer<ObjectType<T>>;
36
36
  isDeprecated?: boolean;
37
37
  }
38
- export interface EntityDeclWithParentReference<Name extends string = string, T extends TConstraint = TConstraint, FK extends keyof T & string = keyof T & string> extends EntityDecl<Name, T, FK> {
38
+ 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> {
39
39
  }
40
40
  export declare const EntityDecl: {
41
41
  <Name extends string, T extends TConstraint>(sourceUrl: string, options: {
@@ -50,7 +50,7 @@ export declare const EntityDecl: {
50
50
  displayNameCustomizer?: DisplayNameCustomizer<ObjectType<T>>;
51
51
  isDeprecated?: boolean;
52
52
  }): EntityDecl<Name, T, undefined>;
53
- <Name extends string, T extends TConstraint, FK extends keyof T & string>(sourceUrl: string, options: {
53
+ <Name extends string, T extends TConstraint, FK extends Extract<keyof T, string>>(sourceUrl: string, options: {
54
54
  name: Name;
55
55
  namePlural: string;
56
56
  comment?: string;
@@ -66,7 +66,7 @@ export declare const EntityDecl: {
66
66
  };
67
67
  export { EntityDecl as Entity };
68
68
  export declare const isEntityDecl: Predicate<EntityDecl>;
69
- export declare const isEntityDeclWithParentReference: <Name extends string, T extends TConstraint, FK extends (keyof T & string) | undefined>(decl: EntityDecl<Name, T, FK>) => decl is EntityDecl<Name, T, NonNullable<FK>>;
69
+ export declare const isEntityDeclWithParentReference: <Name extends string, T extends TConstraint, FK extends Extract<keyof T, string> | undefined>(decl: EntityDecl<Name, T, FK>) => decl is EntityDecl<Name, T, NonNullable<FK>>;
70
70
  export declare const getNestedDeclarationsInEntityDecl: GetNestedDeclarations<EntityDecl>;
71
71
  export declare const validateEntityDecl: Validator<EntityDecl>;
72
72
  export declare const resolveTypeArgumentsInEntityDecl: TypeArgumentsResolver<EntityDecl>;
@@ -29,9 +29,6 @@ export const resolveTypeArgumentsInIncludeIdentifierType = ((args, type, inDecl)
29
29
  else {
30
30
  const parentDecl = inDecl[inDecl.length - 1];
31
31
  const grandParentDecl = inDecl[inDecl.length - 2];
32
- if (grandParentDecl?.name === "AdventurePointsDependingOnActiveInstancesMultiplier") {
33
- console.log(type.args.every((arg, argIndex) => isTypeArgumentType(arg) && parentDecl?.parameters[argIndex] === arg.argument));
34
- }
35
32
  if (type.reference === parentDecl &&
36
33
  parentDecl.parameters.length > 0 &&
37
34
  grandParentDecl &&
@@ -7,6 +7,8 @@ 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";
11
+ import { HTTPError } from "../../utils/error.js";
10
12
  import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
11
13
  import { createInstance, deleteInstance, updateInstance } from "../utils/instanceOperations.js";
12
14
  const debug = Debug("tsondb:server:api:declarations");
@@ -33,7 +35,7 @@ declarationsApi.get("/", (req, res) => {
33
35
  const body = {
34
36
  declarations: filteredEntities.map(decl => ({
35
37
  declaration: serializeNode(decl),
36
- instanceCount: req.instancesByEntityName[decl.name]?.length ?? 0,
38
+ instanceCount: countInstancesOfEntityInDatabaseInMemory(req.databaseInMemory, decl.name),
37
39
  })),
38
40
  localeEntity: req.localeEntity?.name,
39
41
  };
@@ -47,7 +49,7 @@ declarationsApi.get("/:name", (req, res) => {
47
49
  }
48
50
  const body = {
49
51
  declaration: serializeNode(decl),
50
- instanceCount: req.instancesByEntityName[decl.name]?.length ?? 0,
52
+ instanceCount: countInstancesOfEntityInDatabaseInMemory(req.databaseInMemory, decl.name),
51
53
  isLocaleEntity: decl === req.localeEntity,
52
54
  };
53
55
  res.json(body);
@@ -64,9 +66,9 @@ declarationsApi.get("/:name/instances", (req, res) => {
64
66
  }
65
67
  const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
66
68
  const body = {
67
- instances: req.instancesByEntityName[req.params.name]
68
- ?.map(instanceContainer => getInstanceContainerOverview(decl, instanceContainer, req.getInstanceById, getChildInstancesForInstanceId, req.locales))
69
- .toSorted((a, b) => a.displayName.localeCompare(b.displayName, undefined, { numeric: true })) ?? [],
69
+ instances: getInstancesOfEntityFromDatabaseInMemory(req.databaseInMemory, decl.name)
70
+ .map(instanceContainer => getInstanceContainerOverview(decl, instanceContainer, req.getInstanceById, getChildInstancesForInstanceId, req.locales))
71
+ .toSorted((a, b) => a.displayName.localeCompare(b.displayName, undefined, { numeric: true })),
70
72
  isLocaleEntity: decl === req.localeEntity,
71
73
  };
72
74
  res.json(body);
@@ -91,7 +93,12 @@ declarationsApi.post("/:name/instances", async (req, res) => {
91
93
  res.json(body);
92
94
  }
93
95
  else {
94
- res.status(result.error[0]).send(result.error[1]);
96
+ if (result.error instanceof HTTPError) {
97
+ res.status(result.error.code).send(result.error.message);
98
+ }
99
+ else {
100
+ res.status(500).send(result.error.message);
101
+ }
95
102
  }
96
103
  });
97
104
  declarationsApi.get("/:name/instances/:id", (req, res) => {
@@ -104,7 +111,7 @@ declarationsApi.get("/:name/instances/:id", (req, res) => {
104
111
  res.status(400).send(`Declaration "${decl.name}" is not an entity`);
105
112
  return;
106
113
  }
107
- const instance = req.instancesByEntityName[decl.name]?.find(instance => instance.id === req.params.id);
114
+ const instance = getInstanceOfEntityFromDatabaseInMemory(req.databaseInMemory, decl.name, req.params.id);
108
115
  if (instance === undefined) {
109
116
  res.status(404).send(`Instance "${req.params.id}" not found`);
110
117
  return;
@@ -135,7 +142,12 @@ declarationsApi.put("/:name/instances/:id", async (req, res) => {
135
142
  res.json(body);
136
143
  }
137
144
  else {
138
- res.status(result.error[0]).send(result.error[1]);
145
+ if (result.error instanceof HTTPError) {
146
+ res.status(result.error.code).send(result.error.message);
147
+ }
148
+ else {
149
+ res.status(500).send(result.error.message);
150
+ }
139
151
  }
140
152
  });
141
153
  declarationsApi.delete("/:name/instances/:id", async (req, res) => {
@@ -157,7 +169,12 @@ declarationsApi.delete("/:name/instances/:id", async (req, res) => {
157
169
  res.json(body);
158
170
  }
159
171
  else {
160
- res.status(result.error[0]).send(result.error[1]);
172
+ if (result.error instanceof HTTPError) {
173
+ res.status(result.error.code).send(result.error.message);
174
+ }
175
+ else {
176
+ res.status(500).send(result.error.message);
177
+ }
161
178
  }
162
179
  });
163
180
  declarationsApi.get("/:name/instances/:id/children", (req, res) => {
@@ -171,7 +188,7 @@ declarationsApi.get("/:name/instances/:id/children", (req, res) => {
171
188
  return;
172
189
  }
173
190
  const body = {
174
- instances: getChildInstances(req.instancesByEntityName, decl, req.params.id),
191
+ instances: getChildInstances(req.databaseInMemory, decl, req.params.id),
175
192
  };
176
193
  res.json(body);
177
194
  });
@@ -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 { attachGitStatusToInstancesByEntityName } from "../../utils/instances.js";
6
+ import { getGroupedInstancesFromDatabaseInMemory } from "../../utils/databaseInMemory.js";
7
7
  import { reinit } from "../init.js";
8
8
  import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
9
9
  const debug = Debug("tsondb:server:api:git");
@@ -24,16 +24,13 @@ gitApi.get("/", (req, res) => {
24
24
  });
25
25
  gitApi.get("/status", async (req, res) => {
26
26
  const status = await req.git.status();
27
- attachGitStatusToInstancesByEntityName(req.instancesByEntityName, req.dataRoot,
28
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
29
- req.gitRoot, status);
30
27
  const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
31
28
  const body = {
32
29
  currentBranch: status.current,
33
30
  trackingBranch: status.tracking,
34
31
  commitsAhead: status.ahead,
35
32
  commitsBehind: status.behind,
36
- instances: Object.fromEntries(Object.entries(req.instancesByEntityName).map(([entityName, instances]) => [
33
+ instances: Object.fromEntries(getGroupedInstancesFromDatabaseInMemory(req.databaseInMemory).map(([entityName, instances]) => [
37
34
  entityName,
38
35
  instances
39
36
  .filter(instance => hasFileChanges(instance.gitStatus))
@@ -41,6 +38,7 @@ gitApi.get("/status", async (req, res) => {
41
38
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
42
39
  req.entitiesByName[entityName], instance, req.getInstanceById, getChildInstancesForInstanceId, req.locales)),
43
40
  ])),
41
+ latestCommit: await req.git.revparse(["HEAD"]),
44
42
  };
45
43
  res.json(body);
46
44
  });
@@ -1,5 +1,6 @@
1
1
  import Debug from "debug";
2
2
  import express from "express";
3
+ import { getGroupedInstancesFromDatabaseInMemory } from "../../utils/databaseInMemory.js";
3
4
  import { getDisplayNameFromEntityInstance } from "../../utils/displayName.js";
4
5
  import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
5
6
  const debug = Debug("tsondb:server:api:instances");
@@ -11,11 +12,10 @@ instancesApi.use((req, _res, next) => {
11
12
  instancesApi.get("/", (req, res) => {
12
13
  const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
13
14
  const body = {
14
- instances: Object.fromEntries(Object.entries(req.instancesByEntityName)
15
- .filter(([entityName]) => Object.hasOwn(req.entitiesByName, entityName))
16
- .map(([entityName, instances]) => [
15
+ instances: Object.fromEntries(getGroupedInstancesFromDatabaseInMemory(req.databaseInMemory).map(([entityName, instances]) => [
17
16
  entityName,
18
- instances.map(instance => {
17
+ instances
18
+ .map(instance => {
19
19
  const { name, localeId } = getDisplayNameFromEntityInstance(
20
20
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21
21
  req.entitiesByName[entityName], instance, req.getInstanceById, getChildInstancesForInstanceId, req.locales);
@@ -24,7 +24,8 @@ instancesApi.get("/", (req, res) => {
24
24
  name,
25
25
  displayNameLocaleId: localeId,
26
26
  };
27
- }),
27
+ })
28
+ .toSorted((a, b) => a.name.localeCompare(b.name, a.displayNameLocaleId)),
28
29
  ])),
29
30
  };
30
31
  res.json(body);
@@ -1,10 +1,11 @@
1
1
  import type { SimpleGit } from "simple-git";
2
2
  import type { SerializedDecl } from "../../shared/schema/declarations/Declaration.ts";
3
- import type { InstanceContainer, InstancesByEntityName } from "../../shared/utils/instances.ts";
3
+ import type { InstanceContainer } from "../../shared/utils/instances.ts";
4
4
  import type { HomeLayoutSection } from "../config.ts";
5
5
  import type { Decl } from "../schema/declarations/Declaration.ts";
6
6
  import type { EntityDecl } from "../schema/declarations/EntityDecl.ts";
7
7
  import type { Schema } from "../schema/Schema.ts";
8
+ import type { DatabaseInMemory } from "../utils/databaseInMemory.ts";
8
9
  import type { ReferencesToInstances } from "../utils/references.ts";
9
10
  export type ServerOptions = {
10
11
  port: number;
@@ -15,7 +16,7 @@ export interface TSONDBRequestLocals {
15
16
  dataRoot: string;
16
17
  declarations: readonly Decl[];
17
18
  entities: readonly EntityDecl[];
18
- instancesByEntityName: InstancesByEntityName;
19
+ databaseInMemory: DatabaseInMemory;
19
20
  entitiesByName: Record<string, EntityDecl>;
20
21
  serializedDeclarationsByName: Record<string, SerializedDecl>;
21
22
  localeEntity?: EntityDecl;
@@ -24,6 +25,7 @@ export interface TSONDBRequestLocals {
24
25
  locales: string[];
25
26
  homeLayoutSections?: HomeLayoutSection[];
26
27
  getInstanceById: GetInstanceById;
28
+ setLocal: <K extends keyof Omit<TSONDBRequestLocals, "setLocal">>(key: K, value: TSONDBRequestLocals[K]) => void;
27
29
  }
28
30
  export type GetInstanceById = (id: string) => {
29
31
  entity: EntityDecl;
@@ -35,4 +37,4 @@ declare global {
35
37
  }
36
38
  }
37
39
  }
38
- export declare const createServer: (schema: Schema, dataRootPath: string, instancesByEntityName: InstancesByEntityName, defaultLocales: string[], homeLayoutSections?: HomeLayoutSection[], options?: Partial<ServerOptions>, customStylesheetPath?: string) => Promise<void>;
40
+ export declare const createServer: (schema: Schema, dataRootPath: string, databaseInMemory: DatabaseInMemory, defaultLocales: string[], homeLayoutSections?: HomeLayoutSection[], options?: Partial<ServerOptions>, customStylesheetPath?: string) => Promise<void>;
@@ -16,7 +16,7 @@ const staticNodeModule = (moduleName) => {
16
16
  }
17
17
  return express.static(dirname(pathToPackageJson));
18
18
  };
19
- export const createServer = async (schema, dataRootPath, instancesByEntityName, defaultLocales, homeLayoutSections, options, customStylesheetPath) => {
19
+ export const createServer = async (schema, dataRootPath, databaseInMemory, defaultLocales, homeLayoutSections, options, customStylesheetPath) => {
20
20
  const { port } = { ...defaultOptions, ...options };
21
21
  const app = express();
22
22
  app.use(express.static(join(import.meta.dirname, "../../../../public")));
@@ -27,9 +27,12 @@ export const createServer = async (schema, dataRootPath, instancesByEntityName,
27
27
  app.use("/js/client", express.static(join(import.meta.dirname, "../../../../dist/src/web")));
28
28
  app.use("/js/shared", express.static(join(import.meta.dirname, "../../../../dist/src/shared")));
29
29
  app.use(express.json());
30
- const requestLocals = await init(schema, dataRootPath, Object.assign({}, instancesByEntityName), defaultLocales, homeLayoutSections);
30
+ const requestLocals = await init(schema, dataRootPath, databaseInMemory, defaultLocales, homeLayoutSections);
31
31
  app.use((req, _res, next) => {
32
32
  debug("%s %s", req.method, req.originalUrl);
33
+ requestLocals.setLocal = (key, value) => {
34
+ requestLocals[key] = req[key] = value;
35
+ };
33
36
  Object.assign(req, requestLocals);
34
37
  req.locales = getLocalesFromRequest(req) ?? defaultLocales;
35
38
  next();
@@ -1,6 +1,6 @@
1
- import type { InstancesByEntityName } from "../../shared/utils/instances.ts";
2
1
  import type { HomeLayoutSection } from "../config.ts";
3
2
  import type { Schema } from "../schema/Schema.ts";
3
+ import { type DatabaseInMemory } from "../utils/databaseInMemory.ts";
4
4
  import type { TSONDBRequestLocals } from "./index.ts";
5
- export declare const init: (schema: Schema, dataRootPath: string, instancesByEntityName: InstancesByEntityName, defaultLocales: string[], homeLayoutSections?: HomeLayoutSection[]) => Promise<TSONDBRequestLocals>;
5
+ export declare const init: (schema: Schema, dataRootPath: string, databaseInMemory: DatabaseInMemory, defaultLocales: string[], homeLayoutSections?: HomeLayoutSection[]) => Promise<Omit<TSONDBRequestLocals, "setLocal">>;
6
6
  export declare const reinit: (locals: TSONDBRequestLocals) => Promise<void>;
@@ -2,7 +2,8 @@ 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 { attachGitStatusToInstancesByEntityName, getInstancesByEntityName, } from "../utils/instances.js";
5
+ import { createDatabaseInMemory, getInstanceFromDatabaseInMemory, } from "../utils/databaseInMemory.js";
6
+ import { attachGitStatusToDatabaseInMemory } from "../utils/git.js";
6
7
  import { getReferencesToInstances } from "../utils/references.js";
7
8
  const debug = Debug("tsondb:server:init");
8
9
  const getGit = async (dataRootPath) => {
@@ -21,7 +22,7 @@ const getGit = async (dataRootPath) => {
21
22
  return { git };
22
23
  }
23
24
  };
24
- export const init = async (schema, dataRootPath, instancesByEntityName, defaultLocales, homeLayoutSections) => {
25
+ export const init = async (schema, dataRootPath, databaseInMemory, defaultLocales, homeLayoutSections) => {
25
26
  const { git, root: gitRoot, status: gitStatus } = await getGit(dataRootPath);
26
27
  const declarations = resolveTypeArgumentsInDecls(schema.declarations);
27
28
  debug("resolved type arguments in declarations");
@@ -29,18 +30,18 @@ export const init = async (schema, dataRootPath, instancesByEntityName, defaultL
29
30
  debug("found %d entities in declarations", entities.length);
30
31
  const entitiesByName = Object.fromEntries(entities.map(entity => [entity.name, entity]));
31
32
  const serializedDeclarationsByName = Object.fromEntries(declarations.map(decl => [decl.name, serializeNode(decl)]));
32
- const instancesByEntityNameInMemory = Object.assign({}, instancesByEntityName);
33
- const referencesToInstances = await getReferencesToInstances(instancesByEntityName, serializedDeclarationsByName);
33
+ const referencesToInstances = await getReferencesToInstances(databaseInMemory, serializedDeclarationsByName);
34
34
  debug("created references cache");
35
35
  if (gitStatus) {
36
- attachGitStatusToInstancesByEntityName(instancesByEntityName, dataRootPath, gitRoot, gitStatus);
36
+ databaseInMemory = attachGitStatusToDatabaseInMemory(databaseInMemory, dataRootPath, gitRoot, gitStatus);
37
37
  debug("retrieved git status to instances");
38
38
  }
39
39
  const getInstanceById = id => {
40
- for (const entityName in instancesByEntityNameInMemory) {
41
- const instance = instancesByEntityNameInMemory[entityName]?.find(i => i.id === id);
42
- if (instance && entitiesByName[entityName]) {
43
- return { entity: entitiesByName[entityName], instance };
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 };
44
45
  }
45
46
  }
46
47
  return undefined;
@@ -51,7 +52,7 @@ export const init = async (schema, dataRootPath, instancesByEntityName, defaultL
51
52
  dataRoot: dataRootPath,
52
53
  declarations: declarations,
53
54
  entities: entities,
54
- instancesByEntityName: instancesByEntityNameInMemory,
55
+ databaseInMemory,
55
56
  entitiesByName: entitiesByName,
56
57
  serializedDeclarationsByName,
57
58
  localeEntity: schema.localeEntity,
@@ -64,10 +65,11 @@ export const init = async (schema, dataRootPath, instancesByEntityName, defaultL
64
65
  return requestLocals;
65
66
  };
66
67
  export const reinit = async (locals) => {
67
- locals.instancesByEntityName = await getInstancesByEntityName(locals.dataRoot, locals.entities);
68
- locals.referencesToInstances = await getReferencesToInstances(locals.instancesByEntityName, locals.serializedDeclarationsByName);
68
+ let databaseInMemory = await createDatabaseInMemory(locals.dataRoot, locals.entities);
69
69
  const gitStatus = (await locals.git.checkIsRepo()) ? await locals.git.status() : undefined;
70
70
  if (locals.gitRoot && gitStatus) {
71
- attachGitStatusToInstancesByEntityName(locals.instancesByEntityName, locals.dataRoot, locals.gitRoot, gitStatus);
71
+ databaseInMemory = attachGitStatusToDatabaseInMemory(databaseInMemory, locals.dataRoot, locals.gitRoot, gitStatus);
72
72
  }
73
+ locals.setLocal("databaseInMemory", databaseInMemory);
74
+ locals.setLocal("referencesToInstances", await getReferencesToInstances(databaseInMemory, locals.serializedDeclarationsByName));
73
75
  };
@@ -1,6 +1,3 @@
1
- import { type Result } from "../../../shared/utils/result.ts";
2
- import { type EntityTaggedInstanceContainerWithChildInstances, type UnsafeEntityTaggedInstanceContainerWithChildInstances } from "../../utils/childInstances.ts";
3
1
  import type { GetChildInstancesForInstanceId } from "../../utils/displayName.ts";
4
2
  import type { TSONDBRequestLocals } from "../index.ts";
5
- export declare const updateLocalsAfterInstanceTreeChangeToReflectDiskState: (locals: TSONDBRequestLocals, parentId: string | undefined, parentEntityName: string, oldChildInstances: EntityTaggedInstanceContainerWithChildInstances[], childInstances: UnsafeEntityTaggedInstanceContainerWithChildInstances[]) => Promise<Result<void, [code: number, message: string]>>;
6
3
  export declare const createChildInstancesForInstanceIdGetter: (locals: TSONDBRequestLocals) => GetChildInstancesForInstanceId;
@@ -1,46 +1,5 @@
1
- import { error, ok } from "../../../shared/utils/result.js";
2
1
  import { isEntityDeclWithParentReference } from "../../schema/index.js";
3
- import { getChildInstancesFromEntity, } from "../../utils/childInstances.js";
4
- import { updateLocalsAfterInstanceChangeToReflectDiskState } from "./instanceOperations.js";
5
- export const updateLocalsAfterInstanceTreeChangeToReflectDiskState = async (locals, parentId, parentEntityName, oldChildInstances, childInstances) => {
6
- const parentEntity = locals.entitiesByName[parentEntityName];
7
- if (parentEntity === undefined) {
8
- return error([400, `Unknown entity "${parentEntityName}"`]);
9
- }
10
- if (parentId !== undefined) {
11
- // existing parent, some child instances may already exist
12
- for (const oldChildInstance of oldChildInstances) {
13
- const newChildInstance = childInstances.find(ci => ci.id === oldChildInstance.id);
14
- if (newChildInstance === undefined) {
15
- await updateLocalsAfterInstanceChangeToReflectDiskState(locals, oldChildInstance.entityName, oldChildInstance.id, undefined);
16
- }
17
- else {
18
- await updateLocalsAfterInstanceChangeToReflectDiskState(locals, oldChildInstance.entityName, oldChildInstance.id, newChildInstance.content);
19
- }
20
- }
21
- for (const newChildInstance of childInstances.filter(predicate => predicate.id === undefined)) {
22
- if (newChildInstance.id !== undefined) {
23
- await updateLocalsAfterInstanceChangeToReflectDiskState(locals, newChildInstance.entityName, newChildInstance.id, newChildInstance.content);
24
- }
25
- }
26
- }
27
- else {
28
- // new parent, all child instances are new
29
- for (const newChildInstance of childInstances) {
30
- if (newChildInstance.id !== undefined) {
31
- await updateLocalsAfterInstanceChangeToReflectDiskState(locals, newChildInstance.entityName, newChildInstance.id, newChildInstance.content);
32
- }
33
- }
34
- }
35
- // check recursively for child instances of child instances
36
- for (const childInstance of childInstances) {
37
- const oldChildInstance = childInstance.id
38
- ? oldChildInstances.find(ci => ci.id === childInstance.id)
39
- : undefined;
40
- await updateLocalsAfterInstanceTreeChangeToReflectDiskState(locals, childInstance.id, childInstance.entityName, oldChildInstance ? oldChildInstance.childInstances : [], childInstance.childInstances);
41
- }
42
- return ok();
43
- };
2
+ import { getChildInstancesFromEntity } from "../../utils/childInstances.js";
44
3
  export const createChildInstancesForInstanceIdGetter = (locals) => (parentEntityName, parentId, childEntityName) => {
45
4
  const parentEntity = locals.entitiesByName[parentEntityName];
46
5
  const childEntity = locals.entitiesByName[childEntityName];
@@ -49,5 +8,5 @@ export const createChildInstancesForInstanceIdGetter = (locals) => (parentEntity
49
8
  !isEntityDeclWithParentReference(childEntity)) {
50
9
  return [];
51
10
  }
52
- return getChildInstancesFromEntity(locals.instancesByEntityName, parentEntity, parentId, childEntity);
11
+ return getChildInstancesFromEntity(locals.databaseInMemory, parentEntity, parentId, childEntity);
53
12
  };
@@ -2,7 +2,6 @@ import type { InstanceContainer } from "../../../shared/utils/instances.ts";
2
2
  import { type Result } from "../../../shared/utils/result.ts";
3
3
  import { type CreatedEntityTaggedInstanceContainerWithChildInstances, type UpdatedEntityTaggedInstanceContainerWithChildInstances } from "../../utils/childInstances.ts";
4
4
  import type { TSONDBRequestLocals } from "../index.ts";
5
- export declare const updateLocalsAfterInstanceChangeToReflectDiskState: (locals: TSONDBRequestLocals, entityName: string, instanceId: string, newInstanceContent: unknown) => Promise<InstanceContainer>;
6
- export declare const createInstance: (locals: TSONDBRequestLocals, instance: CreatedEntityTaggedInstanceContainerWithChildInstances, idQueryParam: unknown) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
7
- export declare const updateInstance: (locals: TSONDBRequestLocals, instance: UpdatedEntityTaggedInstanceContainerWithChildInstances) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
8
- export declare const deleteInstance: (locals: TSONDBRequestLocals, entityName: string, instanceId: string) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
5
+ export declare const createInstance: (locals: TSONDBRequestLocals, instance: CreatedEntityTaggedInstanceContainerWithChildInstances, idQueryParam: unknown) => Promise<Result<InstanceContainer, Error>>;
6
+ export declare const updateInstance: (locals: TSONDBRequestLocals, instance: UpdatedEntityTaggedInstanceContainerWithChildInstances) => Promise<Result<InstanceContainer, Error>>;
7
+ export declare const deleteInstance: (locals: TSONDBRequestLocals, entityName: string, instanceId: string) => Promise<Result<InstanceContainer, Error>>;