tsondb 0.12.3 → 0.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/node/index.js +17 -20
- package/dist/src/node/schema/Node.d.ts +2 -2
- package/dist/src/node/schema/Node.js +3 -5
- package/dist/src/node/schema/declarations/EntityDecl.d.ts +4 -4
- package/dist/src/node/schema/types/primitives/FloatType.d.ts +5 -2
- package/dist/src/node/schema/types/primitives/FloatType.js +10 -4
- package/dist/src/node/schema/types/references/IncludeIdentifierType.js +0 -3
- package/dist/src/node/server/api/declarations.js +27 -10
- package/dist/src/node/server/api/git.js +5 -4
- package/dist/src/node/server/api/index.js +2 -0
- package/dist/src/node/server/api/instances.js +2 -3
- package/dist/src/node/server/api/search.d.ts +1 -0
- package/dist/src/node/server/api/search.js +50 -0
- package/dist/src/node/server/index.d.ts +4 -3
- package/dist/src/node/server/index.js +2 -2
- package/dist/src/node/server/init.d.ts +2 -2
- package/dist/src/node/server/init.js +14 -11
- package/dist/src/node/server/utils/childInstances.d.ts +0 -3
- package/dist/src/node/server/utils/childInstances.js +2 -43
- package/dist/src/node/server/utils/instanceOperations.d.ts +3 -4
- package/dist/src/node/server/utils/instanceOperations.js +44 -84
- package/dist/src/node/server/utils/query.d.ts +2 -0
- package/dist/src/node/server/utils/query.js +7 -0
- package/dist/src/node/utils/childInstances.d.ts +11 -10
- package/dist/src/node/utils/childInstances.js +66 -130
- package/dist/src/node/utils/databaseInMemory.d.ts +18 -0
- package/dist/src/node/utils/databaseInMemory.js +86 -0
- package/dist/src/node/utils/databaseOnDisk.d.ts +2 -0
- package/dist/src/node/utils/databaseOnDisk.js +62 -0
- package/dist/src/node/utils/databaseTransactions.d.ts +46 -0
- package/dist/src/node/utils/databaseTransactions.js +105 -0
- package/dist/src/node/utils/displayName.d.ts +3 -3
- package/dist/src/node/utils/error.d.ts +4 -0
- package/dist/src/node/utils/error.js +8 -0
- package/dist/src/node/utils/files.d.ts +4 -2
- package/dist/src/node/utils/files.js +2 -1
- package/dist/src/node/utils/git.d.ts +3 -1
- package/dist/src/node/utils/git.js +8 -2
- package/dist/src/node/utils/instanceTransactionSteps.d.ts +9 -0
- package/dist/src/node/utils/instanceTransactionSteps.js +40 -0
- package/dist/src/node/utils/references.d.ts +4 -3
- package/dist/src/node/utils/references.js +3 -2
- package/dist/src/shared/api.d.ts +4 -0
- package/dist/src/shared/schema/declarations/EntityDecl.d.ts +2 -2
- package/dist/src/shared/schema/declarations/EnumDecl.d.ts +8 -0
- package/dist/src/shared/schema/declarations/EnumDecl.js +5 -0
- package/dist/src/shared/schema/types/FloatType.d.ts +6 -5
- package/dist/src/shared/schema/types/FloatType.js +1 -0
- package/dist/src/shared/utils/dictionary.d.ts +17 -0
- package/dist/src/shared/utils/dictionary.js +76 -0
- package/dist/src/shared/utils/instances.d.ts +2 -2
- package/dist/src/shared/utils/result.d.ts +4 -0
- package/dist/src/shared/utils/result.js +4 -0
- package/dist/src/web/api/search.d.ts +2 -0
- package/dist/src/web/api/search.js +7 -0
- package/dist/src/web/components/InstanceRouteSkeleton.d.ts +9 -8
- package/dist/src/web/components/Layout.js +1 -1
- package/dist/src/web/components/git/GitStatusIndicator.js +1 -1
- package/dist/src/web/components/typeInputs/ChildEntitiesTypeInput.js +1 -1
- package/dist/src/web/components/typeInputs/FloatTypeInput.d.ts +1 -1
- package/dist/src/web/components/typeInputs/FloatTypeInput.js +2 -1
- package/dist/src/web/hooks/useGitClient.js +0 -1
- package/dist/src/web/hooks/useMappedAPIResource.js +2 -4
- package/dist/src/web/index.js +2 -1
- package/dist/src/web/routes/Search.d.ts +2 -0
- package/dist/src/web/routes/Search.js +56 -0
- package/dist/src/web/utils/typeSkeleton.d.ts +2 -2
- package/dist/src/web/utils/typeSkeleton.js +1 -1
- package/package.json +3 -3
- package/public/css/styles.css +10 -0
- package/dist/src/node/utils/instanceOperations.d.ts +0 -14
- package/dist/src/node/utils/instanceOperations.js +0 -88
- package/dist/src/node/utils/instances.d.ts +0 -6
- package/dist/src/node/utils/instances.js +0 -29
|
@@ -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
|
|
6
|
-
export declare const
|
|
7
|
-
export declare const
|
|
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>>;
|
|
@@ -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 {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
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(
|
|
9
|
+
return error(new HTTPError(400, "Entity not found"));
|
|
36
10
|
}
|
|
37
|
-
const
|
|
38
|
-
if (isError(
|
|
39
|
-
return
|
|
11
|
+
const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.entitiesByName, undefined, undefined, locals.localeEntity, instance.entityName, undefined, instance, idQueryParam, res));
|
|
12
|
+
if (isError(databaseTransactionResult)) {
|
|
13
|
+
return databaseTransactionResult;
|
|
40
14
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
21
|
+
const instanceContainer = getInstanceOfEntityFromDatabaseInMemory(locals.databaseInMemory, instance.entityName, instance.id);
|
|
56
22
|
if (instanceContainer === undefined) {
|
|
57
|
-
return error(
|
|
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(
|
|
62
|
-
}
|
|
63
|
-
const oldChildInstances = getChildInstances(locals.
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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, 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
|
|
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(
|
|
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(
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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, 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,
|
|
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
|
|
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:
|
|
8
|
+
content: InstanceContent;
|
|
9
9
|
gitStatus?: GitFileStatus;
|
|
10
10
|
}
|
|
11
11
|
export interface EntityTaggedInstanceContainer {
|
|
12
12
|
entityName: string;
|
|
13
13
|
id: string;
|
|
14
|
-
content:
|
|
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:
|
|
27
|
+
content: InstanceContent;
|
|
28
28
|
childInstances: C[];
|
|
29
29
|
}
|
|
30
|
-
export declare const getChildInstancesFromEntity: (
|
|
31
|
-
export declare const getChildInstances: (
|
|
32
|
-
export declare const
|
|
33
|
-
|
|
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>, parentEntityName: string | undefined, 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,11 @@
|
|
|
1
|
+
import { createEnumCaseValue } from "../../shared/schema/declarations/EnumDecl.js";
|
|
1
2
|
import { hasKey } from "../../shared/utils/object.js";
|
|
2
|
-
import { error, isError, ok } from "../../shared/utils/result.js";
|
|
3
|
-
import { reduceNodes } from "../schema/index.js";
|
|
3
|
+
import { error, isError, map, ok } from "../../shared/utils/result.js";
|
|
4
|
+
import { isEntityDeclWithParentReference, isEnumDecl, isIncludeIdentifierType, reduceNodes, } from "../schema/index.js";
|
|
4
5
|
import { isChildEntitiesType } from "../schema/types/references/ChildEntitiesType.js";
|
|
5
|
-
import {
|
|
6
|
-
import {} from "./
|
|
6
|
+
import { getInstancesOfEntityFromDatabaseInMemory, } from "./databaseInMemory.js";
|
|
7
|
+
import { HTTPError } from "./error.js";
|
|
8
|
+
import { createInstance, deleteInstance, updateInstance } from "./instanceTransactionSteps.js";
|
|
7
9
|
const isParentReferenceReferencingParent = (value, parentEntityName, parentId) => {
|
|
8
10
|
if (typeof value === "object" && value !== null && hasKey(value, "kind")) {
|
|
9
11
|
return (value.kind === parentEntityName &&
|
|
@@ -17,149 +19,83 @@ const isParentReferenceReferencingParent = (value, parentEntityName, parentId) =
|
|
|
17
19
|
return false;
|
|
18
20
|
}
|
|
19
21
|
};
|
|
20
|
-
export const getChildInstancesFromEntity = (
|
|
21
|
-
instanceContainer.content
|
|
22
|
-
|
|
23
|
-
isParentReferenceReferencingParent(instanceContainer.content[childEntity.parentReferenceKey], parentEntity.name, parentId)) ?? [];
|
|
24
|
-
export const getChildInstances = (instancesByEntityName, parentEntity, parentId, recursive = true) => {
|
|
22
|
+
export const getChildInstancesFromEntity = (databaseInMemory, parentEntity, parentId, childEntity) => getInstancesOfEntityFromDatabaseInMemory(databaseInMemory, childEntity.name).filter(instanceContainer => hasKey(instanceContainer.content, childEntity.parentReferenceKey) &&
|
|
23
|
+
isParentReferenceReferencingParent(instanceContainer.content[childEntity.parentReferenceKey], parentEntity.name, parentId));
|
|
24
|
+
export const getChildInstances = (databaseInMemory, parentEntity, parentId, recursive = true) => {
|
|
25
25
|
const childEntities = reduceNodes((_parentNodes, node, collectedResults) => isChildEntitiesType(node) ? [...collectedResults, node.entity] : collectedResults, [parentEntity], { followIncludes: true });
|
|
26
|
-
return childEntities.flatMap(childEntity => getChildInstancesFromEntity(
|
|
26
|
+
return childEntities.flatMap(childEntity => getChildInstancesFromEntity(databaseInMemory, parentEntity, parentId, childEntity).map(container => ({
|
|
27
27
|
...container,
|
|
28
28
|
entityName: childEntity.name,
|
|
29
29
|
childInstances: recursive
|
|
30
|
-
? getChildInstances(
|
|
30
|
+
? getChildInstances(databaseInMemory, childEntity, container.id)
|
|
31
31
|
: [],
|
|
32
32
|
})));
|
|
33
33
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
}
|
|
34
|
+
const prepareNewChildInstanceContent = (entity, parentEntityName, parentId, content) => {
|
|
35
|
+
if (isEntityDeclWithParentReference(entity)) {
|
|
36
|
+
if (parentEntityName === undefined || parentId === undefined) {
|
|
37
|
+
return error(new HTTPError(400, `Cannot create instance of child entity "${entity.name}" without parent reference`));
|
|
69
38
|
}
|
|
39
|
+
const parentReferenceType = entity.type.value.properties[entity.parentReferenceKey]?.type;
|
|
40
|
+
return ok({
|
|
41
|
+
...content,
|
|
42
|
+
[entity.parentReferenceKey]: parentReferenceType &&
|
|
43
|
+
isIncludeIdentifierType(parentReferenceType) &&
|
|
44
|
+
isEnumDecl(parentReferenceType.reference)
|
|
45
|
+
? createEnumCaseValue(parentEntityName, parentId)
|
|
46
|
+
: parentId,
|
|
47
|
+
});
|
|
70
48
|
}
|
|
71
49
|
else {
|
|
72
|
-
|
|
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
|
-
}
|
|
50
|
+
return ok(content);
|
|
93
51
|
}
|
|
94
|
-
return ok();
|
|
95
52
|
};
|
|
96
|
-
export const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return error([400, `Unknown entity "${parentEntityName}"`]);
|
|
53
|
+
export const saveInstanceTree = (entitiesByName, parentEntityName, parentId, localeEntity, entityName, oldInstance, newInstance, customId, res) => {
|
|
54
|
+
if (isError(res)) {
|
|
55
|
+
return res;
|
|
100
56
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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;
|
|
57
|
+
const entity = entitiesByName[entityName];
|
|
58
|
+
if (entity === undefined) {
|
|
59
|
+
return error(new HTTPError(400, `Unknown entity "${entityName}"`));
|
|
60
|
+
}
|
|
61
|
+
if (newInstance === undefined) {
|
|
62
|
+
if (oldInstance === undefined) {
|
|
63
|
+
// no-op
|
|
64
|
+
return map(res, data => ({ ...data, instanceContainer: { id: "", content: {} } }));
|
|
137
65
|
}
|
|
66
|
+
// delete all child instances recursively
|
|
67
|
+
const deletedRes = deleteInstance(res, entity, oldInstance.id);
|
|
68
|
+
return map(oldInstance.childInstances.reduce((resAcc, oldChildInstance) => saveInstanceTree(entitiesByName, oldInstance.entityName, oldInstance.id, localeEntity, oldChildInstance.entityName, oldChildInstance, undefined, undefined, resAcc), deletedRes), data => ({
|
|
69
|
+
...data,
|
|
70
|
+
instanceContainer: { id: oldInstance.id, content: oldInstance.content },
|
|
71
|
+
}));
|
|
138
72
|
}
|
|
139
73
|
else {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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;
|
|
74
|
+
const preparedContent = newInstance.id === undefined
|
|
75
|
+
? prepareNewChildInstanceContent(entity, parentEntityName, parentId, newInstance.content)
|
|
76
|
+
: ok(newInstance.content);
|
|
77
|
+
if (isError(preparedContent)) {
|
|
78
|
+
return preparedContent;
|
|
152
79
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (isError(
|
|
161
|
-
return
|
|
80
|
+
const setRes = newInstance.id === undefined
|
|
81
|
+
? createInstance(res, localeEntity, entity, preparedContent.value, customId)
|
|
82
|
+
: map(updateInstance(res, entity, newInstance.id, preparedContent.value), data => ({
|
|
83
|
+
...data,
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
85
|
+
instanceId: newInstance.id,
|
|
86
|
+
}));
|
|
87
|
+
if (isError(setRes)) {
|
|
88
|
+
return setRes;
|
|
162
89
|
}
|
|
90
|
+
const instanceId = setRes.value.instanceId;
|
|
91
|
+
const setResWithoutInfo = ok({ ...setRes.value, additionalInformation: undefined });
|
|
92
|
+
return map(newInstance.childInstances
|
|
93
|
+
.filter(newChildInstance => newChildInstance.id === undefined)
|
|
94
|
+
.reduce((resAcc, newChildInstance) => saveInstanceTree(entitiesByName, newInstance.entityName, instanceId, localeEntity, newChildInstance.entityName, undefined, newChildInstance, undefined, resAcc), oldInstance
|
|
95
|
+
? oldInstance.childInstances.reduce((resAcc, oldChildInstance) => saveInstanceTree(entitiesByName, oldInstance.entityName, instanceId, localeEntity, oldChildInstance.entityName, oldChildInstance, newInstance.childInstances.find(ci => ci.id === oldChildInstance.id), undefined, resAcc), setResWithoutInfo)
|
|
96
|
+
: setResWithoutInfo), data => ({
|
|
97
|
+
...data,
|
|
98
|
+
instanceContainer: { id: instanceId, content: preparedContent.value },
|
|
99
|
+
}));
|
|
163
100
|
}
|
|
164
|
-
return ok();
|
|
165
101
|
};
|
|
@@ -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,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
|
+
};
|