tsondb 0.12.3 → 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 (52) 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 +2 -5
  8. package/dist/src/node/server/api/instances.js +2 -3
  9. package/dist/src/node/server/index.d.ts +4 -3
  10. package/dist/src/node/server/index.js +2 -2
  11. package/dist/src/node/server/init.d.ts +2 -2
  12. package/dist/src/node/server/init.js +14 -11
  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/schema/declarations/EntityDecl.d.ts +2 -2
  37. package/dist/src/shared/utils/dictionary.d.ts +17 -0
  38. package/dist/src/shared/utils/dictionary.js +76 -0
  39. package/dist/src/shared/utils/instances.d.ts +2 -2
  40. package/dist/src/shared/utils/result.d.ts +4 -0
  41. package/dist/src/shared/utils/result.js +4 -0
  42. package/dist/src/web/components/InstanceRouteSkeleton.d.ts +9 -8
  43. package/dist/src/web/components/typeInputs/ChildEntitiesTypeInput.js +1 -1
  44. package/dist/src/web/hooks/useGitClient.js +0 -1
  45. package/dist/src/web/hooks/useMappedAPIResource.js +2 -4
  46. package/dist/src/web/utils/typeSkeleton.d.ts +2 -2
  47. package/dist/src/web/utils/typeSkeleton.js +1 -1
  48. package/package.json +3 -3
  49. package/dist/src/node/utils/instanceOperations.d.ts +0 -14
  50. package/dist/src/node/utils/instanceOperations.js +0 -88
  51. package/dist/src/node/utils/instances.d.ts +0 -6
  52. package/dist/src/node/utils/instances.js +0 -29
@@ -1,107 +1,67 @@
1
- import { removeAt } from "../../../shared/utils/array.js";
2
1
  import { error, isError, ok } from "../../../shared/utils/result.js";
3
- import { checkWriteInstanceTreePossible, getChildInstances, unsafeApplyInstanceTree, } from "../../utils/childInstances.js";
4
- import { getFileNameForId } from "../../utils/files.js";
5
- import { getGitFileStatusFromStatusResult } from "../../utils/git.js";
6
- import * as Instances from "../../utils/instanceOperations.js";
7
- import { updateReferencesToInstances } from "../../utils/references.js";
8
- import { updateLocalsAfterInstanceTreeChangeToReflectDiskState } from "./childInstances.js";
9
- export const updateLocalsAfterInstanceChangeToReflectDiskState = async (locals, entityName, instanceId, newInstanceContent) => {
10
- const oldInstances = locals.instancesByEntityName[entityName] ?? [];
11
- const oldInstanceContainerIndex = oldInstances.findIndex(instance => instance.id === instanceId);
12
- const oldInstanceContainer = oldInstanceContainerIndex > -1 ? oldInstances[oldInstanceContainerIndex] : undefined;
13
- const instanceContainer = oldInstanceContainer ?? {
14
- id: instanceId,
15
- content: undefined,
16
- };
17
- // old content as alternative if instance is deleted to restore old instance container
18
- instanceContainer.content = newInstanceContent ?? instanceContainer.content;
19
- instanceContainer.gitStatus =
20
- locals.gitRoot === undefined
21
- ? undefined
22
- : getGitFileStatusFromStatusResult(await locals.git.status(), locals.gitRoot, locals.dataRoot, entityName, getFileNameForId(instanceId));
23
- if (oldInstanceContainer === undefined) {
24
- locals.instancesByEntityName[entityName] = [...oldInstances, instanceContainer];
25
- }
26
- else if (newInstanceContent === undefined) {
27
- locals.instancesByEntityName[entityName] = removeAt(oldInstances, oldInstanceContainerIndex);
28
- }
29
- Object.assign(locals.referencesToInstances, updateReferencesToInstances(locals.entitiesByName, locals.referencesToInstances, entityName, instanceId, oldInstanceContainer?.content, newInstanceContent));
30
- return instanceContainer;
31
- };
2
+ import { getChildInstances, saveInstanceTree, } from "../../utils/childInstances.js";
3
+ import { getInstanceOfEntityFromDatabaseInMemory } from "../../utils/databaseInMemory.js";
4
+ import { runDatabaseTransaction } from "../../utils/databaseTransactions.js";
5
+ import { HTTPError } from "../../utils/error.js";
32
6
  export const createInstance = async (locals, instance, idQueryParam) => {
33
7
  const entity = locals.entitiesByName[instance.entityName];
34
8
  if (entity === undefined) {
35
- return error([400, "Entity not found"]);
9
+ return error(new HTTPError(400, "Entity not found"));
36
10
  }
37
- const checkTreeResult = checkWriteInstanceTreePossible(locals.entitiesByName, locals.instancesByEntityName, locals.referencesToInstances, instance.id, entity.name, [], instance.childInstances);
38
- if (isError(checkTreeResult)) {
39
- return checkTreeResult;
11
+ const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.entitiesByName, undefined, locals.localeEntity, instance.entityName, undefined, instance, idQueryParam, res));
12
+ if (isError(databaseTransactionResult)) {
13
+ return databaseTransactionResult;
40
14
  }
