tsondb 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ModelContainer.js +9 -7
- package/lib/client/api.d.ts +14 -1
- package/lib/client/api.js +119 -0
- package/lib/client/components/Git.d.ts +2 -0
- package/lib/client/components/Git.js +116 -0
- package/lib/client/components/Layout.js +2 -1
- package/lib/client/components/typeInputs/ArrayTypeInput.js +2 -1
- package/lib/client/components/typeInputs/EnumTypeInput.d.ts +13 -0
- package/lib/client/components/typeInputs/{utils/EnumDeclField.js → EnumTypeInput.js} +10 -10
- package/lib/client/components/typeInputs/IncludeIdentifierTypeInput.js +1 -10
- package/lib/client/components/typeInputs/TypeInput.js +3 -0
- package/lib/client/components/typeInputs/utils/Markdown.js +1 -32
- package/lib/client/hooks/useAPIResource.d.ts +1 -0
- package/lib/client/hooks/useAPIResource.js +2 -0
- package/lib/client/hooks/useMappedAPIResource.d.ts +1 -0
- package/lib/client/hooks/useMappedAPIResource.js +19 -0
- package/lib/client/routes/Entity.js +18 -24
- package/lib/client/routes/Home.js +3 -12
- package/lib/client/utils/typeSkeleton.js +10 -16
- package/lib/renderers/jsonschema/index.d.ts +1 -1
- package/lib/renderers/jsonschema/index.js +30 -7
- package/lib/renderers/jsonschema/render.d.ts +5 -1
- package/lib/renderers/jsonschema/render.js +35 -16
- package/lib/renderers/ts/index.d.ts +1 -1
- package/lib/renderers/ts/index.js +37 -6
- package/lib/renderers/ts/render.d.ts +2 -0
- package/lib/renderers/ts/render.js +55 -32
- package/lib/schema/Node.d.ts +1 -0
- package/lib/schema/Node.js +6 -1
- package/lib/schema/declarations/Declaration.d.ts +3 -1
- package/lib/schema/declarations/Declaration.js +4 -0
- package/lib/schema/declarations/EntityDecl.d.ts +3 -0
- package/lib/schema/declarations/EnumDecl.d.ts +4 -21
- package/lib/schema/declarations/EnumDecl.js +16 -79
- package/lib/schema/index.d.ts +1 -0
- package/lib/schema/index.js +1 -0
- package/lib/schema/types/Type.d.ts +8 -2
- package/lib/schema/types/Type.js +57 -11
- package/lib/schema/types/generic/ArrayType.d.ts +2 -1
- package/lib/schema/types/generic/ArrayType.js +2 -1
- package/lib/schema/types/generic/EnumType.d.ts +38 -0
- package/lib/schema/types/generic/EnumType.js +96 -0
- package/lib/schema/types/generic/ObjectType.d.ts +2 -1
- package/lib/schema/types/generic/ObjectType.js +4 -0
- package/lib/schema/types/primitives/BooleanType.d.ts +2 -1
- package/lib/schema/types/primitives/BooleanType.js +1 -0
- package/lib/schema/types/primitives/DateType.d.ts +2 -1
- package/lib/schema/types/primitives/DateType.js +1 -0
- package/lib/schema/types/primitives/FloatType.d.ts +2 -1
- package/lib/schema/types/primitives/FloatType.js +1 -0
- package/lib/schema/types/primitives/IntegerType.d.ts +2 -1
- package/lib/schema/types/primitives/IntegerType.js +1 -0
- package/lib/schema/types/primitives/StringType.d.ts +2 -1
- package/lib/schema/types/primitives/StringType.js +1 -0
- package/lib/schema/types/references/GenericArgumentIdentifierType.d.ts +2 -1
- package/lib/schema/types/references/GenericArgumentIdentifierType.js +1 -0
- package/lib/schema/types/references/IncludeIdentifierType.d.ts +5 -3
- package/lib/schema/types/references/IncludeIdentifierType.js +15 -2
- package/lib/schema/types/references/NestedEntityMapType.d.ts +2 -1
- package/lib/schema/types/references/NestedEntityMapType.js +5 -1
- package/lib/schema/types/references/ReferenceIdentifierType.d.ts +2 -1
- package/lib/schema/types/references/ReferenceIdentifierType.js +2 -1
- package/lib/server/api/declarations.d.ts +1 -0
- package/lib/server/api/declarations.js +154 -0
- package/lib/server/api/git.d.ts +1 -0
- package/lib/server/api/git.js +174 -0
- package/lib/server/api/index.d.ts +1 -0
- package/lib/server/api/index.js +8 -0
- package/lib/server/api/instanceOperations.d.ts +6 -0
- package/lib/server/api/instanceOperations.js +82 -0
- package/lib/server/api/instances.d.ts +1 -0
- package/lib/server/api/instances.js +23 -0
- package/lib/server/index.d.ts +22 -1
- package/lib/server/index.js +11 -165
- package/lib/server/init.d.ts +5 -0
- package/lib/server/init.js +56 -0
- package/lib/shared/api.d.ts +12 -1
- package/lib/shared/utils/array.d.ts +19 -0
- package/lib/shared/utils/array.js +27 -0
- package/lib/shared/utils/git.d.ts +12 -0
- package/lib/shared/utils/git.js +98 -0
- package/lib/shared/utils/instances.d.ts +10 -0
- package/lib/shared/utils/instances.js +8 -1
- package/lib/shared/utils/markdown.d.ts +14 -0
- package/lib/shared/utils/markdown.js +42 -0
- package/lib/shared/utils/object.d.ts +1 -0
- package/lib/shared/utils/object.js +4 -0
- package/lib/shared/utils/string.d.ts +1 -0
- package/lib/shared/utils/string.js +9 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/git.d.ts +3 -0
- package/lib/utils/git.js +12 -0
- package/lib/utils/instances.d.ts +3 -2
- package/lib/utils/instances.js +9 -2
- package/lib/utils/path.d.ts +1 -0
- package/lib/utils/path.js +2 -0
- package/lib/utils/references.d.ts +7 -0
- package/lib/utils/references.js +40 -0
- package/lib/utils/render.d.ts +6 -1
- package/lib/utils/render.js +27 -1
- package/package.json +8 -1
- package/public/css/styles.css +200 -1
- package/lib/client/components/typeInputs/utils/EnumDeclField.d.ts +0 -13
- package/lib/server/instanceOperations.d.ts +0 -7
- package/lib/server/instanceOperations.js +0 -67
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getNestedDeclarations, getReferencesForDecl, resolveTypeArgumentsInDecl, validateDecl, } from "../../declarations/Declaration.js";
|
|
2
2
|
import { NodeKind } from "../../Node.js";
|
|
3
|
-
import {
|
|
3
|
+
import { formatEnumType } from "../generic/EnumType.js";
|
|
4
|
+
import { formatValue, removeParentKey, resolveTypeArgumentsInType, serializeType, } from "../Type.js";
|
|
4
5
|
export const GenIncludeIdentifierType = (reference, args) => ({
|
|
5
6
|
kind: NodeKind.IncludeIdentifierType,
|
|
6
7
|
reference,
|
|
@@ -18,10 +19,22 @@ export const getNestedDeclarationsInIncludeIdentifierType = (addedDecls, type) =
|
|
|
18
19
|
? addedDecls
|
|
19
20
|
: getNestedDeclarations([type.reference, ...addedDecls], type.reference);
|
|
20
21
|
export const validateIncludeIdentifierType = (helpers, type, value) => validateDecl(helpers, type.reference, type.args, value);
|
|
21
|
-
export const resolveTypeArgumentsInIncludeIdentifierType = (args, type) =>
|
|
22
|
+
export const resolveTypeArgumentsInIncludeIdentifierType = (args, type) => type.args.length === 0
|
|
23
|
+
? type
|
|
24
|
+
: resolveTypeArgumentsInDecl(type.reference, type.args.map(arg => resolveTypeArgumentsInType(args, arg))).type.value;
|
|
22
25
|
export const serializeIncludeIdentifierType = type => ({
|
|
23
26
|
...removeParentKey(type),
|
|
24
27
|
reference: type.reference.name,
|
|
25
28
|
args: type.args.map(arg => serializeType(arg)),
|
|
26
29
|
});
|
|
27
30
|
export const getReferencesForIncludeIdentifierType = (type, value) => getReferencesForDecl(resolveTypeArgumentsInDecl(type.reference, type.args), value);
|
|
31
|
+
export const formatIncludeIdentifierValue = (type, value) => {
|
|
32
|
+
switch (type.reference.kind) {
|
|
33
|
+
case NodeKind.TypeAliasDecl:
|
|
34
|
+
return formatValue(type.reference.type.value, value);
|
|
35
|
+
case NodeKind.EnumDecl:
|
|
36
|
+
return formatEnumType(type.reference.type.value, value);
|
|
37
|
+
default:
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -4,7 +4,7 @@ import { EntityDecl } from "../../declarations/EntityDecl.js";
|
|
|
4
4
|
import { GetReferences, Node, NodeKind, Serializer } from "../../Node.js";
|
|
5
5
|
import { Validator } from "../../validation/type.js";
|
|
6
6
|
import { MemberDecl, ObjectType, SerializedMemberDecl, SerializedObjectType } from "../generic/ObjectType.js";
|
|
7
|
-
import { BaseType, SerializedBaseType, SerializedType, Type } from "../Type.js";
|
|
7
|
+
import { BaseType, SerializedBaseType, SerializedType, StructureFormatter, Type } from "../Type.js";
|
|
8
8
|
type TConstraint = Record<string, MemberDecl<Type, boolean>>;
|
|
9
9
|
export interface NestedEntityMapType<Name extends string = string, T extends TConstraint = TConstraint> extends BaseType {
|
|
10
10
|
kind: NodeKind["NestedEntityMapType"];
|
|
@@ -34,3 +34,4 @@ export declare const validateNestedEntityMapType: Validator<NestedEntityMapType>
|
|
|
34
34
|
export declare const resolveTypeArgumentsInNestedEntityMapType: (args: Record<string, Type>, type: NestedEntityMapType) => NestedEntityMapType;
|
|
35
35
|
export declare const serializeNestedEntityMapType: Serializer<NestedEntityMapType, SerializedNestedEntityMapType>;
|
|
36
36
|
export declare const getReferencesForNestedEntityMapType: GetReferences<NestedEntityMapType>;
|
|
37
|
+
export declare const formatNestedEntityMapValue: StructureFormatter<NestedEntityMapType>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { sortObjectKeysAlphabetically } from "../../../shared/utils/object.js";
|
|
1
2
|
import { parallelizeErrors } from "../../../shared/utils/validation.js";
|
|
2
3
|
import { wrapErrorsIfAny } from "../../../utils/error.js";
|
|
3
4
|
import { Lazy } from "../../../utils/lazy.js";
|
|
4
5
|
import { NodeKind } from "../../Node.js";
|
|
5
6
|
import { getNestedDeclarationsInObjectType, getReferencesForObjectType, resolveTypeArgumentsInObjectType, serializeObjectType, validateObjectType, } from "../generic/ObjectType.js";
|
|
6
|
-
import { removeParentKey } from "../Type.js";
|
|
7
|
+
import { removeParentKey, } from "../Type.js";
|
|
7
8
|
export const NestedEntityMapType = (options) => {
|
|
8
9
|
const nestedEntityMapType = {
|
|
9
10
|
...options,
|
|
@@ -54,3 +55,6 @@ export const getReferencesForNestedEntityMapType = (type, value) => typeof value
|
|
|
54
55
|
.flatMap(item => getReferencesForObjectType(type.type.value, item))
|
|
55
56
|
.concat(Object.keys(value))
|
|
56
57
|
: [];
|
|
58
|
+
export const formatNestedEntityMapValue = (_type, value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
59
|
+
? sortObjectKeysAlphabetically(value)
|
|
60
|
+
: value;
|
|
@@ -2,7 +2,7 @@ import { GetNestedDeclarations } from "../../declarations/Declaration.js";
|
|
|
2
2
|
import { EntityDecl } from "../../declarations/EntityDecl.js";
|
|
3
3
|
import { GetReferences, Node, NodeKind, Serializer } from "../../Node.js";
|
|
4
4
|
import { Validator } from "../../validation/type.js";
|
|
5
|
-
import { BaseType, SerializedBaseType, Type } from "../Type.js";
|
|
5
|
+
import { BaseType, SerializedBaseType, StructureFormatter, Type } from "../Type.js";
|
|
6
6
|
export interface ReferenceIdentifierType extends BaseType {
|
|
7
7
|
kind: NodeKind["ReferenceIdentifierType"];
|
|
8
8
|
entity: EntityDecl;
|
|
@@ -19,3 +19,4 @@ export declare const validateReferenceIdentifierType: Validator<ReferenceIdentif
|
|
|
19
19
|
export declare const resolveTypeArgumentsInReferenceIdentifierType: <Args extends Record<string, Type>>(_args: Args, type: ReferenceIdentifierType) => ReferenceIdentifierType;
|
|
20
20
|
export declare const serializeReferenceIdentifierType: Serializer<ReferenceIdentifierType, SerializedReferenceIdentifierType>;
|
|
21
21
|
export declare const getReferencesForReferenceIdentifierType: GetReferences<ReferenceIdentifierType>;
|
|
22
|
+
export declare const formatReferenceIdentifierValue: StructureFormatter<ReferenceIdentifierType>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getNestedDeclarations } from "../../declarations/Declaration.js";
|
|
2
2
|
import { createEntityIdentifierType } from "../../declarations/EntityDecl.js";
|
|
3
3
|
import { NodeKind } from "../../Node.js";
|
|
4
|
-
import { removeParentKey, validate } from "../Type.js";
|
|
4
|
+
import { removeParentKey, validate, } from "../Type.js";
|
|
5
5
|
export const ReferenceIdentifierType = (entity) => ({
|
|
6
6
|
kind: NodeKind.ReferenceIdentifierType,
|
|
7
7
|
entity,
|
|
@@ -21,3 +21,4 @@ export const serializeReferenceIdentifierType = type => ({
|
|
|
21
21
|
entity: type.entity.name,
|
|
22
22
|
});
|
|
23
23
|
export const getReferencesForReferenceIdentifierType = (_type, value) => (typeof value === "string" ? [value] : []);
|
|
24
|
+
export const formatReferenceIdentifierValue = (_type, value) => value;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const declarationsApi: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { serializeDecl } from "../../schema/declarations/Declaration.js";
|
|
4
|
+
import { isEntityDecl } from "../../schema/declarations/EntityDecl.js";
|
|
5
|
+
import { isEnumDecl } from "../../schema/declarations/EnumDecl.js";
|
|
6
|
+
import { isTypeAliasDecl } from "../../schema/declarations/TypeAliasDecl.js";
|
|
7
|
+
import { isOk } from "../../utils/result.js";
|
|
8
|
+
import { createInstance, deleteInstance, updateInstance } from "./instanceOperations.js";
|
|
9
|
+
const debug = Debug("tsondb:server:api:declarations");
|
|
10
|
+
export const declarationsApi = express.Router();
|
|
11
|
+
declarationsApi.use((req, _res, next) => {
|
|
12
|
+
debug(req.path);
|
|
13
|
+
next();
|
|
14
|
+
});
|
|
15
|
+
declarationsApi.get("/", (req, res) => {
|
|
16
|
+
let filteredEntities;
|
|
17
|
+
switch (req.query["kind"]) {
|
|
18
|
+
case "Entity":
|
|
19
|
+
filteredEntities = req.entities;
|
|
20
|
+
break;
|
|
21
|
+
case "TypeAlias":
|
|
22
|
+
filteredEntities = req.declarations.filter(isTypeAliasDecl);
|
|
23
|
+
break;
|
|
24
|
+
case "Enum":
|
|
25
|
+
filteredEntities = req.declarations.filter(isEnumDecl);
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
filteredEntities = req.declarations;
|
|
29
|
+
}
|
|
30
|
+
const body = {
|
|
31
|
+
declarations: filteredEntities.map(decl => ({
|
|
32
|
+
declaration: serializeDecl(decl),
|
|
33
|
+
instanceCount: req.instancesByEntityName[decl.name]?.length ?? 0,
|
|
34
|
+
})),
|
|
35
|
+
localeEntity: req.localeEntity?.name,
|
|
36
|
+
};
|
|
37
|
+
res.json(body);
|
|
38
|
+
});
|
|
39
|
+
declarationsApi.get("/:name", (req, res) => {
|
|
40
|
+
const decl = req.declarations.find(decl => decl.name === req.params.name);
|
|
41
|
+
if (decl === undefined) {
|
|
42
|
+
res.status(404).send(`Declaration "${req.params.name}" not found`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const body = {
|
|
46
|
+
declaration: serializeDecl(decl),
|
|
47
|
+
instanceCount: req.instancesByEntityName[decl.name]?.length ?? 0,
|
|
48
|
+
isLocaleEntity: decl === req.localeEntity,
|
|
49
|
+
};
|
|
50
|
+
res.json(body);
|
|
51
|
+
});
|
|
52
|
+
declarationsApi.get("/:name/instances", (req, res) => {
|
|
53
|
+
const decl = req.declarations.find(decl => decl.name === req.params.name);
|
|
54
|
+
if (decl === undefined) {
|
|
55
|
+
res.status(404).send(`Declaration "${req.params.name}" not found`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!isEntityDecl(decl)) {
|
|
59
|
+
res.status(400).send(`Declaration "${decl.name}" is not an entity`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const body = {
|
|
63
|
+
instances: req.instancesByEntityName[req.params.name] ?? [],
|
|
64
|
+
isLocaleEntity: decl === req.localeEntity,
|
|
65
|
+
};
|
|
66
|
+
res.json(body);
|
|
67
|
+
});
|
|
68
|
+
declarationsApi.post("/:name/instances", async (req, res) => {
|
|
69
|
+
const decl = req.declarations.find(decl => decl.name === req.params.name);
|
|
70
|
+
if (decl === undefined) {
|
|
71
|
+
res.status(404).send(`Declaration "${req.params.name}" not found`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!isEntityDecl(decl)) {
|
|
75
|
+
res.status(400).send(`Declaration "${decl.name}" is not an entity`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const result = await createInstance(req, req.params.name, req.body, req.query["id"]);
|
|
79
|
+
if (isOk(result)) {
|
|
80
|
+
const body = {
|
|
81
|
+
instance: result.value,
|
|
82
|
+
isLocaleEntity: decl === req.localeEntity,
|
|
83
|
+
};
|
|
84
|
+
res.json(body);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
res.status(result.error[0]).send(result.error[1]);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
declarationsApi.get("/:name/instances/:id", (req, res) => {
|
|
91
|
+
const decl = req.declarations.find(decl => decl.name === req.params.name);
|
|
92
|
+
if (decl === undefined) {
|
|
93
|
+
res.status(404).send(`Declaration "${req.params.name}" not found`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!isEntityDecl(decl)) {
|
|
97
|
+
res.status(400).send(`Declaration "${decl.name}" is not an entity`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const instance = req.instancesByEntityName[decl.name]?.find(instance => instance.id === req.params.id);
|
|
101
|
+
if (instance === undefined) {
|
|
102
|
+
res.status(404).send(`Instance "${req.params.id}" not found`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const body = {
|
|
106
|
+
instance: instance,
|
|
107
|
+
isLocaleEntity: decl === req.localeEntity,
|
|
108
|
+
};
|
|
109
|
+
res.json(body);
|
|
110
|
+
});
|
|
111
|
+
declarationsApi.put("/:name/instances/:id", async (req, res) => {
|
|
112
|
+
const decl = req.declarations.find(decl => decl.name === req.params.name);
|
|
113
|
+
if (decl === undefined) {
|
|
114
|
+
res.status(404).send(`Declaration "${req.params.name}" not found`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!isEntityDecl(decl)) {
|
|
118
|
+
res.status(400).send(`Declaration "${decl.name}" is not an entity`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const result = await updateInstance(req, req.params.name, req.params.id, req.body);
|
|
122
|
+
if (isOk(result)) {
|
|
123
|
+
const body = {
|
|
124
|
+
instance: result.value,
|
|
125
|
+
isLocaleEntity: decl === req.localeEntity,
|
|
126
|
+
};
|
|
127
|
+
res.json(body);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
res.status(result.error[0]).send(result.error[1]);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
declarationsApi.delete("/:name/instances/:id", async (req, res) => {
|
|
134
|
+
const decl = req.declarations.find(decl => decl.name === req.params.name);
|
|
135
|
+
if (decl === undefined) {
|
|
136
|
+
res.status(404).send(`Declaration "${req.params.name}" not found`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!isEntityDecl(decl)) {
|
|
140
|
+
res.status(400).send(`Declaration "${decl.name}" is not an entity`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const result = await deleteInstance(req, req.params.name, req.params.id);
|
|
144
|
+
if (isOk(result)) {
|
|
145
|
+
const body = {
|
|
146
|
+
instance: result.value,
|
|
147
|
+
isLocaleEntity: decl === req.localeEntity,
|
|
148
|
+
};
|
|
149
|
+
res.json(body);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
res.status(result.error[0]).send(result.error[1]);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const gitApi: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { serializeEntityDecl } from "../../schema/index.js";
|
|
5
|
+
import { hasFileChanges } from "../../shared/utils/git.js";
|
|
6
|
+
import { getInstanceContainerOverview } from "../../shared/utils/instances.js";
|
|
7
|
+
import { attachGitStatusToInstancesByEntityName } from "../../utils/instances.js";
|
|
8
|
+
import { reinit } from "../init.js";
|
|
9
|
+
const debug = Debug("tsondb:server:api:git");
|
|
10
|
+
export const gitApi = express.Router();
|
|
11
|
+
gitApi.use((req, res, next) => {
|
|
12
|
+
debug(req.path);
|
|
13
|
+
if (req.gitRoot === undefined) {
|
|
14
|
+
res.status(500).send("Git repository not found");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
next();
|
|
18
|
+
});
|
|
19
|
+
gitApi.get("/status", async (req, res) => {
|
|
20
|
+
const locales = (Array.isArray(req.query["locales"]) ? req.query["locales"] : [req.query["locales"]]).filter(locale => typeof locale === "string");
|
|
21
|
+
const status = await req.git.status();
|
|
22
|
+
attachGitStatusToInstancesByEntityName(req.instancesByEntityName, req.dataRoot, req.gitRoot, status);
|
|
23
|
+
const body = {
|
|
24
|
+
commitsAhead: status.ahead,
|
|
25
|
+
commitsBehind: status.behind,
|
|
26
|
+
instances: Object.fromEntries(Object.entries(req.instancesByEntityName).map(([entityName, instances]) => [
|
|
27
|
+
entityName,
|
|
28
|
+
instances
|
|
29
|
+
.filter(instance => hasFileChanges(instance.gitStatus))
|
|
30
|
+
.map(instance => getInstanceContainerOverview(serializeEntityDecl(req.entitiesByName[entityName]), instance, locales)),
|
|
31
|
+
])),
|
|
32
|
+
};
|
|
33
|
+
res.json(body);
|
|
34
|
+
});
|
|
35
|
+
gitApi.post("/stage", async (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
await req.git.add(req.dataRoot);
|
|
38
|
+
res.status(200).send("Added all database files to index");
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
debug(`${req.path}: ${error.message}`);
|
|
42
|
+
res.status(500).send("Adding all database files to index failed");
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
gitApi.post("/stage/:entityName", async (req, res) => {
|
|
46
|
+
try {
|
|
47
|
+
await req.git.add(join(req.dataRoot, req.params.entityName));
|
|
48
|
+
res.status(200).send(`Added all database files for entity ${req.params.entityName} to index`);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
debug(`${req.path}: ${error.message}`);
|
|
52
|
+
res
|
|
53
|
+
.status(500)
|
|
54
|
+
.send(`Adding all database files for entity ${req.params.entityName} to index failed`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
gitApi.post("/stage/:entityName/:instanceId", async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
await req.git.add(join(req.dataRoot, req.params.entityName, `${req.params.instanceId}.json`));
|
|
60
|
+
res
|
|
61
|
+
.status(200)
|
|
62
|
+
.send(`Added database file ${req.params.instanceId} for entity ${req.params.entityName} to index`);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
debug(`${req.path}: ${error.message}`);
|
|
66
|
+
res
|
|
67
|
+
.status(500)
|
|
68
|
+
.send(`Adding database file ${req.params.instanceId} for entity ${req.params.entityName} to index failed`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
gitApi.post("/unstage", async (req, res) => {
|
|
72
|
+
try {
|
|
73
|
+
await req.git.reset(["HEAD", "--", req.dataRoot]);
|
|
74
|
+
res.status(200).send("Removed all database files to index");
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
debug(`${req.path}: ${error.message}`);
|
|
78
|
+
res.status(500).send("Removing all database files to index failed");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
gitApi.post("/unstage/:entityName", async (req, res) => {
|
|
82
|
+
try {
|
|
83
|
+
await req.git.reset(["HEAD", "--", join(req.dataRoot, req.params.entityName)]);
|
|
84
|
+
res.status(200).send(`Removed all database files for entity ${req.params.entityName} to index`);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
debug(`${req.path}: ${error.message}`);
|
|
88
|
+
res
|
|
89
|
+
.status(500)
|
|
90
|
+
.send(`Removing all database files for entity ${req.params.entityName} to index failed`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
gitApi.post("/unstage/:entityName/:instanceId", async (req, res) => {
|
|
94
|
+
try {
|
|
95
|
+
await req.git.reset([
|
|
96
|
+
"HEAD",
|
|
97
|
+
"--",
|
|
98
|
+
join(req.dataRoot, req.params.entityName, `${req.params.instanceId}.json`),
|
|
99
|
+
]);
|
|
100
|
+
res
|
|
101
|
+
.status(200)
|
|
102
|
+
.send(`Removed database file ${req.params.instanceId} for entity ${req.params.entityName} to index`);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
debug(`${req.path}: ${error.message}`);
|
|
106
|
+
res
|
|
107
|
+
.status(500)
|
|
108
|
+
.send(`Removing database file ${req.params.instanceId} for entity ${req.params.entityName} to index failed`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
gitApi.post("/commit", async (req, res) => {
|
|
112
|
+
const message = req.body.message;
|
|
113
|
+
if (typeof message !== "string" || message.length === 0) {
|
|
114
|
+
res.status(400).send("Invalid commit message");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
await req.git.commit(message);
|
|
119
|
+
res.status(200).send("Commit successful");
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
res.status(500).send("Commit failed");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
gitApi.post("/push", async (req, res) => {
|
|
126
|
+
try {
|
|
127
|
+
await req.git.push();
|
|
128
|
+
res.status(200).send("Push successful");
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
res.status(500).send("Push failed");
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
gitApi.post("/pull", async (req, res) => {
|
|
135
|
+
try {
|
|
136
|
+
await req.git.pull();
|
|
137
|
+
res.status(200).send("Pull successful");
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
res.status(500).send("Pull failed");
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
gitApi.get("/branch", async (req, res) => {
|
|
144
|
+
const branchSummary = await req.git.branchLocal();
|
|
145
|
+
const body = {
|
|
146
|
+
allBranches: branchSummary.all,
|
|
147
|
+
currentBranch: branchSummary.current,
|
|
148
|
+
};
|
|
149
|
+
res.json(body);
|
|
150
|
+
});
|
|
151
|
+
gitApi.post("/branch", async (req, res) => {
|
|
152
|
+
const branchName = req.body.branchName;
|
|
153
|
+
if (typeof branchName !== "string" || branchName.length === 0) {
|
|
154
|
+
res.status(400).send("Invalid branch name");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await req.git.checkoutLocalBranch(branchName);
|
|
159
|
+
await reinit(req);
|
|
160
|
+
res.status(200).send(`Creation of branch "${branchName}" successful`);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
res.status(500).send(`Creation of branch "${branchName}" failed`);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
gitApi.post("/branch/:branchName", async (req, res) => {
|
|
167
|
+
try {
|
|
168
|
+
await req.git.checkout(req.params.branchName);
|
|
169
|
+
res.status(200).send(`Switch to branch "${req.params.branchName}" successful`);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
res.status(500).send(`Switch to branch "${req.params.branchName}" failed`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const api: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { declarationsApi } from "./declarations.js";
|
|
3
|
+
import { gitApi } from "./git.js";
|
|
4
|
+
import { instancesApi } from "./instances.js";
|
|
5
|
+
export const api = express.Router();
|
|
6
|
+
api.use("/declarations", declarationsApi);
|
|
7
|
+
api.use("/instances", instancesApi);
|
|
8
|
+
api.use("/git", gitApi);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { InstanceContainer } from "../../shared/utils/instances.js";
|
|
2
|
+
import { Result } from "../../utils/result.js";
|
|
3
|
+
import { TSONDBRequestLocals } from "../index.js";
|
|
4
|
+
export declare const createInstance: (locals: TSONDBRequestLocals, entityName: string, instance: unknown, idQueryParam: unknown) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
|
|
5
|
+
export declare const updateInstance: (locals: TSONDBRequestLocals, entityName: string, instanceId: string, instance: unknown) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
|
|
6
|
+
export declare const deleteInstance: (locals: TSONDBRequestLocals, entityName: string, instanceId: string) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
import { validateEntityDecl } from "../../schema/declarations/EntityDecl.js";
|
|
5
|
+
import { formatValue } from "../../schema/index.js";
|
|
6
|
+
import { createValidators } from "../../schema/Node.js";
|
|
7
|
+
import { removeAt } from "../../shared/utils/array.js";
|
|
8
|
+
import { getErrorMessageForDisplay } from "../../utils/error.js";
|
|
9
|
+
import { getGitFileStatusFromStatusResult } from "../../utils/git.js";
|
|
10
|
+
import { updateReferencesToInstances } from "../../utils/references.js";
|
|
11
|
+
import { error, ok } from "../../utils/result.js";
|
|
12
|
+
export const createInstance = async (locals, entityName, instance, idQueryParam) => {
|
|
13
|
+
const entity = locals.entitiesByName[entityName];
|
|
14
|
+
const validationErrors = validateEntityDecl(createValidators(locals.instancesByEntityName), entity, instance);
|
|
15
|
+
if (validationErrors.length > 0) {
|
|
16
|
+
return error([400, validationErrors.map(getErrorMessageForDisplay).join("\n\n")]);
|
|
17
|
+
}
|
|
18
|
+
if (locals.localeEntity === entity && typeof idQueryParam !== "string") {
|
|
19
|
+
return error([400, "Missing id for locale entity"]);
|
|
20
|
+
}
|
|
21
|
+
const id = locals.localeEntity === entity ? idQueryParam : uuidv4();
|
|
22
|
+
if (locals.localeEntity === entity &&
|
|
23
|
+
locals.instancesByEntityName[entity.name].some(instance => instance.id === id)) {
|
|
24
|
+
return error([400, `Duplicate id "${id}" for locale entity`]);
|
|
25
|
+
}
|
|
26
|
+
const fileName = `${id}.json`;
|
|
27
|
+
await writeFile(join(locals.dataRoot, entity.name, fileName), JSON.stringify(formatValue(entity.type.value, instance), undefined, 2), { encoding: "utf-8" });
|
|
28
|
+
const instanceContainer = {
|
|
29
|
+
fileName,
|
|
30
|
+
id,
|
|
31
|
+
content: instance,
|
|
32
|
+
gitStatus: locals.gitRoot === undefined
|
|
33
|
+
? undefined
|
|
34
|
+
: getGitFileStatusFromStatusResult(await locals.git.status(), locals.gitRoot, locals.dataRoot, entity.name, fileName),
|
|
35
|
+
};
|
|
36
|
+
locals.instancesByEntityName[entity.name] = [
|
|
37
|
+
...(locals.instancesByEntityName[entity.name] ?? []),
|
|
38
|
+
instanceContainer,
|
|
39
|
+
];
|
|
40
|
+
Object.assign(locals.referencesToInstances, updateReferencesToInstances(locals.entitiesByName, locals.referencesToInstances, entity.name, id, undefined, instance));
|
|
41
|
+
return ok(instanceContainer);
|
|
42
|
+
};
|
|
43
|
+
export const updateInstance = async (locals, entityName, instanceId, instance) => {
|
|
44
|
+
const instanceContainer = locals.instancesByEntityName[entityName]?.find(instance => instance.id === instanceId);
|
|
45
|
+
if (instanceContainer === undefined) {
|
|
46
|
+
return error([404, "Instance not found"]);
|
|
47
|
+
}
|
|
48
|
+
const entity = locals.entitiesByName[entityName];
|
|
49
|
+
const validationErrors = validateEntityDecl(createValidators(locals.instancesByEntityName), entity, instance);
|
|
50
|
+
if (validationErrors.length > 0) {
|
|
51
|
+
return error([400, validationErrors.map(getErrorMessageForDisplay).join("\n\n")]);
|
|
52
|
+
}
|
|
53
|
+
await writeFile(join(locals.dataRoot, entity.name, instanceContainer.fileName), JSON.stringify(formatValue(entity.type.value, instance), undefined, 2), { encoding: "utf-8" });
|
|
54
|
+
const oldInstance = instanceContainer.content;
|
|
55
|
+
instanceContainer.content = instance;
|
|
56
|
+
instanceContainer.gitStatus =
|
|
57
|
+
locals.gitRoot === undefined
|
|
58
|
+
? undefined
|
|
59
|
+
: getGitFileStatusFromStatusResult(await locals.git.status(), locals.gitRoot, locals.dataRoot, entity.name, instanceContainer.fileName);
|
|
60
|
+
Object.assign(locals.referencesToInstances, updateReferencesToInstances(locals.entitiesByName, locals.referencesToInstances, entity.name, instanceId, oldInstance, instance));
|
|
61
|
+
return ok(instanceContainer);
|
|
62
|
+
};
|
|
63
|
+
export const deleteInstance = async (locals, entityName, instanceId) => {
|
|
64
|
+
const instances = locals.instancesByEntityName[entityName] ?? [];
|
|
65
|
+
const instanceContainerIndex = instances.findIndex(instance => instance.id === instanceId);
|
|
66
|
+
const instanceContainer = instances[instanceContainerIndex];
|
|
67
|
+
if (instanceContainer === undefined) {
|
|
68
|
+
return error([404, "Instance not found"]);
|
|
69
|
+
}
|
|
70
|
+
if (locals.referencesToInstances[instanceId]?.some(ref => ref !== instanceId)) {
|
|
71
|
+
return error([400, "Cannot delete instance that is referenced by other instances"]);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await rm(join(locals.dataRoot, entityName, instanceContainer.fileName));
|
|
75
|
+
locals.instancesByEntityName[entityName] = removeAt(instances, instanceContainerIndex);
|
|
76
|
+
Object.assign(locals.referencesToInstances, updateReferencesToInstances(locals.entitiesByName, locals.referencesToInstances, entityName, instanceId, instanceContainer.content, undefined));
|
|
77
|
+
return ok(instanceContainer);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return error([500, `Failed to delete instance: ${err}`]);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const instancesApi: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { serializeEntityDecl } from "../../schema/declarations/EntityDecl.js";
|
|
4
|
+
import { getDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
|
|
5
|
+
const debug = Debug("tsondb:server:api:instances");
|
|
6
|
+
export const instancesApi = express.Router();
|
|
7
|
+
instancesApi.use((req, _res, next) => {
|
|
8
|
+
debug(req.path);
|
|
9
|
+
next();
|
|
10
|
+
});
|
|
11
|
+
instancesApi.get("/", (req, res) => {
|
|
12
|
+
const locales = (Array.isArray(req.query["locales"]) ? req.query["locales"] : [req.query["locales"]]).filter(locale => typeof locale === "string");
|
|
13
|
+
const body = {
|
|
14
|
+
instances: Object.fromEntries(Object.entries(req.instancesByEntityName).map(([entityName, instances]) => [
|
|
15
|
+
entityName,
|
|
16
|
+
instances.map(instance => ({
|
|
17
|
+
id: instance.id,
|
|
18
|
+
name: getDisplayNameFromEntityInstance(serializeEntityDecl(req.entitiesByName[entityName]), instance.content, instance.id, locales),
|
|
19
|
+
})),
|
|
20
|
+
])),
|
|
21
|
+
};
|
|
22
|
+
res.json(body);
|
|
23
|
+
});
|
package/lib/server/index.d.ts
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
+
import { SimpleGit } from "simple-git";
|
|
1
2
|
import { ModelContainer } from "../ModelContainer.js";
|
|
3
|
+
import { Decl } from "../schema/declarations/Declaration.js";
|
|
4
|
+
import { EntityDecl } from "../schema/declarations/EntityDecl.js";
|
|
2
5
|
import { InstancesByEntityName } from "../shared/utils/instances.js";
|
|
6
|
+
import { ReferencesToInstances } from "../utils/references.js";
|
|
3
7
|
type ServerOptions = {
|
|
4
8
|
name: string;
|
|
5
9
|
port: number;
|
|
6
10
|
};
|
|
7
|
-
export
|
|
11
|
+
export interface TSONDBRequestLocals {
|
|
12
|
+
git: SimpleGit;
|
|
13
|
+
gitRoot: string | undefined;
|
|
14
|
+
dataRoot: string;
|
|
15
|
+
declarations: readonly Decl[];
|
|
16
|
+
entities: readonly EntityDecl[];
|
|
17
|
+
instancesByEntityName: InstancesByEntityName;
|
|
18
|
+
entitiesByName: Record<string, EntityDecl>;
|
|
19
|
+
localeEntity?: EntityDecl;
|
|
20
|
+
referencesToInstances: ReferencesToInstances;
|
|
21
|
+
}
|
|
22
|
+
declare global {
|
|
23
|
+
namespace Express {
|
|
24
|
+
interface Request extends TSONDBRequestLocals {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export declare const createServer: (modelContainer: ModelContainer, instancesByEntityName: InstancesByEntityName, options?: Partial<ServerOptions>) => Promise<void>;
|
|
8
29
|
export {};
|