tsondb 0.1.3 → 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.
Files changed (110) hide show
  1. package/lib/ModelContainer.js +9 -7
  2. package/lib/Schema.js +1 -3
  3. package/lib/client/api.d.ts +14 -1
  4. package/lib/client/api.js +119 -0
  5. package/lib/client/components/Git.d.ts +2 -0
  6. package/lib/client/components/Git.js +116 -0
  7. package/lib/client/components/Layout.js +2 -1
  8. package/lib/client/components/typeInputs/ArrayTypeInput.js +3 -2
  9. package/lib/client/components/typeInputs/EnumTypeInput.d.ts +13 -0
  10. package/lib/client/components/typeInputs/{utils/EnumDeclField.js → EnumTypeInput.js} +13 -13
  11. package/lib/client/components/typeInputs/IncludeIdentifierTypeInput.js +1 -10
  12. package/lib/client/components/typeInputs/StringTypeInput.js +8 -5
  13. package/lib/client/components/typeInputs/TypeInput.js +3 -0
  14. package/lib/client/components/typeInputs/utils/Markdown.d.ts +6 -0
  15. package/lib/client/components/typeInputs/utils/Markdown.js +26 -0
  16. package/lib/client/hooks/useAPIResource.d.ts +1 -0
  17. package/lib/client/hooks/useAPIResource.js +2 -0
  18. package/lib/client/hooks/useMappedAPIResource.d.ts +1 -0
  19. package/lib/client/hooks/useMappedAPIResource.js +19 -0
  20. package/lib/client/routes/Entity.js +18 -24
  21. package/lib/client/routes/Home.js +3 -12
  22. package/lib/client/utils/typeSkeleton.js +10 -16
  23. package/lib/renderers/jsonschema/index.d.ts +1 -1
  24. package/lib/renderers/jsonschema/index.js +30 -7
  25. package/lib/renderers/jsonschema/render.d.ts +5 -1
  26. package/lib/renderers/jsonschema/render.js +43 -16
  27. package/lib/renderers/ts/index.d.ts +1 -1
  28. package/lib/renderers/ts/index.js +37 -6
  29. package/lib/renderers/ts/render.d.ts +2 -0
  30. package/lib/renderers/ts/render.js +59 -33
  31. package/lib/schema/Node.d.ts +2 -0
  32. package/lib/schema/Node.js +7 -1
  33. package/lib/schema/declarations/Declaration.d.ts +5 -3
  34. package/lib/schema/declarations/Declaration.js +14 -10
  35. package/lib/schema/declarations/EntityDecl.d.ts +6 -0
  36. package/lib/schema/declarations/EnumDecl.d.ts +11 -8
  37. package/lib/schema/declarations/EnumDecl.js +16 -68
  38. package/lib/schema/declarations/TypeAliasDecl.d.ts +4 -0
  39. package/lib/schema/declarations/TypeAliasDecl.js +1 -1
  40. package/lib/schema/index.d.ts +1 -0
  41. package/lib/schema/index.js +1 -0
  42. package/lib/schema/types/Type.d.ts +8 -2
  43. package/lib/schema/types/Type.js +57 -11
  44. package/lib/schema/types/generic/ArrayType.d.ts +2 -1
  45. package/lib/schema/types/generic/ArrayType.js +3 -2
  46. package/lib/schema/types/generic/EnumType.d.ts +38 -0
  47. package/lib/schema/types/generic/EnumType.js +96 -0
  48. package/lib/schema/types/generic/ObjectType.d.ts +6 -1
  49. package/lib/schema/types/generic/ObjectType.js +9 -4
  50. package/lib/schema/types/primitives/BooleanType.d.ts +2 -1
  51. package/lib/schema/types/primitives/BooleanType.js +1 -0
  52. package/lib/schema/types/primitives/DateType.d.ts +2 -1
  53. package/lib/schema/types/primitives/DateType.js +1 -0
  54. package/lib/schema/types/primitives/FloatType.d.ts +2 -1
  55. package/lib/schema/types/primitives/FloatType.js +1 -0
  56. package/lib/schema/types/primitives/IntegerType.d.ts +2 -1
  57. package/lib/schema/types/primitives/IntegerType.js +1 -0
  58. package/lib/schema/types/primitives/StringType.d.ts +2 -1
  59. package/lib/schema/types/primitives/StringType.js +1 -0
  60. package/lib/schema/types/references/GenericArgumentIdentifierType.d.ts +2 -1
  61. package/lib/schema/types/references/GenericArgumentIdentifierType.js +1 -0
  62. package/lib/schema/types/references/IncludeIdentifierType.d.ts +5 -3
  63. package/lib/schema/types/references/IncludeIdentifierType.js +18 -3
  64. package/lib/schema/types/references/NestedEntityMapType.d.ts +2 -1
  65. package/lib/schema/types/references/NestedEntityMapType.js +6 -13
  66. package/lib/schema/types/references/ReferenceIdentifierType.d.ts +6 -7
  67. package/lib/schema/types/references/ReferenceIdentifierType.js +5 -2
  68. package/lib/server/api/declarations.d.ts +1 -0
  69. package/lib/server/api/declarations.js +154 -0
  70. package/lib/server/api/git.d.ts +1 -0
  71. package/lib/server/api/git.js +174 -0
  72. package/lib/server/api/index.d.ts +1 -0
  73. package/lib/server/api/index.js +8 -0
  74. package/lib/server/api/instanceOperations.d.ts +6 -0
  75. package/lib/server/api/instanceOperations.js +82 -0
  76. package/lib/server/api/instances.d.ts +1 -0
  77. package/lib/server/api/instances.js +23 -0
  78. package/lib/server/index.d.ts +22 -1
  79. package/lib/server/index.js +11 -165
  80. package/lib/server/init.d.ts +5 -0
  81. package/lib/server/init.js +56 -0
  82. package/lib/shared/api.d.ts +12 -1
  83. package/lib/shared/utils/array.d.ts +19 -0
  84. package/lib/shared/utils/array.js +27 -0
  85. package/lib/shared/utils/git.d.ts +12 -0
  86. package/lib/shared/utils/git.js +98 -0
  87. package/lib/shared/utils/instances.d.ts +10 -0
  88. package/lib/shared/utils/instances.js +8 -1
  89. package/lib/shared/utils/markdown.d.ts +14 -0
  90. package/lib/shared/utils/markdown.js +42 -0
  91. package/lib/shared/utils/object.d.ts +1 -0
  92. package/lib/shared/utils/object.js +4 -0
  93. package/lib/shared/utils/string.d.ts +1 -0
  94. package/lib/shared/utils/string.js +9 -0
  95. package/lib/tsconfig.tsbuildinfo +1 -1
  96. package/lib/utils/git.d.ts +3 -0
  97. package/lib/utils/git.js +12 -0
  98. package/lib/utils/instances.d.ts +3 -2
  99. package/lib/utils/instances.js +9 -2
  100. package/lib/utils/path.d.ts +1 -0
  101. package/lib/utils/path.js +2 -0
  102. package/lib/utils/references.d.ts +7 -0
  103. package/lib/utils/references.js +40 -0
  104. package/lib/utils/render.d.ts +6 -1
  105. package/lib/utils/render.js +27 -1
  106. package/package.json +8 -2
  107. package/public/css/styles.css +238 -1
  108. package/lib/client/components/typeInputs/utils/EnumDeclField.d.ts +0 -13
  109. package/lib/server/instanceOperations.d.ts +0 -7
  110. package/lib/server/instanceOperations.js +0 -67