41
- const res = await Instances.createInstance(locals.dataRoot, locals.localeEntity, locals.instancesByEntityName, entity, instance.content, idQueryParam);
42
- if (isError(res)) {
43
- return res;
44
- }
45
- const newInstanceId = res.value;
46
- const treeRes = await unsafeApplyInstanceTree(locals.dataRoot, locals.entitiesByName, locals.instancesByEntityName, newInstanceId, instance.entityName, [], instance.childInstances);
47
- if (isError(treeRes)) {
48
- return treeRes;
49
- }
50
- const instanceContainer = await updateLocalsAfterInstanceChangeToReflectDiskState(locals, entity.name, newInstanceId, instance.content);
51
- await updateLocalsAfterInstanceTreeChangeToReflectDiskState(locals, newInstanceId, entity.name, [], instance.childInstances);
15
+ const { db: newDatabaseInMemory, refs: newReferencesToInstances, instanceContainer, } = databaseTransactionResult.value;
16
+ locals.setLocal("databaseInMemory", newDatabaseInMemory);
17
+ locals.setLocal("referencesToInstances", newReferencesToInstances);
52
18
  return ok(instanceContainer);
53
19
  };
54
20
  export const updateInstance = async (locals, instance) => {
55
- const instanceContainer = locals.instancesByEntityName[instance.entityName]?.find(instance => instance.id === instance.id);
21
+ const instanceContainer = getInstanceOfEntityFromDatabaseInMemory(locals.databaseInMemory, instance.entityName, instance.id);
56
22
  if (instanceContainer === undefined) {
57
- return error([404, "Instance not found"]);
23
+ return error(new HTTPError(400, "Instance not found"));
58
24
  }
59
25
  const entity = locals.entitiesByName[instance.entityName];
60
26
  if (entity === undefined) {
61
- return error([400, "Entity not found"]);
62
- }
63
- const oldChildInstances = getChildInstances(locals.instancesByEntityName, entity, instance.id);
64
- const checkTreeResult = checkWriteInstanceTreePossible(locals.entitiesByName, locals.instancesByEntityName, locals.referencesToInstances, instance.id, entity.name, oldChildInstances, instance.childInstances);
65
- if (isError(checkTreeResult)) {
66
- return checkTreeResult;
67
- }
68
- const res = await Instances.updateInstance(locals.dataRoot, locals.instancesByEntityName, entity, instance.id, instance.content);
69
- if (isError(res)) {
70
- return res;
71
- }
72
- const treeRes = await unsafeApplyInstanceTree(locals.dataRoot, locals.entitiesByName, locals.instancesByEntityName, instance.id, instance.entityName, oldChildInstances, instance.childInstances);
73
- if (isError(treeRes)) {
74
- return treeRes;
75
- }
76
- const newInstanceContainer = await updateLocalsAfterInstanceChangeToReflectDiskState(locals, entity.name, instance.id, instance.content);
77
- await updateLocalsAfterInstanceTreeChangeToReflectDiskState(locals, instance.id, instance.entityName, oldChildInstances, instance.childInstances);
27
+ return error(new HTTPError(400, "Entity not found"));
28
+ }
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.entitiesByName, undefined, locals.localeEntity, instance.entityName, {
31
+ id: instance.id,
32
+ content: instanceContainer.content,
33
+ childInstances: oldChildInstances,
34
+ entityName: instance.entityName,
35
+ }, instance, undefined, res));
36
+ if (isError(databaseTransactionResult)) {
37
+ return databaseTransactionResult;
38
+ }
39
+ const { db: newDatabaseInMemory, refs: newReferencesToInstances, instanceContainer: newInstanceContainer, } = databaseTransactionResult.value;
40
+ locals.setLocal("databaseInMemory", newDatabaseInMemory);
41
+ locals.setLocal("referencesToInstances", newReferencesToInstances);
78
42
  return ok(newInstanceContainer);
79
43
  };
