tsondb 0.1.3

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 (170) hide show
  1. package/README.md +3 -0
  2. package/lib/ModelContainer.d.ts +17 -0
  3. package/lib/ModelContainer.js +63 -0
  4. package/lib/Schema.d.ts +8 -0
  5. package/lib/Schema.js +72 -0
  6. package/lib/client/api.d.ts +11 -0
  7. package/lib/client/api.js +83 -0
  8. package/lib/client/components/Layout.d.ts +10 -0
  9. package/lib/client/components/Layout.js +4 -0
  10. package/lib/client/components/Select.d.ts +3 -0
  11. package/lib/client/components/Select.js +2 -0
  12. package/lib/client/components/typeInputs/ArrayTypeInput.d.ts +13 -0
  13. package/lib/client/components/typeInputs/ArrayTypeInput.js +10 -0
  14. package/lib/client/components/typeInputs/BooleanTypeInput.d.ts +9 -0
  15. package/lib/client/components/typeInputs/BooleanTypeInput.js +6 -0
  16. package/lib/client/components/typeInputs/DateTypeInput.d.ts +9 -0
  17. package/lib/client/components/typeInputs/DateTypeInput.js +9 -0
  18. package/lib/client/components/typeInputs/FloatTypeInput.d.ts +9 -0
  19. package/lib/client/components/typeInputs/FloatTypeInput.js +15 -0
  20. package/lib/client/components/typeInputs/GenericTypeArgumentIdentifierTypeInput.d.ts +7 -0
  21. package/lib/client/components/typeInputs/GenericTypeArgumentIdentifierTypeInput.js +4 -0
  22. package/lib/client/components/typeInputs/IncludeIdentifierTypeInput.d.ts +13 -0
  23. package/lib/client/components/typeInputs/IncludeIdentifierTypeInput.js +18 -0
  24. package/lib/client/components/typeInputs/IntegerTypeInput.d.ts +9 -0
  25. package/lib/client/components/typeInputs/IntegerTypeInput.js +15 -0
  26. package/lib/client/components/typeInputs/NestedEntityMapTypeInput.d.ts +13 -0
  27. package/lib/client/components/typeInputs/NestedEntityMapTypeInput.js +25 -0
  28. package/lib/client/components/typeInputs/ObjectTypeInput.d.ts +13 -0
  29. package/lib/client/components/typeInputs/ObjectTypeInput.js +20 -0
  30. package/lib/client/components/typeInputs/ReferenceIdentifierTypeInput.d.ts +11 -0
  31. package/lib/client/components/typeInputs/ReferenceIdentifierTypeInput.js +9 -0
  32. package/lib/client/components/typeInputs/StringTypeInput.d.ts +9 -0
  33. package/lib/client/components/typeInputs/StringTypeInput.js +10 -0
  34. package/lib/client/components/typeInputs/TypeInput.d.ts +13 -0
  35. package/lib/client/components/typeInputs/TypeInput.js +87 -0
  36. package/lib/client/components/typeInputs/utils/EnumDeclField.d.ts +13 -0
  37. package/lib/client/components/typeInputs/utils/EnumDeclField.js +38 -0
  38. package/lib/client/components/typeInputs/utils/MismatchingTypeError.d.ts +7 -0
  39. package/lib/client/components/typeInputs/utils/MismatchingTypeError.js +4 -0
  40. package/lib/client/components/typeInputs/utils/ValidationErrors.d.ts +6 -0
  41. package/lib/client/components/typeInputs/utils/ValidationErrors.js +4 -0
  42. package/lib/client/hooks/useEntityFromRoute.d.ts +5 -0
  43. package/lib/client/hooks/useEntityFromRoute.js +20 -0
  44. package/lib/client/hooks/useInstanceNamesByEntity.d.ts +3 -0
  45. package/lib/client/hooks/useInstanceNamesByEntity.js +18 -0
  46. package/lib/client/hooks/useSecondaryDeclarations.d.ts +3 -0
  47. package/lib/client/hooks/useSecondaryDeclarations.js +18 -0
  48. package/lib/client/index.d.ts +1 -0
  49. package/lib/client/index.js +11 -0
  50. package/lib/client/routes/CreateInstance.d.ts +2 -0
  51. package/lib/client/routes/CreateInstance.js +68 -0
  52. package/lib/client/routes/Entity.d.ts +2 -0
  53. package/lib/client/routes/Entity.js +47 -0
  54. package/lib/client/routes/Home.d.ts +2 -0
  55. package/lib/client/routes/Home.js +18 -0
  56. package/lib/client/routes/Instance.d.ts +2 -0
  57. package/lib/client/routes/Instance.js +73 -0
  58. package/lib/client/routes/NotFound.d.ts +2 -0
  59. package/lib/client/routes/NotFound.js +5 -0
  60. package/lib/client/utils/typeSkeleton.d.ts +3 -0
  61. package/lib/client/utils/typeSkeleton.js +51 -0
  62. package/lib/index.d.ts +1 -0
  63. package/lib/index.js +1 -0
  64. package/lib/renderers/Output.d.ts +4 -0
  65. package/lib/renderers/Output.js +1 -0
  66. package/lib/renderers/jsonschema/index.d.ts +6 -0
  67. package/lib/renderers/jsonschema/index.js +12 -0
  68. package/lib/renderers/jsonschema/render.d.ts +5 -0
  69. package/lib/renderers/jsonschema/render.js +156 -0
  70. package/lib/renderers/ts/index.d.ts +6 -0
  71. package/lib/renderers/ts/index.js +11 -0
  72. package/lib/renderers/ts/render.d.ts +5 -0
  73. package/lib/renderers/ts/render.js +109 -0
  74. package/lib/schema/Node.d.ts +37 -0
  75. package/lib/schema/Node.js +79 -0
  76. package/lib/schema/declarations/Declaration.d.ts +44 -0
  77. package/lib/schema/declarations/Declaration.js +98 -0
  78. package/lib/schema/declarations/EntityDecl.d.ts +73 -0
  79. package/lib/schema/declarations/EntityDecl.js +57 -0
  80. package/lib/schema/declarations/EnumDecl.d.ts +33 -0
  81. package/lib/schema/declarations/EnumDecl.js +104 -0
  82. package/lib/schema/declarations/TypeAliasDecl.d.ts +33 -0
  83. package/lib/schema/declarations/TypeAliasDecl.js +49 -0
  84. package/lib/schema/index.d.ts +20 -0
  85. package/lib/schema/index.js +20 -0
  86. package/lib/schema/parameters/TypeParameter.d.ts +14 -0
  87. package/lib/schema/parameters/TypeParameter.js +11 -0
  88. package/lib/schema/types/Type.d.ts +42 -0
  89. package/lib/schema/types/Type.js +177 -0
  90. package/lib/schema/types/generic/ArrayType.d.ts +30 -0
  91. package/lib/schema/types/generic/ArrayType.js +38 -0
  92. package/lib/schema/types/generic/ObjectType.d.ts +47 -0
  93. package/lib/schema/types/generic/ObjectType.js +70 -0
  94. package/lib/schema/types/primitives/BooleanType.d.ts +15 -0
  95. package/lib/schema/types/primitives/BooleanType.js +15 -0
  96. package/lib/schema/types/primitives/DateType.d.ts +16 -0
  97. package/lib/schema/types/primitives/DateType.js +17 -0
  98. package/lib/schema/types/primitives/FloatType.d.ts +26 -0
  99. package/lib/schema/types/primitives/FloatType.js +17 -0
  100. package/lib/schema/types/primitives/IntegerType.d.ts +26 -0
  101. package/lib/schema/types/primitives/IntegerType.js +21 -0
  102. package/lib/schema/types/primitives/NumericType.d.ts +6 -0
  103. package/lib/schema/types/primitives/NumericType.js +2 -0
  104. package/lib/schema/types/primitives/PrimitiveType.d.ts +6 -0
  105. package/lib/schema/types/primitives/PrimitiveType.js +1 -0
  106. package/lib/schema/types/primitives/StringType.d.ts +25 -0
  107. package/lib/schema/types/primitives/StringType.js +20 -0
  108. package/lib/schema/types/references/GenericArgumentIdentifierType.d.ts +21 -0
  109. package/lib/schema/types/references/GenericArgumentIdentifierType.js +18 -0
  110. package/lib/schema/types/references/IncludeIdentifierType.d.ts +28 -0
  111. package/lib/schema/types/references/IncludeIdentifierType.js +25 -0
  112. package/lib/schema/types/references/NestedEntityMapType.d.ts +36 -0
  113. package/lib/schema/types/references/NestedEntityMapType.js +67 -0
  114. package/lib/schema/types/references/ReferenceIdentifierType.d.ts +23 -0
  115. package/lib/schema/types/references/ReferenceIdentifierType.js +21 -0
  116. package/lib/schema/validation/options.d.ts +4 -0
  117. package/lib/schema/validation/options.js +12 -0
  118. package/lib/schema/validation/type.d.ts +4 -0
  119. package/lib/schema/validation/type.js +1 -0
  120. package/lib/server/index.d.ts +8 -0
  121. package/lib/server/index.js +207 -0
  122. package/lib/server/instanceOperations.d.ts +7 -0
  123. package/lib/server/instanceOperations.js +67 -0
  124. package/lib/shared/api.d.ts +42 -0
  125. package/lib/shared/api.js +1 -0
  126. package/lib/shared/enum.d.ts +1 -0
  127. package/lib/shared/enum.js +1 -0
  128. package/lib/shared/utils/compare.d.ts +13 -0
  129. package/lib/shared/utils/compare.js +24 -0
  130. package/lib/shared/utils/displayName.d.ts +2 -0
  131. package/lib/shared/utils/displayName.js +31 -0
  132. package/lib/shared/utils/instances.d.ts +6 -0
  133. package/lib/shared/utils/instances.js +1 -0
  134. package/lib/shared/utils/object.d.ts +2 -0
  135. package/lib/shared/utils/object.js +2 -0
  136. package/lib/shared/utils/string.d.ts +6 -0
  137. package/lib/shared/utils/string.js +52 -0
  138. package/lib/shared/utils/typeSafety.d.ts +1 -0
  139. package/lib/shared/utils/typeSafety.js +3 -0
  140. package/lib/shared/utils/validation.d.ts +3 -0
  141. package/lib/shared/utils/validation.js +14 -0
  142. package/lib/shared/validation/array.d.ts +6 -0
  143. package/lib/shared/validation/array.js +29 -0
  144. package/lib/shared/validation/date.d.ts +4 -0
  145. package/lib/shared/validation/date.js +13 -0
  146. package/lib/shared/validation/identifier.d.ts +1 -0
  147. package/lib/shared/validation/identifier.js +7 -0
  148. package/lib/shared/validation/number.d.ts +12 -0
  149. package/lib/shared/validation/number.js +34 -0
  150. package/lib/shared/validation/object.d.ts +6 -0
  151. package/lib/shared/validation/object.js +13 -0
  152. package/lib/shared/validation/string.d.ts +6 -0
  153. package/lib/shared/validation/string.js +15 -0
  154. package/lib/tsconfig.tsbuildinfo +1 -0
  155. package/lib/utils/enum.d.ts +6 -0
  156. package/lib/utils/enum.js +1 -0
  157. package/lib/utils/error.d.ts +2 -0
  158. package/lib/utils/error.js +18 -0
  159. package/lib/utils/instances.d.ts +4 -0
  160. package/lib/utils/instances.js +12 -0
  161. package/lib/utils/lazy.d.ts +16 -0
  162. package/lib/utils/lazy.js +32 -0
  163. package/lib/utils/object.d.ts +3 -0
  164. package/lib/utils/object.js +1 -0
  165. package/lib/utils/render.d.ts +4 -0
  166. package/lib/utils/render.js +8 -0
  167. package/lib/utils/result.d.ts +57 -0
  168. package/lib/utils/result.js +48 -0
  169. package/package.json +46 -0
  170. package/public/css/styles.css +418 -0