@@ -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
+ });
@@ -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 declare const createServer: (modelContainer: ModelContainer, instancesByEntityName: InstancesByEntityName, options?: Partial<ServerOptions>) => void;
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 {};
@@ -1,17 +1,14 @@
1
+ import Debug from "debug";
1
2
  import express from "express";
2
3
  import { join } from "node:path";
3
- import { serializeDecl } from "../schema/declarations/Declaration.js";
4
- import { isEntityDecl, serializeEntityDecl } from "../schema/declarations/EntityDecl.js";
5
- import { isEnumDecl } from "../schema/declarations/EnumDecl.js";
6
- import { isTypeAliasDecl } from "../schema/declarations/TypeAliasDecl.js";
7
- import { getDisplayNameFromEntityInstance } from "../shared/utils/displayName.js";
8
- import { isOk } from "../utils/result.js";
9
- import { createInstance, deleteInstance, updateInstance } from "./instanceOperations.js";
4
+ import { api } from "./api/index.js";
5
+ import { init } from "./init.js";
6
+ const debug = Debug("tsondb:server");
10
7
  const defaultOptions = {
11
8
  name: "tsondb server",
12
9
  port: 3000,
13
10
  };
14
- export const createServer = (modelContainer, instancesByEntityName, options) => {
11
+ export const createServer = async (modelContainer, instancesByEntityName, options) => {
15
12
  const { name, port } = { ...defaultOptions, ...options };
16
13
  const app = express();
17
14
  app.use(express.static(join(import.meta.dirname, "../../public")));
@@ -19,163 +16,12 @@ export const createServer = (modelContainer, instancesByEntityName, options) =>
19
16
  app.use("/js/client", express.static(join(import.meta.dirname, "../../lib/client")));
20
17
  app.use("/js/shared", express.static(join(import.meta.dirname, "../../lib/shared")));
21
18
  app.use(express.json());
22
- const declarations = modelContainer.schema.declarations;
23
- const entities = declarations.filter(isEntityDecl);
24
- const entitiesByName = Object.fromEntries(entities.map(entity => [entity.name, entity]));
25
- const instancesByEntityNameInMemory = { ...instancesByEntityName };
26
- app.get("/api/declarations", (req, res) => {
27
- let filteredEntities;
28
- switch (req.query["kind"]) {
29
- case "Entity":
30
- filteredEntities = entities.filter(isEntityDecl);
31
- break;
32
- case "TypeAlias":
33
- filteredEntities = entities.filter(isTypeAliasDecl);
34
- break;
35
- case "Enum":
36
- filteredEntities = entities.filter(isEnumDecl);
37
- break;
38
- default:
39
- filteredEntities = declarations;
40
- }
41
- const body = {
42
- declarations: filteredEntities.map(decl => ({
43
- declaration: serializeDecl(decl),
44
- instanceCount: instancesByEntityNameInMemory[decl.name]?.length ?? 0,
45
- })),
46
- localeEntity: modelContainer.schema.localeEntity?.name,
47
- };
48
- res.json(body);
49
- });
50
- app.get("/api/declarations/:name", (req, res) => {
51
- const decl = declarations.find(decl => decl.name === req.params.name);
52
- if (decl === undefined) {
53
- res.status(404).send(`Declaration "${req.params.name}" not found`);
54
- return;
55
- }
56
- const body = {
57
- declaration: serializeDecl(decl),
58
- instanceCount: instancesByEntityNameInMemory[decl.name]?.length ?? 0,
59
- isLocaleEntity: decl === modelContainer.schema.localeEntity,
60
- };
61
- res.json(body);
62
- });
63
- app.get("/api/declarations/:name/instances", (req, res) => {
64
- const decl = declarations.find(decl => decl.name === req.params.name);
65
- if (decl === undefined) {
66
- res.status(404).send(`Declaration "${req.params.name}" not found`);
67
- return;
68
- }
69
- if (!isEntityDecl(decl)) {
70
- res.status(400).send(`Declaration "${decl.name}" is not an entity`);
71
- return;
72
- }
73
- const body = {
74
- instances: instancesByEntityNameInMemory[req.params.name] ?? [],
75
- isLocaleEntity: decl === modelContainer.schema.localeEntity,
76
- };
77
- res.json(body);
78
- });
79
- app.post("/api/declarations/:name/instances", async (req, res) => {
80
- const decl = declarations.find(decl => decl.name === req.params.name);
81
- if (decl === undefined) {
82
- res.status(404).send(`Declaration "${req.params.name}" not found`);
83
- return;
84
- }
85
- if (!isEntityDecl(decl)) {
86
- res.status(400).send(`Declaration "${decl.name}" is not an entity`);
87
- return;
88
- }
89
- const result = await createInstance(modelContainer, entitiesByName, instancesByEntityNameInMemory, decl.name, req.body, req.query["id"]);
90
- if (isOk(result)) {
91
- const body = {
92
- instance: result.value,
93
- isLocaleEntity: decl === modelContainer.schema.localeEntity,
94
- };
95
- res.json(body);
96
- }
97
- else {
98
- res.status(result.error[0]).send(result.error[1]);
99
- }
100
- });
101
- app.get("/api/declarations/:name/instances/:id", (req, res) => {
102
- const decl = declarations.find(decl => decl.name === req.params.name);
103
- if (decl === undefined) {
104
- res.status(404).send(`Declaration "${req.params.name}" not found`);
105
- return;
106
- }
107
- if (!isEntityDecl(decl)) {
108
- res.status(400).send(`Declaration "${decl.name}" is not an entity`);
109
- return;
110
- }
111
- const instance = instancesByEntityNameInMemory[decl.name]?.find(instance => instance.id === req.params.id);
112
- if (instance === undefined) {
113
- res.status(404).send(`Instance "${req.params.id}" not found`);
114
- return;
115
- }
116
- const body = {
117
- instance: instance,
118
- isLocaleEntity: decl === modelContainer.schema.localeEntity,
119
- };
120
- res.json(body);
121
- });
122
- app.put("/api/declarations/:name/instances/:id", async (req, res) => {
123
- const decl = declarations.find(decl => decl.name === req.params.name);
124
- if (decl === undefined) {
125
- res.status(404).send(`Declaration "${req.params.name}" not found`);
126
- return;
127
- }
128
- if (!isEntityDecl(decl)) {
129
- res.status(400).send(`Declaration "${decl.name}" is not an entity`);
130
- return;
131
- }
132
- const result = await updateInstance(modelContainer, entitiesByName, instancesByEntityNameInMemory, decl.name, req.params.id, req.body);
133
- if (isOk(result)) {
134
- const body = {
135
- instance: result.value,
136
- isLocaleEntity: decl === modelContainer.schema.localeEntity,
137
- };
138
- res.json(body);
139
- }
140
- else {
141
- res.status(result.error[0]).send(result.error[1]);
142
- }
143
- });
144
- app.delete("/api/declarations/:name/instances/:id", async (req, res) => {
145
- const decl = declarations.find(decl => decl.name === req.params.name);
146
- if (decl === undefined) {
147
- res.status(404).send(`Declaration "${req.params.name}" not found`);
148
- return;
149
- }
150
- if (!isEntityDecl(decl)) {
151
- res.status(400).send(`Declaration "${decl.name}" is not an entity`);
152
- return;
153
- }
154
- const result = await deleteInstance(modelContainer, instancesByEntityNameInMemory, decl.name, req.params.id);
155
- if (isOk(result)) {
156
- const body = {
157
- instance: result.value,
158
- isLocaleEntity: decl === modelContainer.schema.localeEntity,
159
- };
160
- res.json(body);
161
- }
162
- else {
163
- res.status(result.error[0]).send(result.error[1]);
164
- }
165
- });
166
- app.get("/api/instances", (req, res) => {
167
- const locales = (Array.isArray(req.query["locales"]) ? req.query["locales"] : [req.query["locales"]]).filter(locale => typeof locale === "string");
168
- const body = {
169
- instances: Object.fromEntries(Object.entries(instancesByEntityNameInMemory).map(([entityName, instances]) => [
170
- entityName,
171
- instances.map(instance => ({
172
- id: instance.id,
173
- name: getDisplayNameFromEntityInstance(serializeEntityDecl(entitiesByName[entityName]), instance.content, instance.id, locales),
174
- })),
175
- ])),
176
- };
177
- res.json(body);
19
+ const requestLocals = await init(modelContainer, Object.assign({}, instancesByEntityName));
20
+ app.use((req, _res, next) => {
21
+ Object.assign(req, requestLocals);
22
+ next();
178
23
  });
24
+ app.use("/api", api);
179
25
  app.get(/^\/.*/, (_req, res) => {
180
26
  res.send(`<!DOCTYPE html>
181
27
  <html lang="en">
@@ -202,6 +48,6 @@ export const createServer = (modelContainer, instancesByEntityName, options) =>
202
48
  </html>`);
203
49
  });
204
50
  app.listen(port, () => {
205
- console.log(`${name} listening on http://localhost:${port}`);
51
+ debug(`${name} listening on http://localhost:${port}`);
206
52
  });
207
53
  };
@@ -0,0 +1,5 @@
1
+ import { ModelContainer } from "../ModelContainer.js";
2
+ import { InstancesByEntityName } from "../shared/utils/instances.js";
3
+ import { TSONDBRequestLocals } from "./index.js";
4
+ export declare const init: (modelContainer: ModelContainer, instancesByEntityName: InstancesByEntityName) => Promise<TSONDBRequestLocals>;
5
+ export declare const reinit: (locals: TSONDBRequestLocals) => Promise<void>;
@@ -0,0 +1,56 @@
1
+ import { simpleGit } from "simple-git";
2
+ import { isEntityDecl } from "../schema/declarations/EntityDecl.js";
3
+ import { resolveTypeArgumentsInDecls } from "../schema/index.js";
4
+ import { attachGitStatusToInstancesByEntityName, getInstancesByEntityName, } from "../utils/instances.js";
5
+ import { getReferencesToInstances } from "../utils/references.js";
6
+ const getGit = async (modelContainer) => {
7
+ const git = simpleGit({ baseDir: modelContainer.dataRootPath });
8
+ if (await git.checkIsRepo()) {
9
+ try {
10
+ const root = await git.revparse({ "--show-toplevel": null });
11
+ const status = await git.status();
12
+ return { git, root, status };
13
+ }
14
+ catch {
15
+ return { git };
16
+ }
17
+ }
18
+ else {
19
+ return { git };
20
+ }
21
+ };
22
+ export const init = async (modelContainer, instancesByEntityName) => {
23
+ const { git, root: gitRoot, status: gitStatus } = await getGit(modelContainer);
24
+ const declarations = resolveTypeArgumentsInDecls(modelContainer.schema.declarations);
25
+ const entities = declarations.filter(isEntityDecl);
26
+ const entitiesByName = Object.fromEntries(entities.map(entity => [entity.name, entity]));
27
+ const instancesByEntityNameInMemory = Object.assign({}, instancesByEntityName);
28
+ const referencesToInstances = getReferencesToInstances(instancesByEntityName, entitiesByName);
29
+ if (gitStatus) {
30
+ attachGitStatusToInstancesByEntityName(instancesByEntityName, modelContainer.dataRootPath, gitRoot, gitStatus);
31
+ }
32
+ const requestLocals = {
33
+ git: git,
34
+ gitRoot: gitRoot,
35
+ dataRoot: modelContainer.dataRootPath,
36
+ declarations: declarations,
37
+ entities: entities,
38
+ instancesByEntityName: instancesByEntityNameInMemory,
39
+ entitiesByName: entitiesByName,
40
+ localeEntity: modelContainer.schema.localeEntity,
41
+ referencesToInstances,
42
+ };
43
+ return requestLocals;
44
+ };
45
+ export const reinit = async (locals) => {
46
+ const gitStatus = (await locals.git.checkIsRepo()) ? await locals.git.status() : undefined;
47
+ const instancesByEntityName = await getInstancesByEntityName(locals.dataRoot, locals.entities);
48
+ const referencesToInstances = getReferencesToInstances(instancesByEntityName, locals.entitiesByName);
49
+ if (locals.gitRoot && gitStatus) {
50
+ attachGitStatusToInstancesByEntityName(instancesByEntityName, locals.dataRoot, locals.gitRoot, gitStatus);
51
+ }
52
+ Object.assign(locals, {
53
+ instancesByEntityName,
54
+ referencesToInstances,
55
+ });
56
+ };
@@ -1,5 +1,5 @@
1
1
  import { SerializedDecl } from "../schema/declarations/Declaration.js";
2
- import { InstanceContainer } from "./utils/instances.js";
2
+ import { InstanceContainer, InstanceContainerOverview } from "./utils/instances.js";
3
3
  export interface GetAllDeclarationsResponseBody<D extends SerializedDecl = SerializedDecl> {
4
4
  declarations: {
5
5
  declaration: D;
@@ -40,3 +40,14 @@ export interface GetAllInstancesResponseBody {
40
40
  }[];
41
41
  };
42
42
  }
43
+ export interface GitStatusResponseBody {
44
+ commitsAhead: number;
45
+ commitsBehind: number;
46
+ instances: {
47
+ [entity: string]: InstanceContainerOverview[];
48
+ };
49
+ }
50
+ export interface GetAllGitBranchesResponseBody {
51
+ allBranches: string[];
52
+ currentBranch: string;
53
+ }
@@ -0,0 +1,19 @@
1
+ export declare const removeAt: <T>(arr: T[], index: number) => T[];
2
+ export declare const insertAt: <T>(arr: T[], index: number, item: T) => T[];
3
+ /**
4
+ * Calculates the difference between two arrays, including duplicated values.
5
+ * @param oldArr - The original array.
6
+ * @param newArr - The new array to compare against.
7
+ * @returns An object containing the added and removed elements.
8
+ */
9
+ export declare const difference: <T>(oldArr: T[], newArr: T[]) => ArrayDiffResult<T>;
10
+ export interface ArrayDiffResult<T> {
11
+ /**
12
+ * Elements in `newArr` that are not in `oldArr`.
13
+ */
14
+ added: T[];
15
+ /**
16
+ * Elements in `oldArr` that are not in `newArr`.
17
+ */
18
+ removed: T[];
19
+ }
@@ -0,0 +1,27 @@
1
+ export const removeAt = (arr, index) => [
2
+ ...arr.slice(0, index),
3
+ ...arr.slice(index + 1),
4
+ ];
5
+ export const insertAt = (arr, index, item) => [
6
+ ...arr.slice(0, index),
7
+ item,
8
+ ...arr.slice(index),
9
+ ];
10
+ /**
11
+ * Calculates the difference between two arrays, including duplicated values.
12
+ * @param oldArr - The original array.
13
+ * @param newArr - The new array to compare against.
14
+ * @returns An object containing the added and removed elements.
15
+ */
16
+ export const difference = (oldArr, newArr) => newArr.reduce((acc, item) => {
17
+ const oldIndex = acc.removed.indexOf(item);
18
+ const newIndex = acc.added.indexOf(item);
19
+ if (oldIndex > -1) {
20
+ return {
21
+ ...acc,
22
+ removed: removeAt(acc.removed, oldIndex),
23
+ added: removeAt(acc.added, newIndex),
24
+ };
25
+ }
26
+ return acc;
27
+ }, { removed: oldArr, added: newArr });
@@ -0,0 +1,12 @@
1
+ export type GitFileStatus = {
2
+ index: GitFileStatusCode;
3
+ workingDir: GitFileStatusCode;
4
+ };
5
+ export declare const isChangedInIndex: (fileStatus: GitFileStatus) => boolean;
6
+ export declare const isChangedInWorkingDir: (fileStatus: GitFileStatus) => boolean;
7
+ export type GitFileStatusCode = " " | "M" | "T" | "A" | "D" | "R" | "C" | "U" | "m" | "?" | "!";
8
+ export declare const isUnchangedStatus: (code: GitFileStatusCode) => boolean;
9
+ export type GitFileStatusForDisplay = "U" | "M" | "A" | "D" | "R" | undefined;
10
+ export declare const hasFileChanges: (fileStatus: GitFileStatus | undefined) => boolean;
11
+ export declare const getGitStatusForDisplay: (fileStatus: GitFileStatus | undefined) => GitFileStatusForDisplay;
12
+ export declare const getLabelForGitStatus: (status: GitFileStatusForDisplay) => string;
@@ -0,0 +1,98 @@
1
+ export const isChangedInIndex = (fileStatus) => {
2
+ const { index } = fileStatus;
3
+ switch (index) {
4
+ case "M":
5
+ case "T":
6
+ case "A":
7
+ case "D":
8
+ case "R":
9
+ case "C":
10
+ case "U":
11
+ return true;
12
+ case " ":
13
+ case "m":
14
+ case "?":
15
+ case "!":
16
+ return false;
17
+ }
18
+ };
19
+ export const isChangedInWorkingDir = (fileStatus) => {
20
+ const { workingDir } = fileStatus;
21
+ switch (workingDir) {
22
+ case "M":
23
+ case "T":
24
+ case "A":
25
+ case "D":
26
+ case "R":
27
+ case "C":
28
+ case "U":
29
+ case "?":
30
+ return true;
31
+ case " ":
32
+ case "m":
33
+ case "!":
34
+ return false;
35
+ }
36
+ };
37
+ const unchangedStatusCodes = new Set([" ", "!", "C", "T", "m", "U"]);
38
+ export const isUnchangedStatus = (code) => unchangedStatusCodes.has(code);
39
+ export const hasFileChanges = (fileStatus) => {
40
+ if (fileStatus === undefined) {
41
+ return false;
42
+ }
43
+ const { index, workingDir } = fileStatus;
44
+ return !isUnchangedStatus(index) || !isUnchangedStatus(workingDir);
45
+ };
46
+ export const getGitStatusForDisplay = (fileStatus) => {
47
+ if (fileStatus === undefined) {
48
+ return undefined;
49
+ }
50
+ const { index, workingDir } = fileStatus;
51
+ switch (workingDir) {
52
+ case " ":
53
+ switch (index) {
54
+ case " ":
55
+ return undefined;
56
+ case "M":
57
+ return "M";
58
+ case "A":
59
+ return "A";
60
+ case "D":
61
+ return "D";
62
+ case "R":
63
+ return "R";
64
+ case "?":
65
+ return "U";
66
+ default:
67
+ return undefined;
68
+ }
69
+ case "M":
70
+ return "M";
71
+ case "A":
72
+ return "A";
73
+ case "D":
74
+ return "D";
75
+ case "R":
76
+ return "R";
77
+ case "?":
78
+ return "U";
79
+ default:
80
+ return undefined;
81
+ }
82
+ };
83
+ export const getLabelForGitStatus = (status) => {
84
+ switch (status) {
85
+ case "U":
86
+ return "untracked";
87
+ case "M":
88
+ return "modified";
89
+ case "A":
90
+ return "sdded";
91
+ case "D":
92
+ return "deleted";
93
+ case "R":
94
+ return "renamed";
95
+ default:
96
+ return "";
97
+ }
98
+ };
@@ -1,6 +1,16 @@
1
+ import { SerializedEntityDecl } from "../../schema/index.js";
2
+ import { GitFileStatus } from "./git.js";
1
3
  export interface InstanceContainer {
2
4
  fileName: string;
3
5
  id: string;
4
6
  content: unknown;
7
+ gitStatus?: GitFileStatus;
5
8
  }
9
+ export interface InstanceContainerOverview {
10
+ fileName: string;
11
+ id: string;
12
+ gitStatus?: GitFileStatus;
13
+ displayName: string;
14
+ }
15
+ export declare const getInstanceContainerOverview: (entity: SerializedEntityDecl, instanceContainer: InstanceContainer, locales?: string[]) => InstanceContainerOverview;
6
16
  export type InstancesByEntityName = Record<string, InstanceContainer[]>;
@@ -1 +1,8 @@
1
- export {};
1
+ import { getDisplayNameFromEntityInstance } from "./displayName.js";
2
+ export const getInstanceContainerOverview = (entity, instanceContainer, locales) => {
3
+ const { content: _, ...rest } = instanceContainer;
4
+ return {
5
+ ...rest,
6
+ displayName: getDisplayNameFromEntityInstance(entity, instanceContainer.content, instanceContainer.id, locales),
7
+ };
8
+ };
@@ -0,0 +1,14 @@
1
+ type TextNode = {
2
+ kind: "text";
3
+ content: string;
4
+ };
5
+ export type InlineMarkdownNode = {
6
+ kind: "bold" | "italic";
7
+ content: InlineMarkdownNode[];
8
+ } | TextNode;
9
+ export type BlockMarkdownNode = {
10
+ kind: "paragraph";
11
+ content: InlineMarkdownNode[];
12
+ } | TextNode;
13
+ export declare const parseBlockMarkdown: (text: string) => BlockMarkdownNode[];
14
+ export {};