80
44
  export const deleteInstance = async (locals, entityName, instanceId) => {
81
- const instances = locals.instancesByEntityName[entityName] ?? [];
82
- const instanceContainerIndex = instances.findIndex(instance => instance.id === instanceId);
83
- const instanceContainer = instances[instanceContainerIndex];
45
+ const instanceContainer = getInstanceOfEntityFromDatabaseInMemory(locals.databaseInMemory, entityName, instanceId);
84
46
  if (instanceContainer === undefined) {
85
- return error([404, "Instance not found"]);
47
+ return error(new HTTPError(400, "Instance not found"));
86
48
  }
87
49
  const entity = locals.entitiesByName[entityName];
88
50
  if (entity === undefined) {
89
- return error([400, "Entity not found"]);
90
- }
91
- const oldChildInstances = getChildInstances(locals.instancesByEntityName, entity, instanceContainer.id);
92
- const checkTreeResult = checkWriteInstanceTreePossible(locals.entitiesByName, locals.instancesByEntityName, locals.referencesToInstances, instanceContainer.id, entityName, oldChildInstances, []);
93
- if (isError(checkTreeResult)) {
94
- return checkTreeResult;
51
+ return error(new HTTPError(400, "Entity not found"));
95
52
  }
96
- const res = await Instances.deleteInstance(locals.dataRoot, locals.referencesToInstances, entityName, instanceId);
97
- if (isError(res)) {
98
- return res;
99
- }
100
- const treeRes = await unsafeApplyInstanceTree(locals.dataRoot, locals.entitiesByName, locals.instancesByEntityName, instanceContainer.id, entityName, oldChildInstances, []);
101
- if (isError(treeRes)) {
102
- return treeRes;
103
- }
104
- const oldInstanceContainer = await updateLocalsAfterInstanceChangeToReflectDiskState(locals, entity.name, instanceContainer.id, undefined);
105
- await updateLocalsAfterInstanceTreeChangeToReflectDiskState(locals, instanceContainer.id, entityName, oldChildInstances, []);
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.entitiesByName, undefined, locals.localeEntity, entityName, {
55
+ id: instanceId,
56
+ content: instanceContainer.content,
57
+ childInstances: oldChildInstances,
58
+ entityName,
59
+ }, undefined, undefined, res));
60
+ if (isError(databaseTransactionResult)) {
61
+ return databaseTransactionResult;
62
+ }
63
+ const { db: newDatabaseInMemory, refs: newReferencesToInstances, instanceContainer: oldInstanceContainer, } = databaseTransactionResult.value;
64
+ locals.setLocal("databaseInMemory", newDatabaseInMemory);
65
+ locals.setLocal("referencesToInstances", newReferencesToInstances);
106
66
  return ok(oldInstanceContainer);
107
67
  };
@@ -1,17 +1,17 @@
1
1
  import type { GitFileStatus } from "../../shared/utils/git.ts";
2
- import type { InstanceContainer, InstancesByEntityName } from "../../shared/utils/instances.ts";
3
- import { type Result } from "../../shared/utils/result.ts";
2
+ import type { InstanceContainer, InstanceContent } from "../../shared/utils/instances.ts";
4
3
  import type { EntityDecl, EntityDeclWithParentReference } from "../schema/declarations/EntityDecl.ts";
5
- import { type ReferencesToInstances } from "./references.ts";
4
+ import { type DatabaseInMemory } from "./databaseInMemory.ts";
5
+ import type { TransactionResult } from "./databaseTransactions.ts";
6
6
  export interface ChildInstanceContainer {
7
7
  id?: string;
8
- content: unknown;
8
+ content: InstanceContent;
9
9
  gitStatus?: GitFileStatus;
10
10
  }
11
11
  export interface EntityTaggedInstanceContainer {
12
12
  entityName: string;
13
13
  id: string;
14
- content: unknown;
14
+ content: InstanceContent;
15
15
  }
16
16
  export interface CreatedEntityTaggedInstanceContainerWithChildInstances extends GenEntityTaggedInstanceContainerWithChildInstances<undefined, UnsafeEntityTaggedInstanceContainerWithChildInstances> {
17
17
  }
@@ -24,10 +24,11 @@ export interface EntityTaggedInstanceContainerWithChildInstances extends GenEnti
24
24
  export interface GenEntityTaggedInstanceContainerWithChildInstances<ID extends string | undefined, C> {
25
25
  entityName: string;
26
26
  id: ID;
27
- content: unknown;
27
+ content: InstanceContent;
28
28
  childInstances: C[];
29
29
  }
30
- export declare const getChildInstancesFromEntity: (instancesByEntityName: InstancesByEntityName, parentEntity: EntityDecl, parentId: string, childEntity: EntityDeclWithParentReference) => InstanceContainer[];
31
- export declare const getChildInstances: (instancesByEntityName: InstancesByEntityName, parentEntity: EntityDecl, parentId: string, recursive?: boolean) => EntityTaggedInstanceContainerWithChildInstances[];
32
- export declare const checkWriteInstanceTreePossible: (entitiesByName: Record<string, EntityDecl>, instancesByEntityName: InstancesByEntityName, referencesToInstances: ReferencesToInstances, parentId: string | undefined, parentEntityName: string, oldChildInstances: EntityTaggedInstanceContainerWithChildInstances[], childInstances: UnsafeEntityTaggedInstanceContainerWithChildInstances[]) => Result<void, [code: number, message: string]>;
33
- export declare const unsafeApplyInstanceTree: (dataRoot: string, entitiesByName: Record<string, EntityDecl>, instancesByEntityName: InstancesByEntityName, parentId: string | undefined, parentEntityName: string, oldChildInstances: EntityTaggedInstanceContainerWithChildInstances[], childInstances: UnsafeEntityTaggedInstanceContainerWithChildInstances[]) => Promise<Result<void, [code: number, message: string]>>;
30
+ export declare const getChildInstancesFromEntity: (databaseInMemory: DatabaseInMemory, parentEntity: EntityDecl, parentId: string, childEntity: EntityDeclWithParentReference) => InstanceContainer[];
31
+ export declare const getChildInstances: (databaseInMemory: DatabaseInMemory, parentEntity: EntityDecl, parentId: string, recursive?: boolean) => EntityTaggedInstanceContainerWithChildInstances[];
32
+ export declare const saveInstanceTree: (entitiesByName: Record<string, EntityDecl>, parentId: string | undefined, localeEntity: EntityDecl | undefined, entityName: string, oldInstance: EntityTaggedInstanceContainerWithChildInstances | undefined, newInstance: UnsafeEntityTaggedInstanceContainerWithChildInstances | undefined, customId: unknown, res: TransactionResult) => TransactionResult<{
33
+ instanceContainer: InstanceContainer;
34
+ }>;
@@ -1,9 +1,10 @@
1
1
  import { hasKey } from "../../shared/utils/object.js";