@@ -0,0 +1,8 @@
1
+ import { ModelContainer } from "../ModelContainer.js";
2
+ import { InstancesByEntityName } from "../shared/utils/instances.js";
3
+ type ServerOptions = {
4
+ name: string;
5
+ port: number;
6
+ };
7
+ export declare const createServer: (modelContainer: ModelContainer, instancesByEntityName: InstancesByEntityName, options?: Partial<ServerOptions>) => void;
8
+ export {};
@@ -0,0 +1,207 @@
1
+ import express from "express";
2
+ 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";
10
+ const defaultOptions = {
11
+ name: "tsondb server",
12
+ port: 3000,
13
+ };
14
+ export const createServer = (modelContainer, instancesByEntityName, options) => {
15
+ const { name, port } = { ...defaultOptions, ...options };
16
+ const app = express();
17
+ app.use(express.static(join(import.meta.dirname, "../../public")));
18
+ app.use("/js/node_modules", express.static(join(import.meta.dirname, "../../node_modules")));
19
+ app.use("/js/client", express.static(join(import.meta.dirname, "../../lib/client")));
20
+ app.use("/js/shared", express.static(join(import.meta.dirname, "../../lib/shared")));
21
+ 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);
178
+ });
179
+ app.get(/^\/.*/, (_req, res) => {
180
+ res.send(`<!DOCTYPE html>
181
+ <html lang="en">
182
+ <head>
183
+ <meta charset="UTF-8">
184
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
185
+ <title>TSONDB</title>
186
+ <link rel="stylesheet" href="/css/styles.css">
187
+ </head>
188
+ <body>
189
+ <div id="app"></div>
190
+ <script type="importmap">
191
+ {
192
+ "imports": {
193
+ "preact": "/js/node_modules/preact/dist/preact.module.js",
194
+ "preact/hooks": "/js/node_modules/preact/hooks/dist/hooks.module.js",
195
+ "preact/jsx-runtime": "/js/node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js",
196
+ "preact-iso": "/js/node_modules/preact-iso/src/index.js"
197
+ }
198
+ }
199
+ </script>
200
+ <script type="module" src="/js/client/index.js"></script>
201
+ </body>
202
+ </html>`);
203
+ });
204
+ app.listen(port, () => {
205
+ console.log(`${name} listening on http://localhost:${port}`);
206
+ });
207
+ };
@@ -0,0 +1,7 @@
1
+ import { ModelContainer } from "../ModelContainer.js";
2
+ import { EntityDecl } from "../schema/declarations/EntityDecl.js";
3
+ import { InstanceContainer, InstancesByEntityName } from "../shared/utils/instances.js";
4
+ import { Result } from "../utils/result.js";
5
+ export declare const createInstance: (modelContainer: ModelContainer, entitiesByName: Record<string, EntityDecl>, instancesByEntityName: InstancesByEntityName, entityName: string, instance: unknown, idQueryParam: unknown) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
6
+ export declare const updateInstance: (modelContainer: ModelContainer, entitiesByName: Record<string, EntityDecl>, instancesByEntityName: InstancesByEntityName, entityName: string, id: string, instance: unknown) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
7
+ export declare const deleteInstance: (modelContainer: ModelContainer, instancesByEntityName: InstancesByEntityName, entityName: string, id: string) => Promise<Result<InstanceContainer, [code: number, message: string]>>;
@@ -0,0 +1,67 @@
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 { createValidators } from "../schema/Node.js";
6
+ import { getErrorMessageForDisplay } from "../utils/error.js";
7
+ import { error, ok } from "../utils/result.js";
8
+ export const createInstance = async (modelContainer, entitiesByName, instancesByEntityName, entityName, instance, idQueryParam) => {
9
+ const entity = entitiesByName[entityName];
10
+ const validationErrors = validateEntityDecl(createValidators(instancesByEntityName), entity, instance);
11
+ if (validationErrors.length > 0) {
12
+ return error([400, validationErrors.map(getErrorMessageForDisplay).join("\n\n")]);
13
+ }
14
+ if (modelContainer.schema.localeEntity === entity && typeof idQueryParam !== "string") {
15
+ return error([400, "Missing id for locale entity"]);
16
+ }
17
+ const id = modelContainer.schema.localeEntity === entity ? idQueryParam : uuidv4();
18
+ if (modelContainer.schema.localeEntity === entity &&
19
+ instancesByEntityName[entity.name].some(instance => instance.id === id)) {
20
+ return error([400, `Duplicate id "${id}" for locale entity`]);
21
+ }
22
+ const fileName = `${id}.json`;
23
+ await writeFile(join(modelContainer.dataRootPath, entity.name, fileName), JSON.stringify(instance, undefined, 2), { encoding: "utf-8" });
24
+ const instanceContainer = {
25
+ fileName,
26
+ id,
27
+ content: instance,
28
+ };
29
+ instancesByEntityName[entity.name] = [
30
+ ...(instancesByEntityName[entity.name] ?? []),
31
+ instanceContainer,
32
+ ];
33
+ return ok(instanceContainer);
34
+ };
35
+ export const updateInstance = async (modelContainer, entitiesByName, instancesByEntityName, entityName, id, instance) => {
36
+ const instanceContainer = instancesByEntityName[entityName]?.find(instance => instance.id === id);
37
+ if (instanceContainer === undefined) {
38
+ return error([404, "Instance not found"]);
39
+ }
40
+ const entity = entitiesByName[entityName];
41
+ const validationErrors = validateEntityDecl(createValidators(instancesByEntityName), entity, instance);
42
+ if (validationErrors.length > 0) {
43
+ return error([400, validationErrors.map(getErrorMessageForDisplay).join("\n\n")]);
44
+ }
45
+ await writeFile(join(modelContainer.dataRootPath, entity.name, instanceContainer.fileName), JSON.stringify(instance, undefined, 2), { encoding: "utf-8" });
46
+ instanceContainer.content = instance;
47
+ return ok(instanceContainer);
48
+ };
49
+ export const deleteInstance = async (modelContainer, instancesByEntityName, entityName, id) => {
50
+ const instances = instancesByEntityName[entityName] ?? [];
51
+ const instanceContainerIndex = instances.findIndex(instance => instance.id === id);
52
+ const instanceContainer = instances[instanceContainerIndex];
53
+ if (instanceContainer === undefined) {
54
+ return error([404, "Instance not found"]);
55
+ }
56
+ try {
57
+ await rm(join(modelContainer.dataRootPath, entityName, instanceContainer.fileName));
58
+ instancesByEntityName[entityName] = [
59
+ ...instances.slice(0, instanceContainerIndex),
60
+ ...instances.slice(instanceContainerIndex + 1),
61
+ ];
62
+ return ok(instanceContainer);
63
+ }
64
+ catch (err) {
65
+ return error([500, `Failed to delete instance: ${err}`]);
66
+ }
67
+ };
@@ -0,0 +1,42 @@
1
+ import { SerializedDecl } from "../schema/declarations/Declaration.js";
2
+ import { InstanceContainer } from "./utils/instances.js";
3
+ export interface GetAllDeclarationsResponseBody<D extends SerializedDecl = SerializedDecl> {
4
+ declarations: {
5
+ declaration: D;
6
+ instanceCount: number;
7
+ }[];
8
+ localeEntity?: string;
9
+ }
10
+ export interface GetDeclarationResponseBody<D extends SerializedDecl = SerializedDecl> {
11
+ declaration: D;
12
+ instanceCount: number;
13
+ isLocaleEntity: boolean;
14
+ }
15
+ export interface GetAllInstancesOfEntityResponseBody {
16
+ instances: InstanceContainer[];
17
+ isLocaleEntity: boolean;
18
+ }
19
+ export interface CreateInstanceOfEntityResponseBody {
20
+ instance: InstanceContainer;
21
+ isLocaleEntity: boolean;
22
+ }
23
+ export interface GetInstanceOfEntityResponseBody {
24
+ instance: InstanceContainer;
25
+ isLocaleEntity: boolean;
26
+ }
27
+ export interface UpdateInstanceOfEntityResponseBody {
28
+ instance: InstanceContainer;
29
+ isLocaleEntity: boolean;
30
+ }
31
+ export interface DeleteInstanceOfEntityResponseBody {
32
+ instance: InstanceContainer;
33
+ isLocaleEntity: boolean;
34
+ }
35
+ export interface GetAllInstancesResponseBody {
36
+ instances: {
37
+ [entity: string]: {
38
+ id: string;
39
+ name: string;
40
+ }[];
41
+ };
42
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const discriminatorKey = "kind";
@@ -0,0 +1 @@
1
+ export const discriminatorKey = "kind";
@@ -0,0 +1,13 @@
1
+ export type ComparisonOperator = (a: number, b: number) => boolean;
2
+ export declare const lt: ComparisonOperator;
3
+ export declare const lte: ComparisonOperator;
4
+ export declare const gt: ComparisonOperator;
5
+ export declare const gte: ComparisonOperator;
6
+ export declare const eq: ComparisonOperator;
7
+ export declare const neq: ComparisonOperator;
8
+ /**
9
+ * Checks two values for value equality. This is a deep equality check that
10
+ * works for all types, including objects and arrays. For objects, it only
11
+ * compares all enumerable keys, no other properties or the prototype chain.
12
+ */
13
+ export declare const deepEqual: <T>(a: T, b: T) => boolean;
@@ -0,0 +1,24 @@
1
+ export const lt = (a, b) => a < b;
2
+ export const lte = (a, b) => a <= b;
3
+ export const gt = (a, b) => a > b;
4
+ export const gte = (a, b) => a >= b;
5
+ export const eq = (a, b) => a === b;
6
+ export const neq = (a, b) => a !== b;
7
+ /**
8
+ * Checks two values for value equality. This is a deep equality check that
9
+ * works for all types, including objects and arrays. For objects, it only
10
+ * compares all enumerable keys, no other properties or the prototype chain.
11
+ */
12
+ export const deepEqual = (a, b) => {
13
+ if (a === b) {
14
+ return true;
15
+ }
16
+ if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
17
+ const keys = Object.keys(a);
18
+ if (keys.length !== Object.keys(b).length) {
19
+ return false;
20
+ }
21
+ return keys.every(key => key in b && deepEqual(a[key], b[key]));
22
+ }
23
+ return false;
24
+ };
@@ -0,0 +1,2 @@
1
+ import { SerializedEntityDecl } from "../../schema/index.js";
2
+ export declare const getDisplayNameFromEntityInstance: (entity: SerializedEntityDecl, instance: unknown, defaultName: string, locales?: string[]) => string;
@@ -0,0 +1,31 @@
1
+ const getValueAtPath = (value, path) => {
2
+ const parts = path.split(".");
3
+ let current = value;
4
+ for (const part of parts) {
5
+ if (typeof current === "object" && current !== null && part in current) {
6
+ current = current[part];
7
+ }
8
+ else {
9
+ return undefined;
10
+ }
11
+ }
12
+ return current;
13
+ };
14
+ export const getDisplayNameFromEntityInstance = (entity, instance, defaultName, locales = []) => {
15
+ const displayNamePath = entity.displayName ?? "name";
16
+ if (typeof displayNamePath === "string") {
17
+ return getValueAtPath(instance, displayNamePath) ?? defaultName;
18
+ }
19
+ else {
20
+ const localeMapPath = displayNamePath.pathToLocaleMap ?? "translations";
21
+ const localeMap = getValueAtPath(instance, localeMapPath);
22
+ const pathInLocaleMap = displayNamePath.pathInLocaleMap ?? "name";
23
+ const availableLocales = Object.keys(localeMap ?? {});
24
+ return availableLocales.length === 0
25
+ ? defaultName
26
+ : locales.reduce((name, locale) => name ??
27
+ getValueAtPath(localeMap[locale], pathInLocaleMap), undefined) ??
28
+ getValueAtPath(localeMap[availableLocales[0]], pathInLocaleMap) ??
29
+ defaultName;
30
+ }
31
+ };
@@ -0,0 +1,6 @@
1
+ export interface InstanceContainer {
2
+ fileName: string;
3
+ id: string;
4
+ content: unknown;
5
+ }
6
+ export type InstancesByEntityName = Record<string, InstanceContainer[]>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const sortObjectKeys: (obj: Record<string, unknown>, keys: string[]) => Record<string, unknown>;
2
+ export declare const sortObjectKeysAlphabetically: (obj: Record<string, unknown>) => Record<string, unknown>;
@@ -0,0 +1,2 @@
1
+ export const sortObjectKeys = (obj, keys) => Object.fromEntries(keys.flatMap(key => (obj[key] === undefined ? [] : [[key, obj[key]]])));
2
+ export const sortObjectKeysAlphabetically = (obj) => Object.fromEntries(Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)));
@@ -0,0 +1,6 @@
1
+ export declare const splitStringParts: (str: string) => string[];
2
+ export declare const toPascalCase: (str: string) => string;
3
+ export declare const toCamelCase: (str: string) => string;
4
+ export declare const toKebabCase: (str: string) => string;
5
+ export declare const toSnakeCase: (str: string) => string;
6
+ export declare const toTitleCase: (str: string) => string;
@@ -0,0 +1,52 @@
1
+ const letterOrDigit = /[a-zA-Z0-9]/;
2
+ const uppercase = /[A-Z]/;
3
+ const lowerCaseOrDigit = /[a-z0-9]/;
4
+ const separator = /[^a-z0-9]/;
5
+ const lastChar = (str) => str[str.length - 1];
6
+ const lastElement = (arr) => arr[arr.length - 1];
7
+ const isAllUppercase = (str) => str === str.toUpperCase();
8
+ export const splitStringParts = (str) => [...str].reduce((acc, char, i, strArr) => {
9
+ if (acc.length === 0) {
10
+ return letterOrDigit.test(char) ? [char] : acc;
11
+ }
12
+ const lastPart = lastElement(acc);
13
+ if (uppercase.test(char)) {
14
+ const lastCharOfLastPart = lastChar(lastPart);
15
+ const nextChar = strArr[i + 1];
16
+ if (lastCharOfLastPart === undefined ||
17
+ (uppercase.test(lastCharOfLastPart) && (nextChar === undefined || separator.test(nextChar)))) {
18
+ return [...acc.slice(0, -1), lastPart + char];
19
+ }
20
+ else {
21
+ return [...acc, char];
22
+ }
23
+ }
24
+ if (lowerCaseOrDigit.test(char)) {
25
+ return [...acc.slice(0, -1), lastPart + char];
26
+ }
27
+ if (lastPart === "") {
28
+ return acc;
29
+ }
30
+ else {
31
+ return [...acc, ""];
32
+ }
33
+ }, []);
34
+ export const toPascalCase = (str) => splitStringParts(isAllUppercase(str) ? str.toLowerCase() : str)
35
+ .map(part => isAllUppercase(part) ? part : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
36
+ .join("");
37
+ export const toCamelCase = (str) => splitStringParts(isAllUppercase(str) ? str.toLowerCase() : str)
38
+ .map((part, i) => i === 0
39
+ ? part.toLowerCase()
40
+ : isAllUppercase(part)
41
+ ? part
42
+ : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
43
+ .join("");
44
+ export const toKebabCase = (str) => splitStringParts(str)
45
+ .map(part => part.toLowerCase())
46
+ .join("-");
47
+ export const toSnakeCase = (str) => splitStringParts(str)
48
+ .map(part => part.toLowerCase())
49
+ .join("_");
50
+ export const toTitleCase = (str) => splitStringParts(str)
51
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
52
+ .join(" ");
@@ -0,0 +1 @@
1
+ export declare const assertExhaustive: (_x: never, msg?: string) => never;
@@ -0,0 +1,3 @@
1
+ export const assertExhaustive = (_x, msg = "The switch is not exhaustive.") => {
2
+ throw new Error(msg);
3
+ };
@@ -0,0 +1,3 @@
1
+ export declare const parallelizeErrors: (errors: (Error | undefined)[]) => Error[];
2
+ export type NumerusLabel = [singular: string, plural: string];
3
+ export declare const validateLengthRangeBound: (end: "lower" | "upper", label: string | NumerusLabel, rangeBound: number | undefined, value: unknown[]) => Error | undefined;
@@ -0,0 +1,14 @@
1
+ import { gte, lte } from "./compare.js";
2
+ export const parallelizeErrors = (errors) => errors.filter(error => error !== undefined);
3
+ export const validateLengthRangeBound = (end, label, rangeBound, value) => {
4
+ if (rangeBound === undefined) {
5
+ return;
6
+ }
7
+ const [operator, description] = end === "lower" ? [gte, "least"] : [lte, "most"];
8
+ const length = value.length;
9
+ const normalizedLabel = Array.isArray(label) ? label : [label, label + "s"];
10
+ if (!operator(length, rangeBound)) {
11
+ return RangeError(`Expected at ${description} ${rangeBound} ${normalizedLabel[rangeBound === 1 ? 0 : 1]}, but got ${length} ${normalizedLabel[length === 1 ? 0 : 1]}`);
12
+ }
13
+ return;
14
+ };
@@ -0,0 +1,6 @@
1
+ export interface ArrayConstraints {
2
+ minItems?: number;
3
+ maxItems?: number;
4
+ uniqueItems?: boolean;
5
+ }
6
+ export declare const validateArrayConstraints: (constraints: ArrayConstraints, value: unknown[]) => Error[];
@@ -0,0 +1,29 @@
1
+ import { parallelizeErrors, validateLengthRangeBound } from "../utils/validation.js";
2
+ export const validateArrayConstraints = (constraints, value) => parallelizeErrors([
3
+ validateLengthRangeBound("lower", "item", constraints.minItems, value),
4
+ validateLengthRangeBound("upper", "item", constraints.maxItems, value),
5
+ constraints.uniqueItems
6
+ ? (() => {
7
+ // if (typeof this.uniqueItems === "function") {
8
+ // const seen = new Set<any>()
9
+ // for (const item of value) {
10
+ // for (const other of seen) {
11
+ // if (this.uniqueItems(item, other)) {
12
+ // return TypeError(`Duplicate item found: ${JSON.stringify(item)}`)
13
+ // }
14
+ // }
15
+ // seen.add(item)
16
+ // }
17
+ // } else {
18
+ const seen = new Set();
19
+ for (const item of value) {
20
+ if (seen.has(item)) {
21
+ return TypeError(`duplicate item found: ${JSON.stringify(item)}`);
22
+ }
23
+ seen.add(item);
24
+ }
25
+ return undefined;
26
+ // }
27
+ })()
28
+ : undefined,
29
+ ]);
@@ -0,0 +1,4 @@
1
+ export interface DateConstraints {
2
+ time?: boolean;
3
+ }
4
+ export declare const validateDateConstraints: (constraints: DateConstraints, value: string) => Error[];
@@ -0,0 +1,13 @@
1
+ import { parallelizeErrors } from "../utils/validation.js";
2
+ const datePattern = /^\d{4}-\d{2}-\d{2}$/;
3
+ const dateTimePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
4
+ const isValidISODateString = (dateString) => !isNaN(new Date(dateString).getTime());
5
+ export const validateDateConstraints = (constraints, value) => parallelizeErrors([
6
+ constraints.time === true
7
+ ? dateTimePattern.test(value) && isValidISODateString(value)
8
+ ? undefined
9
+ : TypeError(`invalid ISO 8601 date time string: ${value}`)
10
+ : datePattern.test(value) && isValidISODateString(value)
11
+ ? undefined
12
+ : TypeError(`invalid ISO 8601 date-only string: ${value}`),
13
+ ]);
@@ -0,0 +1 @@
1
+ export declare const validateLocaleIdentifier: (value: string) => Error[];
@@ -0,0 +1,7 @@
1
+ export const validateLocaleIdentifier = (value) => {
2
+ const localePattern = /^[a-z]{2,3}(-[A-Z]{2,3})?$/;
3
+ if (localePattern.test(value)) {
4
+ return [];
5
+ }
6
+ return [TypeError(`invalid locale identifier: ${JSON.stringify(value)}`)];
7
+ };
@@ -0,0 +1,12 @@
1
+ export interface NumberConstraints {
2
+ minimum?: RangeBound;
3
+ maximum?: RangeBound;
4
+ multipleOf?: number;
5
+ }
6
+ export declare const validateNumberConstraints: (constraints: NumberConstraints, value: number) => Error[];
7
+ export type RangeBound = number | {
8
+ value: number;
9
+ isExclusive: boolean;
10
+ };
11
+ export declare const validateRangeBound: (end: "lower" | "upper", rangeBound: RangeBound | undefined, value: number) => Error | undefined;
12
+ export declare const validateMultipleOf: (multipleOf: number | undefined, value: number) => Error | undefined;
@@ -0,0 +1,34 @@
1
+ import { gt, gte, lt, lte } from "../utils/compare.js";
2
+ import { parallelizeErrors } from "../utils/validation.js";
3
+ export const validateNumberConstraints = (constraints, value) => parallelizeErrors([
4
+ validateRangeBound("lower", constraints.minimum, value),
5
+ validateRangeBound("upper", constraints.maximum, value),
6
+ validateMultipleOf(constraints.multipleOf, value),
7
+ ]);
8
+ const normalizeRangeBound = (rangeBound) => typeof rangeBound === "number" ? { value: rangeBound, isExclusive: false } : rangeBound;
9
+ export const validateRangeBound = (end, rangeBound, value) => {
10
+ if (rangeBound === undefined) {
11
+ return;
12
+ }
13
+ const normalizedRangeBound = normalizeRangeBound(rangeBound);
14
+ const [operator, description] = end === "lower"
15
+ ? normalizedRangeBound.isExclusive
16
+ ? [gt, "greater than"]
17
+ : [gte, "greater than or equal"]
18
+ : normalizedRangeBound.isExclusive
19
+ ? [lt, "less than"]
20
+ : [lte, "less than or equal"];
21
+ if (operator(value, normalizedRangeBound.value)) {
22
+ return RangeError(`expected a value ${description} ${normalizedRangeBound.value}, but got ${value}`);
23
+ }
24
+ return;
25
+ };
26
+ export const validateMultipleOf = (multipleOf, value) => {
27
+ if (multipleOf === undefined) {
28
+ return;
29
+ }
30
+ if (value % multipleOf !== 0) {
31
+ return RangeError(`expected a value that is a multiple of ${multipleOf}, but got ${value}`);
32
+ }
33
+ return;
34
+ };