2
- import { error, isError, ok } from "../../shared/utils/result.js";
3
- import { reduceNodes } from "../schema/index.js";
2
+ import { error, isError, map, ok } from "../../shared/utils/result.js";
3
+ import { isEntityDeclWithParentReference, reduceNodes } from "../schema/index.js";
4
4
  import { isChildEntitiesType } from "../schema/types/references/ChildEntitiesType.js";
5
- import { checkCreateNonLocaleInstancePossible, checkDeleteInstancePossible, checkUpdateInstancePossible, createNewId, unsafeDeleteInstance, unsafeWriteInstance, } from "./instanceOperations.js";
6
- import {} from "./references.js";
5
+ import { getInstancesOfEntityFromDatabaseInMemory, } from "./databaseInMemory.js";
6
+ import { HTTPError } from "./error.js";
7
+ import { createInstance, deleteInstance, updateInstance } from "./instanceTransactionSteps.js";
7
8
  const isParentReferenceReferencingParent = (value, parentEntityName, parentId) => {
8
9
  if (typeof value === "object" && value !== null && hasKey(value, "kind")) {
9
10
  return (value.kind === parentEntityName &&
@@ -17,149 +18,78 @@ const isParentReferenceReferencingParent = (value, parentEntityName, parentId) =
17
18
  return false;
18
19
  }
19
20
  };
20
- export const getChildInstancesFromEntity = (instancesByEntityName, parentEntity, parentId, childEntity) => instancesByEntityName[childEntity.name]?.filter(instanceContainer => typeof instanceContainer.content === "object" &&
21
- instanceContainer.content !== null &&
22
- hasKey(instanceContainer.content, childEntity.parentReferenceKey) &&
23
- isParentReferenceReferencingParent(instanceContainer.content[childEntity.parentReferenceKey], parentEntity.name, parentId)) ?? [];
24
- export const getChildInstances = (instancesByEntityName, parentEntity, parentId, recursive = true) => {
21
+ export const getChildInstancesFromEntity = (databaseInMemory, parentEntity, parentId, childEntity) => getInstancesOfEntityFromDatabaseInMemory(databaseInMemory, childEntity.name).filter(instanceContainer => hasKey(instanceContainer.content, childEntity.parentReferenceKey) &&
22
+ isParentReferenceReferencingParent(instanceContainer.content[childEntity.parentReferenceKey], parentEntity.name, parentId));
23
+ export const getChildInstances = (databaseInMemory, parentEntity, parentId, recursive = true) => {
25
24
  const childEntities = reduceNodes((_parentNodes, node, collectedResults) => isChildEntitiesType(node) ? [...collectedResults, node.entity] : collectedResults, [parentEntity], { followIncludes: true });
26
- return childEntities.flatMap(childEntity => getChildInstancesFromEntity(instancesByEntityName, parentEntity, parentId, childEntity).map(container => ({
25
+ return childEntities.flatMap(childEntity => getChildInstancesFromEntity(databaseInMemory, parentEntity, parentId, childEntity).map(container => ({
27
26
  ...container,
28
27
  entityName: childEntity.name,
29
28
  childInstances: recursive
30
- ? getChildInstances(instancesByEntityName, childEntity, container.id)
29
+ ? getChildInstances(databaseInMemory, childEntity, container.id)
31
30
  : [],
32
31
  })));
33
32
  };
34
- export const checkWriteInstanceTreePossible = (entitiesByName, instancesByEntityName, referencesToInstances, parentId, parentEntityName, oldChildInstances, childInstances) => {
35
- const parentEntity = entitiesByName[parentEntityName];
36
- if (parentEntity === undefined) {
37
- return error([400, `Unknown entity "${parentEntityName}"`]);
38
- }
39
- if (parentId !== undefined) {
40
- // existing parent, some child instances may already exist
41
- for (const oldChildInstance of oldChildInstances) {
42
- const newChildInstance = childInstances.find(ci => ci.id === oldChildInstance.id);
43
- if (newChildInstance === undefined) {
44
- const prerequisiteCheckResult = checkDeleteInstancePossible(referencesToInstances, oldChildInstance.id);
45
- if (isError(prerequisiteCheckResult)) {
46
- return prerequisiteCheckResult;
47
- }
48
- }
49
- else {
50
- const entity = entitiesByName[newChildInstance.entityName];
51
- if (entity === undefined) {
52
- return error([400, `Unknown entity "${newChildInstance.entityName}"`]);
53
- }
54
- const prerequisiteCheckResult = checkUpdateInstancePossible(instancesByEntityName, entity, newChildInstance.content);
55
- if (isError(prerequisiteCheckResult)) {
56
- return prerequisiteCheckResult;
57
- }
58
- }
59
- }
60
- for (const newChildInstance of childInstances.filter(predicate => predicate.id === undefined)) {
61
- const entity = entitiesByName[newChildInstance.entityName];
62
- if (entity === undefined) {
63
- return error([400, `Unknown entity "${newChildInstance.entityName}"`]);
64
- }
65
- const prerequisiteCheckResult = checkCreateNonLocaleInstancePossible(instancesByEntityName, entity, newChildInstance.content);
66
- if (isError(prerequisiteCheckResult)) {
67
- return prerequisiteCheckResult;
68
- }
33
+ const prepareNewChildInstanceContent = (entity, parentId, content) => {
34
+ if (isEntityDeclWithParentReference(entity)) {
35
+ if (parentId === undefined) {
36
+ return error(new HTTPError(400, `Cannot create instance of child entity "${entity.name}" without parent reference`));
69
37
  }
38
+ return ok({
39
+ ...content,
40
+ [entity.parentReferenceKey]: parentId,
41
+ });
70
42
  }
71
43
  else {
72
- // new parent, all child instances are new
73
- for (const newChildInstance of childInstances) {
74
- const entity = entitiesByName[newChildInstance.entityName];
75
- if (entity === undefined) {
76
- return error([400, `Unknown entity "${newChildInstance.entityName}"`]);
77
- }
78
- const prerequisiteCheckResult = checkCreateNonLocaleInstancePossible(instancesByEntityName, entity, newChildInstance.content);
79
- if (isError(prerequisiteCheckResult)) {
80
- return prerequisiteCheckResult;
81
- }
82
- }
83
- }
84
- // check recursively for child instances of child instances
85
- for (const childInstance of childInstances) {
86
- const oldChildInstance = childInstance.id
87
- ? oldChildInstances.find(ci => ci.id === childInstance.id)
88
- : undefined;
89
- const prerequisiteCheckResult = checkWriteInstanceTreePossible(entitiesByName, instancesByEntityName, referencesToInstances, childInstance.id, childInstance.entityName, oldChildInstance ? oldChildInstance.childInstances : [], childInstance.childInstances);
90
- if (isError(prerequisiteCheckResult)) {
91
- return prerequisiteCheckResult;
92
- }
44
+ return ok(content);
93
45
  }
94
- return ok();
95
46
  };
96
- export const unsafeApplyInstanceTree = async (dataRoot, entitiesByName, instancesByEntityName, parentId, parentEntityName, oldChildInstances, childInstances) => {
97
- const parentEntity = entitiesByName[parentEntityName];
98
- if (parentEntity === undefined) {
99
- return error([400, `Unknown entity "${parentEntityName}"`]);
47
+ export const saveInstanceTree = (entitiesByName, parentId, localeEntity, entityName, oldInstance, newInstance, customId, res) => {
48
+ if (isError(res)) {
49
+ return res;
100
50
  }
101
- if (parentId !== undefined) {
102
- // existing parent, some child instances may already exist
103
- for (const oldChildInstance of oldChildInstances) {
104
- const newChildInstance = childInstances.find(ci => ci.id === oldChildInstance.id);
105
- if (newChildInstance === undefined) {
106
- const oldGrandChildInstancesResult = await unsafeApplyInstanceTree(dataRoot, entitiesByName, instancesByEntityName, oldChildInstance.id, oldChildInstance.entityName, oldChildInstance.childInstances, []);
107
- if (isError(oldGrandChildInstancesResult)) {
108
- return oldGrandChildInstancesResult;
109
- }
110
- const prerequisiteCheckResult = await unsafeDeleteInstance(dataRoot, oldChildInstance.entityName, oldChildInstance.id);
111
- if (isError(prerequisiteCheckResult)) {
112
- return prerequisiteCheckResult;
113
- }
114
- }
115
- else {
116
- const entity = entitiesByName[newChildInstance.entityName];
117
- if (entity === undefined) {
118
- return error([400, `Unknown entity "${newChildInstance.entityName}"`]);
119
- }
120
- const prerequisiteCheckResult = await unsafeWriteInstance(dataRoot, entity, oldChildInstance.id, newChildInstance.content);
121
- if (isError(prerequisiteCheckResult)) {
122
- return prerequisiteCheckResult;
123
- }
124
- }
125
- }
126
- for (const newChildInstance of childInstances.filter(predicate => predicate.id === undefined)) {
127
- const entity = entitiesByName[newChildInstance.entityName];
128
- if (entity === undefined) {
129
- return error([400, `Unknown entity "${newChildInstance.entityName}"`]);
130
- }
131
- const newInstanceID = createNewId();
132
- const prerequisiteCheckResult = await unsafeWriteInstance(dataRoot, entity, newInstanceID, newChildInstance.content);
133
- if (isError(prerequisiteCheckResult)) {
134
- return prerequisiteCheckResult;
135
- }
136
- newChildInstance.id = newInstanceID;
51
+ const entity = entitiesByName[entityName];
52
+ if (entity === undefined) {
53
+ return error(new HTTPError(400, `Unknown entity "${entityName}"`));
54
+ }
55
+ if (newInstance === undefined) {
56
+ if (oldInstance === undefined) {
57
+ // no-op
58
+ return map(res, data => ({ ...data, instanceContainer: { id: "", content: {} } }));
137
59
  }
60
+ // delete all child instances recursively
61
+ const deletedRes = deleteInstance(res, entity, oldInstance.id);
62
+ return map(oldInstance.childInstances.reduce((resAcc, oldChildInstance) => saveInstanceTree(entitiesByName, oldInstance.id, localeEntity, oldInstance.entityName, oldChildInstance, undefined, undefined, resAcc), deletedRes), data => ({
63
+ ...data,
64
+ instanceContainer: { id: oldInstance.id, content: oldInstance.content },
65
+ }));
138
66
  }
139
67
  else {
140
- // new parent, all child instances are new
141
- for (const newChildInstance of childInstances) {
142
- const entity = entitiesByName[newChildInstance.entityName];
143
- if (entity === undefined) {
144
- return error([400, `Unknown entity "${newChildInstance.entityName}"`]);
145
- }
146
- const newInstanceID = createNewId();
147
- const prerequisiteCheckResult = await unsafeWriteInstance(dataRoot, entity, newInstanceID, newChildInstance.content);
148
- if (isError(prerequisiteCheckResult)) {
149
- return prerequisiteCheckResult;
150
- }
151
- newChildInstance.id = newInstanceID;
68
+ const preparedContent = newInstance.id === undefined
69
+ ? prepareNewChildInstanceContent(entity, parentId, newInstance.content)
70
+ : ok(newInstance.content);
71
+ if (isError(preparedContent)) {
72
+ return preparedContent;
152
73
  }
153
- }
154
- // check recursively for child instances of child instances
155
- for (const childInstance of childInstances) {
156
- const oldChildInstance = childInstance.id
157
- ? oldChildInstances.find(ci => ci.id === childInstance.id)
158
- : undefined;
159
- const prerequisiteCheckResult = await unsafeApplyInstanceTree(dataRoot, entitiesByName, instancesByEntityName, childInstance.id, childInstance.entityName, oldChildInstance ? oldChildInstance.childInstances : [], childInstance.childInstances);
160
- if (isError(prerequisiteCheckResult)) {
161
- return prerequisiteCheckResult;
74
+ const setRes = newInstance.id === undefined
75
+ ? createInstance(res, localeEntity, entity, preparedContent.value, customId)
76
+ : map(updateInstance(res, entity, newInstance.id, preparedContent.value), data => ({
77
+ ...data,
78
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
79
+ instanceId: newInstance.id,
80
+ }));
81
+ if (isError(setRes)) {
82
+ return setRes;
162
83
  }
84
+ const instanceId = setRes.value.instanceId;
85
+ const setResWithoutInfo = ok({ ...setRes.value, additionalInformation: undefined });
86
+ return map(newInstance.childInstances
87
+ .filter(newChildInstance => newChildInstance.id === undefined)
88
+ .reduce((resAcc, newChildInstance) => saveInstanceTree(entitiesByName, instanceId, localeEntity, newChildInstance.entityName, undefined, newChildInstance, undefined, resAcc), oldInstance
89
+ ? oldInstance.childInstances.reduce((resAcc, oldChildInstance) => saveInstanceTree(entitiesByName, instanceId, localeEntity, oldChildInstance.entityName, oldChildInstance, newInstance.childInstances.find(ci => ci.id === oldChildInstance.id), undefined, resAcc), setResWithoutInfo)
90
+ : setResWithoutInfo), data => ({
91
+ ...data,
92
+ instanceContainer: { id: instanceId, content: preparedContent.value },
93
+ }));
163
94
  }
164
- return ok();
165
95
  };
@@ -0,0 +1,18 @@
1
+ import { type Dictionary } from "../../shared/utils/dictionary.ts";
2
+ import type { InstanceContainer, InstanceContent } from "../../shared/utils/instances.ts";
3
+ import type { EntityDecl } from "../schema/index.ts";
4
+ export type DatabaseInMemory = Dictionary<InstancesInMemory>;
5
+ export type InstancesInMemory = Dictionary<InstanceContainer>;
6
+ export declare const emptyDatabaseInMemory: DatabaseInMemory;
7
+ export declare const getInstanceFromDatabaseInMemory: (db: DatabaseInMemory, instanceId: string) => [entityName: string, InstanceContainer] | undefined;
8
+ export declare const getInstanceOfEntityFromDatabaseInMemory: (db: DatabaseInMemory, entityName: string, instanceId: string) => InstanceContainer | undefined;
9
+ export declare const getInstancesOfEntityFromDatabaseInMemory: (db: DatabaseInMemory, entityName: string) => InstanceContainer[];
10
+ export declare const getGroupedInstancesFromDatabaseInMemory: (db: DatabaseInMemory) => [entityName: string, InstanceContainer[]][];
11
+ export declare const forEachInstanceOfEntityInDatabaseInMemory: (db: DatabaseInMemory, entityName: string, fn: (instance: InstanceContainer) => void) => void;
12
+ export declare const asyncForEachInstanceOfEntityInDatabaseInMemory: (db: DatabaseInMemory, entityName: string, fn: (instance: InstanceContainer) => Promise<void>) => Promise<void>;
13
+ export declare const forEachInstanceInDatabaseInMemory: (db: DatabaseInMemory, fn: (entityName: string, instance: InstanceContainer) => void) => void;
14
+ export declare const asyncForEachInstanceInDatabaseInMemory: (db: DatabaseInMemory, fn: (entityName: string, instance: InstanceContainer) => Promise<void>) => Promise<void>;
15
+ export declare const countInstancesOfEntityInDatabaseInMemory: (db: DatabaseInMemory, entityName: string) => number;
16
+ export declare const createDatabaseInMemory: (dataRoot: string, entities: readonly EntityDecl[]) => Promise<DatabaseInMemory>;
17
+ export declare const setInstanceInDatabaseInMemory: (db: DatabaseInMemory, entityName: string, instance: InstanceContainer) => [DatabaseInMemory, oldInstance: InstanceContent | undefined];
18
+ export declare const deleteInstanceInDatabaseInMemory: (db: DatabaseInMemory, entityName: string, instanceId: string) => [DatabaseInMemory, oldInstance: InstanceContent | undefined];
@@ -0,0 +1,86 @@
1
+ import child_process from "node:child_process";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import { basename, extname, join } from "node:path";
4
+ import { platform } from "node:process";
5
+ import { promisify } from "node:util";
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";
8
+ export const emptyDatabaseInMemory = emptyD;
9
+ export const getInstanceFromDatabaseInMemory = (db, instanceId) => mapFirstD(db, (instances, entityName) => {
10
+ const instance = getD(instances, instanceId);
11
+ if (instance) {
12
+ return [entityName, instance];
13
+ }
14
+ return undefined;
15
+ });
16
+ export const getInstanceOfEntityFromDatabaseInMemory = (db, entityName, instanceId) => getMapD(db, entityName, instances => getD(instances, instanceId));
17
+ export const getInstancesOfEntityFromDatabaseInMemory = (db, entityName) => getMapD(db, entityName, toValuesD) ?? [];
18
+ export const getGroupedInstancesFromDatabaseInMemory = (db) => toEntriesD(db).map(([entityName, instances]) => [entityName, Object.values(instances[0])]);
19
+ export const forEachInstanceOfEntityInDatabaseInMemory = (db, entityName, fn) => {
20
+ for (const instance of Object.values(getMapD(db, entityName, toValuesD) ?? {})) {
21
+ fn(instance);
22
+ }
23
+ };
24
+ export const asyncForEachInstanceOfEntityInDatabaseInMemory = async (db, entityName, fn) => {
25
+ for (const instance of Object.values(getMapD(db, entityName, toValuesD) ?? {})) {
26
+ await fn(instance);
27
+ }
28
+ };
29
+ export const forEachInstanceInDatabaseInMemory = (db, fn) => {
30
+ forEachD(db, (instances, entityName) => {
31
+ forEachD(instances, instance => {
32
+ fn(entityName, instance);
33
+ });
34
+ });
35
+ };
36
+ export const asyncForEachInstanceInDatabaseInMemory = async (db, fn) => forEachAsyncD(db, (instances, entityName) => forEachAsyncD(instances, instance => fn(entityName, instance)));
37
+ export const countInstancesOfEntityInDatabaseInMemory = (db, entityName) => getMapD(db, entityName, sizeD) ?? 0;
38
+ const exec = promisify(child_process.exec);
39
+ const ulimit = platform === "win32" ? 2048 : Number.parseInt((await exec("ulimit -n")).stdout);
40
+ export const createDatabaseInMemory = async (dataRoot, entities) => fromEntriesD(await mapAsync(entities, async (entity) => {
41
+ const entityDir = join(dataRoot, entity.name);
42
+ const instanceFileNames = await readdir(entityDir);
43
+ const instances = await mapAsync(instanceFileNames, async (instanceFileName) => {
44
+ const id = basename(instanceFileName, extname(instanceFileName));
45
+ return [
46
+ id,
47
+ {
48
+ id,
49
+ content: JSON.parse(await readFile(join(entityDir, instanceFileName), "utf-8")),
50
+ },
51
+ ];
52
+ }, ulimit);
53
+ const instancesById = fromEntriesD(instances);
54
+ return [entity.name, instancesById];
55
+ }, 1));
56
+ const setInstanceInMemory = (instances, instance) => {
57
+ const oldInstance = getD(instances, instance.id);
58
+ return [setD(instances, instance.id, instance), oldInstance?.content];
59
+ };
60
+ export const setInstanceInDatabaseInMemory = (db, entityName, instance) => {
61
+ const [entityInstances, oldInstance] = setInstanceInMemory(getD(db, entityName) ?? emptyD, instance);
62
+ return [setD(db, entityName, entityInstances), oldInstance];
63
+ };
64
+ const deleteInstanceInMemory = (instances, instanceId) => {
65
+ if (!hasD(instances, instanceId)) {
66
+ return [instances, undefined];
67
+ }
68
+ const oldInstance = getD(instances, instanceId);
69
+ const remainingInstancesById = removeD(instances, instanceId);
70
+ return [remainingInstancesById, oldInstance?.content];
71
+ };
72
+ export const deleteInstanceInDatabaseInMemory = (db, entityName, instanceId) => {
73
+ const entityInstances = getD(db, entityName);
74
+ if (entityInstances === undefined) {
75
+ return [db, undefined];
76
+ }
77
+ const [remainingInstances, oldInstance] = deleteInstanceInMemory(entityInstances, instanceId);
78
+ if (entityInstances === remainingInstances) {
79
+ return [db, undefined];
80
+ }
81
+ if (remainingInstances[1].length === 0) {
82
+ const remainingEntities = removeD(db, entityName);
83
+ return [remainingEntities, oldInstance];
84
+ }
85
+ return [setD(db, entityName, remainingInstances), oldInstance];
86
+ };
@@ -0,0 +1,2 @@
1
+ import type { TransactionStep } from "./databaseTransactions.ts";
2
+ export declare const applyStepsToDisk: (root: string, steps: TransactionStep[]) => Promise<import("../../shared/utils/result.ts").Ok<void> | import("../../shared/utils/result.ts").Error<Error>>;
@@ -0,0 +1,62 @@
1
+ import { error, isError, ok } from "../../shared/utils/result.js";
2
+ import * as DatabaseFilesystem from "./files.js";
3
+ const setInstanceOnDisk = async (root, entity, instanceId, instanceContent) => {
4
+ try {
5
+ await DatabaseFilesystem.writeInstance(root, entity, instanceId, instanceContent);
6
+ return ok();
7
+ }
8
+ catch (e) {
9
+ return error(e);
10
+ }
11
+ };
12
+ const deleteInstanceOnDisk = async (root, entityName, instanceId) => {
13
+ try {
14
+ await DatabaseFilesystem.deleteInstance(root, entityName, instanceId);
15
+ return ok();
16
+ }
17
+ catch (e) {
18
+ return error(e);
19
+ }
20
+ };
21
+ const rollbackChanges = async (root, steps) => {
22
+ for (const step of steps) {
23
+ const res = await runReverseStepAction(root, step);
24
+ if (isError(res)) {
25
+ return res;
26
+ }
27
+ }
28
+ return ok();
29
+ };
30
+ const runStepAction = (root, step) => {
31
+ switch (step.kind) {
32
+ case "set":
33
+ return setInstanceOnDisk(root, step.entity, step.instanceId, step.instance);
34
+ case "delete":
35
+ return deleteInstanceOnDisk(root, step.entity.name, step.instanceId);
36
+ }
37
+ };
38
+ const runReverseStepAction = (root, step) => {
39
+ switch (step.kind) {
40
+ case "set":
41
+ if (step.oldInstance === undefined) {
42
+ return deleteInstanceOnDisk(root, step.entity.name, step.instanceId);
43
+ }
44
+ else {
45
+ return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance);
46
+ }
47
+ case "delete":
48
+ return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance);
49
+ }
50
+ };
51
+ export const applyStepsToDisk = async (root, steps) => {
52
+ for (let i = 0; i < steps.length; i++) {
53
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
54
+ const step = steps[i];
55
+ const res = await runStepAction(root, step);
56
+ if (isError(res)) {
57
+ await rollbackChanges(root, steps.slice(0, i));
58
+ return res;
59
+ }
60
+ }
61
+ return ok();
62
+ };