tsondb 0.21.2 → 0.21.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/node/git.js +1 -1
- package/dist/src/node/index.d.ts +18 -2
- package/dist/src/node/index.js +28 -1
- package/dist/src/node/renderers/ts/render.js +1 -1
- package/dist/src/node/schema/index.js +7 -0
- package/dist/src/node/schema/treeOperations/validation.js +1 -1
- package/dist/src/node/server/api/declarations.js +44 -0
- package/dist/src/node/server/utils/instanceOperations.js +1 -1
- package/dist/src/node/transaction.d.ts +5 -0
- package/dist/src/node/transaction.js +49 -1
- package/dist/src/node/utils/childInstances.js +1 -1
- package/dist/src/node/utils/databaseInMemory.d.ts +8 -0
- package/dist/src/node/utils/databaseInMemory.js +59 -0
- package/dist/src/node/utils/error.d.ts +0 -8
- package/dist/src/node/utils/error.js +1 -39
- package/dist/src/node/utils/sortOrder.d.ts +13 -0
- package/dist/src/node/utils/sortOrder.js +12 -0
- package/dist/src/node/utils/unique.d.ts +0 -4
- package/dist/src/node/utils/unique.js +1 -7
- package/dist/src/shared/api.d.ts +18 -0
- package/dist/src/shared/schema/utils/keyPath.d.ts +1 -0
- package/dist/src/shared/schema/utils/keyPath.js +30 -0
- package/dist/src/shared/schema/utils/sortOrder.d.ts +9 -0
- package/dist/src/shared/schema/utils/sortOrder.js +11 -6
- package/dist/src/shared/utils/error.d.ts +8 -0
- package/dist/src/shared/utils/error.js +39 -0
- package/dist/src/{node → shared}/utils/render.js +1 -1
- package/dist/src/shared/utils/unique.d.ts +4 -0
- package/dist/src/shared/utils/unique.js +7 -0
- package/dist/src/web/web.js +93 -14
- package/package.json +1 -1
- package/public/css/styles.css +3 -2
- /package/dist/src/{node → shared}/utils/render.d.ts +0 -0
package/dist/src/node/git.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Debug from "debug";
|
|
2
2
|
import { join } from "path";
|
|
3
|
+
import { HTTPError } from "../shared/utils/error.js";
|
|
3
4
|
import { getAllInstanceOverviewsByEntityName } from "./utils/displayName.js";
|
|
4
|
-
import { HTTPError } from "./utils/error.js";
|
|
5
5
|
import { getFileNameForId } from "./utils/files.js";
|
|
6
6
|
import { attachGitStatusToDatabaseInMemory } from "./utils/git.js";
|
|
7
7
|
const debug = Debug("tsondb:git");
|
package/dist/src/node/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InstanceContainer, InstanceContainerOverview, SingletonInstanceContainer } from "../shared/utils/instances.ts";
|
|
1
|
+
import type { InstanceContainer, InstanceContainerOverview, InstanceContent, SingletonInstanceContainer } from "../shared/utils/instances.ts";
|
|
2
2
|
import type { FormatterOptions } from "./config.ts";
|
|
3
3
|
import { Git } from "./git.js";
|
|
4
4
|
import type { Output } from "./output.ts";
|
|
@@ -14,7 +14,7 @@ export interface DefaultTSONDBTypes {
|
|
|
14
14
|
enumMap: AnyEnumMap;
|
|
15
15
|
typeAliasMap: AnyTypeAliasMap;
|
|
16
16
|
}
|
|
17
|
-
export type Entity<T extends DefaultTSONDBTypes, E extends EntityName<T>> = T["entityMap"][E];
|
|
17
|
+
export type Entity<T extends DefaultTSONDBTypes, E extends EntityName<T> = EntityName<T>> = T["entityMap"][E];
|
|
18
18
|
export type EntityName<T extends DefaultTSONDBTypes> = Extract<keyof T["entityMap"], string>;
|
|
19
19
|
export type SingletonEntity<T extends DefaultTSONDBTypes, E extends SingletonEntityName<T>> = T["singletonEntityMap"][E];
|
|
20
20
|
export type SingletonEntityName<T extends DefaultTSONDBTypes> = Extract<keyof T["singletonEntityMap"], string>;
|
|
@@ -191,6 +191,22 @@ export declare class TSONDB<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> {
|
|
|
191
191
|
* @throws {Error} when the instance could not be deleted
|
|
192
192
|
*/
|
|
193
193
|
deleteSingletonInstance<E extends SingletonEntityName<T>>(entityName: E): Promise<SingletonEntity<T, E>>;
|
|
194
|
+
/**
|
|
195
|
+
* Reorders all instances of an entity in the database.
|
|
196
|
+
*
|
|
197
|
+
* The entity must have a custom sort order via an integer field defined.
|
|
198
|
+
*
|
|
199
|
+
* @throws {Error}
|
|
200
|
+
*/
|
|
201
|
+
reorderAllInstancesOfEntity(entityName: EntityName<T> | EntityDecl<EntityName<T>>, newOrder: string[]): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* Reorders an instance of an entity in the database.
|
|
204
|
+
*
|
|
205
|
+
* The entity must have a custom sort order via an integer field defined.
|
|
206
|
+
*
|
|
207
|
+
* @throws {Error}
|
|
208
|
+
*/
|
|
209
|
+
reorderInstanceOfEntity(entityName: EntityName<T> | EntityDecl<EntityName<T>>, instanceId: string, newIndex: number): Promise<[id: string, oldContent: InstanceContent, newContent: InstanceContent][]>;
|
|
194
210
|
/**
|
|
195
211
|
* Retrieves an instance of the specified entity by its identifier.
|
|
196
212
|
*/
|
package/dist/src/node/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { stderr } from "node:process";
|
|
|
11
11
|
import { styleText } from "node:util";
|
|
12
12
|
import { simpleGit } from "simple-git";
|
|
13
13
|
import { NodeKind } from "../shared/schema/Node.js";
|
|
14
|
+
import { countError, countErrors, getErrorMessageForDisplay, HTTPError, wrapErrorsIfAny, } from "../shared/utils/error.js";
|
|
14
15
|
import { parallelizeErrors } from "../shared/utils/validation.js";
|
|
15
16
|
import { Git } from "./git.js";
|
|
16
17
|
import { getDisplayName, getDisplayNameWithId } from "./schema/detached.js";
|
|
@@ -24,7 +25,6 @@ import { checkCustomConstraintsForAllEntities } from "./utils/customConstraints.
|
|
|
24
25
|
import { DatabaseInMemory } from "./utils/databaseInMemory.js";
|
|
25
26
|
import { applyStepsToDisk } from "./utils/databaseOnDisk.js";
|
|
26
27
|
import { getAllInstanceOverviewsByEntityName, getDisplayNameFromEntityInstance, getInstanceOverview, getInstanceOverviewsByEntityName, } from "./utils/displayName.js";
|
|
27
|
-
import { countError, countErrors, getErrorMessageForDisplay, HTTPError, wrapErrorsIfAny, } from "./utils/error.js";
|
|
28
28
|
import { formatInstance, getFileNameForId, getSingletonFileName, readInstance, readSingletonInstance, writeInstance, writeSingletonInstance, } from "./utils/files.js";
|
|
29
29
|
import { attachGitStatusToDatabaseInMemory } from "./utils/git.js";
|
|
30
30
|
import { getReferencesToInstances, isReferencedByOtherInstances, } from "./utils/references.js";
|
|
@@ -509,6 +509,33 @@ export class TSONDB {
|
|
|
509
509
|
txn.deleteSingletonInstance(this.#schema.getSingletonEntity(entityName)));
|
|
510
510
|
return res.content;
|
|
511
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Reorders all instances of an entity in the database.
|
|
514
|
+
*
|
|
515
|
+
* The entity must have a custom sort order via an integer field defined.
|
|
516
|
+
*
|
|
517
|
+
* @throws {Error}
|
|
518
|
+
*/
|
|
519
|
+
async reorderAllInstancesOfEntity(entityName, newOrder) {
|
|
520
|
+
return this.runTransaction(txn => [
|
|
521
|
+
txn.reorderAllInstancesOfEntity(
|
|
522
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
523
|
+
typeof entityName === "string" ? this.#schema.getEntity(entityName) : entityName, newOrder),
|
|
524
|
+
undefined,
|
|
525
|
+
]);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Reorders an instance of an entity in the database.
|
|
529
|
+
*
|
|
530
|
+
* The entity must have a custom sort order via an integer field defined.
|
|
531
|
+
*
|
|
532
|
+
* @throws {Error}
|
|
533
|
+
*/
|
|
534
|
+
async reorderInstanceOfEntity(entityName, instanceId, newIndex) {
|
|
535
|
+
return this.runTransaction(txn => txn.reorderInstanceOfEntity(
|
|
536
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
537
|
+
typeof entityName === "string" ? this.#schema.getEntity(entityName) : entityName, instanceId, newIndex));
|
|
538
|
+
}
|
|
512
539
|
/**
|
|
513
540
|
* Retrieves an instance of the specified entity by its identifier.
|
|
514
541
|
*/
|
|
@@ -5,6 +5,7 @@ import { EOL } from "node:os";
|
|
|
5
5
|
import { dirname, relative } from "node:path";
|
|
6
6
|
import { ENUM_DISCRIMINATOR_KEY } from "../../../shared/schema/declarations/EnumDecl.js";
|
|
7
7
|
import { NodeKind } from "../../../shared/schema/Node.js";
|
|
8
|
+
import { combineSyntaxes, emptyRenderResult, getIndentation, indent, prefixLines, syntax, } from "../../../shared/utils/render.js";
|
|
8
9
|
import { extractParameterTypeNamesFromMessage, mapParameterTypeNames, } from "../../../shared/utils/translation.js";
|
|
9
10
|
import { asDecl } from "../../schema/dsl/declarations/Decl.js";
|
|
10
11
|
import { addEphemeralUUIDToType, createEntityIdentifierTypeAsDecl, isEntityDecl, isEntityDeclWithParentReference, } from "../../schema/dsl/declarations/EntityDecl.js";
|
|
@@ -16,7 +17,6 @@ import { isObjectType } from "../../schema/dsl/types/ObjectType.js";
|
|
|
16
17
|
import { getTypeOfKey } from "../../schema/dsl/types/TranslationObjectType.js";
|
|
17
18
|
import { flatMapAuxiliaryDecls, isFinalChildEntitiesType } from "../../schema/helpers.js";
|
|
18
19
|
import { ensureSpecialDirStart } from "../../utils/path.js";
|
|
19
|
-
import { combineSyntaxes, emptyRenderResult, getIndentation, indent, prefixLines, syntax, } from "../../utils/render.js";
|
|
20
20
|
const defaultOptions = {
|
|
21
21
|
indentation: 2,
|
|
22
22
|
objectTypeKeyword: "interface",
|
|
@@ -440,6 +440,13 @@ const checkSortOrders = (declarations) => {
|
|
|
440
440
|
if (!isValidSortOrderType(type)) {
|
|
441
441
|
throw TypeError(`type at key path "${renderKeyPath(sortOrder.keyPath)}" is not a valid sort order type (string, integer, or float) in entity "${decl.name}"`);
|
|
442
442
|
}
|
|
443
|
+
if (sortOrder.isIndex &&
|
|
444
|
+
(!isIntegerType(type) ||
|
|
445
|
+
type.maximum !== undefined ||
|
|
446
|
+
(typeof type.minimum === "object" && type.minimum.isExclusive) ||
|
|
447
|
+
type.multipleOf !== undefined)) {
|
|
448
|
+
throw TypeError(`type at key path ${renderKeyPath(sortOrder.keyPath)} is not a valid index sort order type (integer, optional inclusive minimum, no other constraints) in entity "${decl.name}"`);
|
|
449
|
+
}
|
|
443
450
|
}
|
|
444
451
|
else {
|
|
445
452
|
// const entityMapType = getType(decl.type.value, sortOrder.entityMapKeyPath)
|
|
@@ -4,6 +4,7 @@ import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
|
4
4
|
import { MessageError, parseMessage, validate } from "messageformat";
|
|
5
5
|
import { ENUM_DISCRIMINATOR_KEY } from "../../../shared/schema/declarations/EnumDecl.js";
|
|
6
6
|
import { NodeKind } from "../../../shared/schema/Node.js";
|
|
7
|
+
import { wrapErrorsIfAny } from "../../../shared/utils/error.js";
|
|
7
8
|
import { extendsParameterTypes, extractParameterTypeNamesFromMessage, } from "../../../shared/utils/translation.js";
|
|
8
9
|
import { parallelizeErrors } from "../../../shared/utils/validation.js";
|
|
9
10
|
import { validateArrayConstraints } from "../../../shared/validation/array.js";
|
|
@@ -12,7 +13,6 @@ import { validateNumberConstraints } from "../../../shared/validation/number.js"
|
|
|
12
13
|
import { validateObjectConstraints, validateObjectRangeConstraints, validateUnknownKeys, } from "../../../shared/validation/object.js";
|
|
13
14
|
import { validateStringConstraints } from "../../../shared/validation/string.js";
|
|
14
15
|
import {} from "../../utils/databaseInMemory.js";
|
|
15
|
-
import { wrapErrorsIfAny } from "../../utils/error.js";
|
|
16
16
|
import { entity, json, key } from "../../utils/errorFormatting.js";
|
|
17
17
|
import { getTypeArgumentsRecord } from "../dsl/declarations/Decl.js";
|
|
18
18
|
import { createEntityIdentifierType } from "../dsl/declarations/EntityDecl.js";
|
|
@@ -148,6 +148,50 @@ declarationsApi.post("/:name/instances", async (req, res) => {
|
|
|
148
148
|
sendErrorResponse(res, err);
|
|
149
149
|
}
|
|
150
150
|
});
|
|
151
|
+
declarationsApi.put("/:name/instances/reorder", async (req, res) => {
|
|
152
|
+
const decl = req.db.schema.getResolvedDeclaration(req.params.name);
|
|
153
|
+
if (decl === undefined) {
|
|
154
|
+
res.status(404).send(`Declaration "${req.params.name}" not found`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!isEntityDecl(decl)) {
|
|
158
|
+
res.status(400).send(`Declaration "${decl.name}" is not an entity`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const requestBody = req.body;
|
|
163
|
+
await req.db.reorderAllInstancesOfEntity(decl, requestBody.order);
|
|
164
|
+
const body = {
|
|
165
|
+
instances: req.db.getAllInstanceOverviewsOfEntity(decl.name),
|
|
166
|
+
};
|
|
167
|
+
res.json(body);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
sendErrorResponse(res, err);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
declarationsApi.put("/:name/instances/reorder/:id", async (req, res) => {
|
|
174
|
+
const decl = req.db.schema.getResolvedDeclaration(req.params.name);
|
|
175
|
+
if (decl === undefined) {
|
|
176
|
+
res.status(404).send(`Declaration "${req.params.name}" not found`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!isEntityDecl(decl)) {
|
|
180
|
+
res.status(400).send(`Declaration "${decl.name}" is not an entity`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const requestBody = req.body;
|
|
185
|
+
await req.db.reorderInstanceOfEntity(decl, req.params.id, requestBody.targetIndex);
|
|
186
|
+
const body = {
|
|
187
|
+
instances: req.db.getAllInstanceOverviewsOfEntity(decl.name),
|
|
188
|
+
};
|
|
189
|
+
res.json(body);
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
sendErrorResponse(res, err);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
151
195
|
declarationsApi.get("/:name/instances/:id", (req, res) => {
|
|
152
196
|
const decl = req.db.schema.getResolvedDeclaration(req.params.name);
|
|
153
197
|
if (decl === undefined) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { HTTPError } from "../../../shared/utils/error.js";
|
|
1
2
|
import { getChildInstances, saveInstanceTree } from "../../utils/childInstances.js";
|
|
2
|
-
import { HTTPError } from "../../utils/error.js";
|
|
3
3
|
export const createInstance = async (db, instance, idQueryParam) => {
|
|
4
4
|
const getEntity = db.schema.getEntity.bind(db.schema);
|
|
5
5
|
const entity = getEntity(instance.entityName);
|
|
@@ -52,6 +52,11 @@ export declare class Transaction<EM extends AnyEntityMap = RegisteredEntityMap,
|
|
|
52
52
|
createSingletonInstance(entity: SingletonEntityDecl<Extract<keyof SEM, string>>, instanceContent: InstanceContent): [Transaction<EM, SEM>, SingletonInstanceContainer];
|
|
53
53
|
updateSingletonInstance(entity: SingletonEntityDecl<Extract<keyof SEM, string>>, instanceContent: InstanceContent): [Transaction<EM, SEM>, SingletonInstanceContainer];
|
|
54
54
|
deleteSingletonInstance(entity: SingletonEntityDecl<Extract<keyof SEM, string>>): [Transaction<EM, SEM>, SingletonInstanceContainer];
|
|
55
|
+
reorderAllInstancesOfEntity(entity: EntityDecl<Extract<keyof EM, string>>, newOrder: string[]): Transaction<EM, SEM>;
|
|
56
|
+
reorderInstanceOfEntity(entity: EntityDecl<Extract<keyof EM, string>>, instanceId: string, newIndex: number): [
|
|
57
|
+
Transaction<EM, SEM>,
|
|
58
|
+
changedInstances: [id: string, oldContent: InstanceContent, newContent: InstanceContent][]
|
|
59
|
+
];
|
|
55
60
|
/**
|
|
56
61
|
* @package
|
|
57
62
|
*/
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { isEmpty } from "@elyukai/utils/array/nonEmpty";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { getSortOrderKeyPathForReorder } from "../shared/schema/utils/sortOrder.js";
|
|
4
|
+
import { getErrorMessageForDisplay, HTTPError } from "../shared/utils/error.js";
|
|
2
5
|
import {} from "./schema/dsl/index.js";
|
|
3
6
|
import {} from "./utils/databaseInMemory.js";
|
|
4
|
-
import { getErrorMessageForDisplay, HTTPError } from "./utils/error.js";
|
|
5
7
|
import { isReferencedByOtherInstances, updateReferencesToInstances, } from "./utils/references.js";
|
|
8
|
+
import { getSortOrderStartIndex } from "./utils/sortOrder.js";
|
|
6
9
|
export const createNewId = () => randomUUID();
|
|
7
10
|
/**
|
|
8
11
|
* @throws {HTTPError}
|
|
@@ -208,6 +211,51 @@ export class Transaction {
|
|
|
208
211
|
{ content: oldInstance },
|
|
209
212
|
];
|
|
210
213
|
}
|
|
214
|
+
reorderAllInstancesOfEntity(entity, newOrder) {
|
|
215
|
+
const { data, steps } = this.#values;
|
|
216
|
+
const positionKeyPath = getSortOrderKeyPathForReorder(entity);
|
|
217
|
+
const startIndex = getSortOrderStartIndex(entity);
|
|
218
|
+
const newData = data.reorderAllInstancesOfEntity(entity.name, positionKeyPath, newOrder, startIndex);
|
|
219
|
+
const newSteps = newData
|
|
220
|
+
.getAllInstanceContainersOfEntity(entity.name)
|
|
221
|
+
.map(instance => ({
|
|
222
|
+
kind: "update",
|
|
223
|
+
entity,
|
|
224
|
+
instanceId: instance.id,
|
|
225
|
+
instanceContent: instance.content,
|
|
226
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
227
|
+
oldInstance: data.getInstanceContainerOfEntityById(entity.name, instance.id).content,
|
|
228
|
+
}));
|
|
229
|
+
return new Transaction({
|
|
230
|
+
...this.#values,
|
|
231
|
+
data: newData,
|
|
232
|
+
steps: [...steps, ...newSteps],
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
reorderInstanceOfEntity(entity, instanceId, newIndex) {
|
|
236
|
+
const { data, steps } = this.#values;
|
|
237
|
+
const startIndex = getSortOrderStartIndex(entity);
|
|
238
|
+
const positionKeyPath = getSortOrderKeyPathForReorder(entity);
|
|
239
|
+
const [newData, changedInstances] = data.reorderInstanceOfEntity(entity.name, positionKeyPath, instanceId, newIndex, startIndex);
|
|
240
|
+
if (isEmpty(changedInstances)) {
|
|
241
|
+
return [this, changedInstances];
|
|
242
|
+
}
|
|
243
|
+
const newSteps = changedInstances.map(([id, oldContent, newContent]) => ({
|
|
244
|
+
kind: "update",
|
|
245
|
+
entity,
|
|
246
|
+
instanceId: id,
|
|
247
|
+
instanceContent: newContent,
|
|
248
|
+
oldInstance: oldContent,
|
|
249
|
+
}));
|
|
250
|
+
return [
|
|
251
|
+
new Transaction({
|
|
252
|
+
...this.#values,
|
|
253
|
+
data: newData,
|
|
254
|
+
steps: [...steps, ...newSteps],
|
|
255
|
+
}),
|
|
256
|
+
changedInstances,
|
|
257
|
+
];
|
|
258
|
+
}
|
|
211
259
|
/**
|
|
212
260
|
* @package
|
|
213
261
|
*/
|
|
@@ -2,13 +2,13 @@ import { hasKey } from "@elyukai/utils/object";
|
|
|
2
2
|
import { error, isError, ok } from "@elyukai/utils/result";
|
|
3
3
|
import { createEnumCaseValue, ENUM_DISCRIMINATOR_KEY, } from "../../shared/schema/declarations/EnumDecl.js";
|
|
4
4
|
import { NodeKind } from "../../shared/schema/Node.js";
|
|
5
|
+
import { HTTPError } from "../../shared/utils/error.js";
|
|
5
6
|
import {} from "../schema/dsl/index.js";
|
|
6
7
|
import { isChildEntitiesType } from "../schema/dsl/types/ChildEntitiesType.js";
|
|
7
8
|
import { Case } from "../schema/generatedTypeHelpers.js";
|
|
8
9
|
import { isEntityDeclWithParentReference, isEnumDecl, isIncludeIdentifierType, isReferenceIdentifierType, } from "../schema/guards.js";
|
|
9
10
|
import { reduceNodes } from "../schema/helpers.js";
|
|
10
11
|
import {} from "./databaseInMemory.js";
|
|
11
|
-
import { HTTPError } from "./error.js";
|
|
12
12
|
const isParentReferenceReferencingParent = (value, parentEntityName, parentId) => {
|
|
13
13
|
if (typeof value === "object" && value !== null && hasKey(value, ENUM_DISCRIMINATOR_KEY)) {
|
|
14
14
|
return (value[ENUM_DISCRIMINATOR_KEY] === parentEntityName &&
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Dictionary } from "@elyukai/utils/dictionary";
|
|
2
|
+
import { type KeyPath } from "../../shared/schema/utils/keyPath.ts";
|
|
2
3
|
import type { InstanceContainer, InstanceContent, SingletonInstanceContainer } from "../../shared/utils/instances.ts";
|
|
3
4
|
import type { EntityDecl, SingletonEntityDecl } from "../schema/dsl/index.ts";
|
|
4
5
|
import { type AnyChildEntityMap, type AnyEntityMap, type AnySingletonEntityMap, type GetEntityByName, type IdArgsVariant, type RegisteredEntityMap, type RegisteredSingletonEntityMap } from "../schema/generatedTypeHelpers.ts";
|
|
@@ -21,6 +22,8 @@ export declare class DatabaseInMemory<EM extends AnyEntityMap = RegisteredEntity
|
|
|
21
22
|
hasInstanceOfSingletonEntity(entityName: Extract<keyof SEM, string>): boolean;
|
|
22
23
|
forEachInstance(fn: (entityName: Extract<keyof EM, string>, instance: InstanceContainer) => Promise<void>, async: true): Promise<void>;
|
|
23
24
|
forEachInstance(fn: (entityName: Extract<keyof EM, string>, instance: InstanceContainer) => void, async?: false): void;
|
|
25
|
+
forEachInstanceOfEntity(entityName: Extract<keyof EM, string>, fn: (instance: InstanceContainer) => Promise<void>, async: true): Promise<void>;
|
|
26
|
+
forEachInstanceOfEntity(entityName: Extract<keyof EM, string>, fn: (instance: InstanceContainer) => void, async?: false): void;
|
|
24
27
|
forEachSingletonInstance(fn: (entityName: Extract<keyof SEM, string>, instance: SingletonInstanceContainer) => Promise<void>, async: true): Promise<void>;
|
|
25
28
|
forEachSingletonInstance(fn: (entityName: Extract<keyof SEM, string>, instance: SingletonInstanceContainer) => void, async?: false): void;
|
|
26
29
|
countInstancesOfEntity(entityName: Extract<keyof EM, string>): number;
|
|
@@ -34,4 +37,9 @@ export declare class DatabaseInMemory<EM extends AnyEntityMap = RegisteredEntity
|
|
|
34
37
|
map(fn: (instances: Dictionary<InstanceContainer>, entityName: Extract<keyof EM, string>) => Dictionary<InstanceContainer>): DatabaseInMemory<EM, SEM>;
|
|
35
38
|
mapSingletons(fn: (instance: SingletonInstanceContainer, entityName: Extract<keyof SEM, string>) => SingletonInstanceContainer): DatabaseInMemory<EM, SEM>;
|
|
36
39
|
getEntityNameOfInstanceId(id: string): Extract<keyof EM, string> | undefined;
|
|
40
|
+
reorderAllInstancesOfEntity(entityName: Extract<keyof EM, string>, keyPath: KeyPath, newOrder: string[], startIndex?: number): DatabaseInMemory<EM, SEM>;
|
|
41
|
+
reorderInstanceOfEntity(entityName: Extract<keyof EM, string>, keyPath: KeyPath, instanceId: string, newIndex: number, startIndex?: number): [
|
|
42
|
+
DatabaseInMemory<EM, SEM>,
|
|
43
|
+
changedInstances: [id: string, oldContent: InstanceContent, newContent: InstanceContent][]
|
|
44
|
+
];
|
|
37
45
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import { reorder } from "@elyukai/utils/array/modify";
|
|
1
2
|
import { mapAsync } from "@elyukai/utils/async";
|
|
2
3
|
import { Dictionary } from "@elyukai/utils/dictionary";
|
|
3
4
|
import { deepEqual } from "@elyukai/utils/equality";
|
|
5
|
+
import { on } from "@elyukai/utils/function";
|
|
4
6
|
import { Lazy } from "@elyukai/utils/lazy";
|
|
7
|
+
import { compareNumber } from "@elyukai/utils/ordering";
|
|
5
8
|
import child_process from "node:child_process";
|
|
6
9
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
7
10
|
import { basename, extname, join } from "node:path";
|
|
8
11
|
import { platform } from "node:process";
|
|
9
12
|
import { promisify } from "node:util";
|
|
13
|
+
import { getValueAtKeyPathIfDefined, setValueAtKeyPath, } from "../../shared/schema/utils/keyPath.js";
|
|
14
|
+
import { HTTPError } from "../../shared/utils/error.js";
|
|
10
15
|
import { normalizedIdArgs, } from "../schema/generatedTypeHelpers.js";
|
|
11
16
|
import { getPathToSingletonInstance, readSingletonInstance } from "./files.js";
|
|
12
17
|
const exec = promisify(child_process.exec);
|
|
@@ -102,6 +107,15 @@ export class DatabaseInMemory {
|
|
|
102
107
|
});
|
|
103
108
|
}
|
|
104
109
|
}
|
|
110
|
+
forEachInstanceOfEntity(entityName, ...args) {
|
|
111
|
+
const [fn, async] = args;
|
|
112
|
+
if (async) {
|
|
113
|
+
return this.#data.get(entityName)?.forEach(fn, true);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.#data.get(entityName)?.forEach(fn);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
105
119
|
forEachSingletonInstance(...args) {
|
|
106
120
|
const [fn, async] = args;
|
|
107
121
|
if (async) {
|
|
@@ -178,4 +192,49 @@ export class DatabaseInMemory {
|
|
|
178
192
|
getEntityNameOfInstanceId(id) {
|
|
179
193
|
return this.#data.findKey(instances => instances.has(id));
|
|
180
194
|
}
|
|
195
|
+
reorderAllInstancesOfEntity(entityName, keyPath, newOrder, startIndex = 0) {
|
|
196
|
+
const instancesWithPositions = this.getAllInstanceContainersOfEntity(entityName).map(instance => [instance.id, getValueAtKeyPathIfDefined(instance.content, keyPath)]);
|
|
197
|
+
if (newOrder.length !== instancesWithPositions.length) {
|
|
198
|
+
throw new HTTPError(400, `New order of instances (${newOrder.length.toFixed()} elements) does not match the amount of instances in entity "${entityName}" (${instancesWithPositions.length.toFixed()} instances)`);
|
|
199
|
+
}
|
|
200
|
+
if (instancesWithPositions.some(([id]) => !newOrder.includes(id))) {
|
|
201
|
+
throw new HTTPError(400, `Identifiers in the new order do not match existing instance identifiers for entity "${entityName}"`);
|
|
202
|
+
}
|
|
203
|
+
return new DatabaseInMemory(instancesWithPositions.reduce((db, [id]) => db.modify(entityName, instances => instances?.modify(id, instanceContainer => instanceContainer === undefined
|
|
204
|
+
? undefined
|
|
205
|
+
: {
|
|
206
|
+
...instanceContainer,
|
|
207
|
+
content: setValueAtKeyPath(instanceContainer.content, keyPath, newOrder.indexOf(id) + startIndex),
|
|
208
|
+
})), this.#data), this.#singletonData);
|
|
209
|
+
}
|
|
210
|
+
reorderInstanceOfEntity(entityName, keyPath, instanceId, newIndex, startIndex = 0) {
|
|
211
|
+
const instancesWithPositions = this.getAllInstanceContainersOfEntity(entityName).map(instance => [instance.id, getValueAtKeyPathIfDefined(instance.content, keyPath)]);
|
|
212
|
+
const normalizedInstancesWithPositions = instancesWithPositions.toSorted(on(([_, position]) => position, compareNumber));
|
|
213
|
+
const currentIndex = normalizedInstancesWithPositions.findIndex(([id]) => id === instanceId);
|
|
214
|
+
if (currentIndex === -1) {
|
|
215
|
+
throw new HTTPError(400, `Instance with ID "${instanceId}" does not exist in entity "${entityName}"`);
|
|
216
|
+
}
|
|
217
|
+
const newSafeIndex = Math.max(0, Math.min(newIndex, normalizedInstancesWithPositions.length - 1));
|
|
218
|
+
if (currentIndex === newSafeIndex) {
|
|
219
|
+
return [this, []];
|
|
220
|
+
}
|
|
221
|
+
const reorderedInstances = reorder(normalizedInstancesWithPositions, currentIndex, newSafeIndex);
|
|
222
|
+
const changedInstances = [];
|
|
223
|
+
return [
|
|
224
|
+
new DatabaseInMemory(reorderedInstances.reduce((db, [id], index) => db.modify(entityName, instances => instances?.modify(id, instanceContainer => {
|
|
225
|
+
if (instanceContainer === undefined) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
const oldContent = instanceContainer.content;
|
|
229
|
+
const currentPosition = getValueAtKeyPathIfDefined(oldContent, keyPath);
|
|
230
|
+
if (currentPosition === index + startIndex) {
|
|
231
|
+
return instanceContainer;
|
|
232
|
+
}
|
|
233
|
+
const newContent = setValueAtKeyPath(oldContent, keyPath, index + startIndex);
|
|
234
|
+
changedInstances.push([id, oldContent, newContent]);
|
|
235
|
+
return { ...instanceContainer, content: newContent };
|
|
236
|
+
})), this.#data), this.#singletonData),
|
|
237
|
+
changedInstances,
|
|
238
|
+
];
|
|
239
|
+
}
|
|
181
240
|
}
|
|
@@ -1,10 +1,2 @@
|
|
|
1
1
|
import type { Response } from "express";
|
|
2
|
-
export declare const getErrorMessageForDisplay: (error: Error, indentation?: number) => string;
|
|
3
|
-
export declare const countError: (error: Error) => number;
|
|
4
|
-
export declare const countErrors: (errors: Error[]) => number;
|
|
5
|
-
export declare const wrapErrorsIfAny: (message: string, errors: Error[]) => AggregateError | undefined;
|
|
6
|
-
export declare class HTTPError extends Error {
|
|
7
|
-
code: number;
|
|
8
|
-
constructor(code: number, message: string);
|
|
9
|
-
}
|
|
10
2
|
export declare const sendErrorResponse: (res: Response, error: unknown) => void;
|
|
@@ -1,42 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { UniqueConstraintError } from "./unique.js";
|
|
3
|
-
export const getErrorMessageForDisplay = (error, indentation = 2) => {
|
|
4
|
-
if (error instanceof AggregateError) {
|
|
5
|
-
return `${error.message}\n${applyIndentation(1, error.errors
|
|
6
|
-
.filter(subError => subError instanceof Error)
|
|
7
|
-
.map(subError => getErrorMessageForDisplay(subError, indentation))
|
|
8
|
-
.join("\n"), indentation)}`;
|
|
9
|
-
}
|
|
10
|
-
else if (error.cause instanceof Error) {
|
|
11
|
-
return `${error.message}\n${applyIndentation(1, getErrorMessageForDisplay(error.cause, indentation), indentation)}`;
|
|
12
|
-
}
|
|
13
|
-
else if (error instanceof UniqueConstraintError) {
|
|
14
|
-
return `${error.message}\n${applyIndentation(1, error.parts.join("\n"), indentation)}`;
|
|
15
|
-
}
|
|
16
|
-
else {
|
|
17
|
-
return error.message;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
export const countError = (error) => error instanceof AggregateError
|
|
21
|
-
? countErrors(error.errors.filter(subError => subError instanceof Error))
|
|
22
|
-
: error.cause instanceof Error
|
|
23
|
-
? countError(error.cause)
|
|
24
|
-
: 1;
|
|
25
|
-
export const countErrors = (errors) => errors.reduce((count, error) => count + countError(error), 0);
|
|
26
|
-
export const wrapErrorsIfAny = (message, errors) => {
|
|
27
|
-
if (errors.length === 0) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
return new AggregateError(errors, message);
|
|
31
|
-
};
|
|
32
|
-
export class HTTPError extends Error {
|
|
33
|
-
code;
|
|
34
|
-
constructor(code, message) {
|
|
35
|
-
super(message);
|
|
36
|
-
this.code = code;
|
|
37
|
-
this.name = "HTTPError";
|
|
38
|
-
}
|
|
39
|
-
}
|
|
1
|
+
import { getErrorMessageForDisplay, HTTPError } from "../../shared/utils/error.js";
|
|
40
2
|
export const sendErrorResponse = (res, error) => {
|
|
41
3
|
res.set("Content-Type", "text/plain");
|
|
42
4
|
if (error instanceof HTTPError) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Lazy } from "@elyukai/utils/lazy";
|
|
2
|
+
import { type SortOrder } from "../../shared/schema/utils/sortOrder.ts";
|
|
3
|
+
import type { IntegerType, Type } from "../schema/dsl/index.ts";
|
|
4
|
+
export declare const getSortOrderType: (decl: {
|
|
5
|
+
name: string;
|
|
6
|
+
type: Lazy<Type>;
|
|
7
|
+
sortOrder?: SortOrder;
|
|
8
|
+
}) => IntegerType;
|
|
9
|
+
export declare const getSortOrderStartIndex: (decl: {
|
|
10
|
+
name: string;
|
|
11
|
+
type: Lazy<Type>;
|
|
12
|
+
sortOrder?: SortOrder;
|
|
13
|
+
}) => number;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getSortOrderKeyPathForReorder, } from "../../shared/schema/utils/sortOrder.js";
|
|
2
|
+
import { findTypeAtPath } from "../schema/helpers.js";
|
|
3
|
+
export const getSortOrderType = (decl) => {
|
|
4
|
+
const keyPath = getSortOrderKeyPathForReorder(decl);
|
|
5
|
+
return findTypeAtPath(decl.type.value, keyPath, { throwOnPathMismatch: true });
|
|
6
|
+
};
|
|
7
|
+
export const getSortOrderStartIndex = (decl) => {
|
|
8
|
+
const sortOrderType = getSortOrderType(decl);
|
|
9
|
+
return typeof sortOrderType.minimum === "number"
|
|
10
|
+
? sortOrderType.minimum
|
|
11
|
+
: (sortOrderType.minimum?.value ?? 0);
|
|
12
|
+
};
|
|
@@ -2,10 +2,6 @@ import { type Result } from "@elyukai/utils/result";
|
|
|
2
2
|
import type { InstanceContainer, InstanceContainerOverview } from "../../shared/utils/instances.ts";
|
|
3
3
|
import type { EntityDecl } from "../schema/dsl/index.ts";
|
|
4
4
|
import { type DatabaseInMemory } from "./databaseInMemory.ts";
|
|
5
|
-
export declare class UniqueConstraintError extends Error {
|
|
6
|
-
readonly parts: string[];
|
|
7
|
-
constructor(message: string, parts: string[]);
|
|
8
|
-
}
|
|
9
5
|
/**
|
|
10
6
|
* Checks all unique constraints for the provided entity and its instances.
|
|
11
7
|
*
|
|
@@ -3,14 +3,8 @@ import { flatCombine } from "@elyukai/utils/array/transformations";
|
|
|
3
3
|
import { deepEqual } from "@elyukai/utils/equality";
|
|
4
4
|
import { error, isError, mapError, ok } from "@elyukai/utils/result";
|
|
5
5
|
import { getValueAtKeyPath, getValueAtKeyPathIfDefined, renderKeyPath, } from "../../shared/schema/utils/keyPath.js";
|
|
6
|
+
import { UniqueConstraintError } from "../../shared/utils/unique.js";
|
|
6
7
|
import {} from "./databaseInMemory.js";
|
|
7
|
-
export class UniqueConstraintError extends Error {
|
|
8
|
-
parts;
|
|
9
|
-
constructor(message, parts) {
|
|
10
|
-
super(message);
|
|
11
|
-
this.parts = parts;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
8
|
const printUniqueConstraint = (constraint, values) => (Array.isArray(constraint) ? constraint : [constraint])
|
|
15
9
|
.map((elem, i) => "keyPath" in elem
|
|
16
10
|
? renderKeyPath(elem.keyPath) +
|
package/dist/src/shared/api.d.ts
CHANGED
|
@@ -58,6 +58,24 @@ export interface CreateInstanceOfEntityResponseBody {
|
|
|
58
58
|
instance: InstanceContainer;
|
|
59
59
|
isLocaleEntity: boolean;
|
|
60
60
|
}
|
|
61
|
+
export interface ReorderAllInstancesOfEntityRequestBody {
|
|
62
|
+
/**
|
|
63
|
+
* All identifiers in the requested order.
|
|
64
|
+
*/
|
|
65
|
+
order: string[];
|
|
66
|
+
}
|
|
67
|
+
export interface ReorderAllInstancesOfEntityResponseBody {
|
|
68
|
+
instances: InstanceContainerOverview[];
|
|
69
|
+
}
|
|
70
|
+
export interface ReorderInstanceOfEntityRequestBody {
|
|
71
|
+
/**
|
|
72
|
+
* The 0-based index of the instance in the new order.
|
|
73
|
+
*/
|
|
74
|
+
targetIndex: number;
|
|
75
|
+
}
|
|
76
|
+
export interface ReorderInstanceOfEntityResponseBody {
|
|
77
|
+
instances: InstanceContainerOverview[];
|
|
78
|
+
}
|
|
61
79
|
export interface GetInstanceOfEntityResponseBody {
|
|
62
80
|
instance: InstanceContainer;
|
|
63
81
|
isLocaleEntity: boolean;
|
|
@@ -15,3 +15,4 @@ export declare const renderParsedKeyPath: (keyPath: ParsedKeyPath) => string;
|
|
|
15
15
|
export declare const getAtKeyPath: <T>(value: T, previousPath: ParsedKeyPath, remainingKeyPath: ParsedKeyPath, throwOnPathMismatch: boolean, fArray: (value: T, index: number) => Result<T, (previousPath: string) => string>, fObject: (value: T, key: string) => Result<T, (previousPath: string) => string>, ...fs: ((value: T, key: KeyPathElement) => Result<[T, skipKey?: boolean], void>)[]) => T;
|
|
16
16
|
export declare const getValueAtKeyPath: (value: unknown, keyPath: KeyPath, throwOnPathMismatch?: boolean) => unknown;
|
|
17
17
|
export declare const getValueAtKeyPathIfDefined: (value: unknown, keyPath: KeyPath) => unknown;
|
|
18
|
+
export declare const setValueAtKeyPath: (value: unknown, keyPath: KeyPath, newValue: unknown) => unknown;
|
|
@@ -64,3 +64,33 @@ export const getValueAtKeyPath = (value, keyPath, throwOnPathMismatch = false) =
|
|
|
64
64
|
: error(previousPath => `Object at key path "${previousPath}" does not contain the key ${name}.`)
|
|
65
65
|
: error(previousPath => `Key path "${previousPath}" does not contain an object.`));
|
|
66
66
|
export const getValueAtKeyPathIfDefined = (value, keyPath) => trySafe(() => getValueAtKeyPath(value, keyPath, true));
|
|
67
|
+
const _setValueAtKeyPath = (value, keyPath, newValue) => {
|
|
68
|
+
if (isNotEmpty(keyPath)) {
|
|
69
|
+
const [key, ...remainingPath] = keyPath;
|
|
70
|
+
switch (key.kind) {
|
|
71
|
+
case "index":
|
|
72
|
+
if (!Array.isArray(value)) {
|
|
73
|
+
throw new TypeError(`Key path "${renderParsedKeyPath(keyPath)}" does not contain an array.`);
|
|
74
|
+
}
|
|
75
|
+
return [
|
|
76
|
+
...value.slice(0, key.index),
|
|
77
|
+
_setValueAtKeyPath(value[key.index], remainingPath, newValue),
|
|
78
|
+
...value.slice(key.index + 1),
|
|
79
|
+
];
|
|
80
|
+
case "property":
|
|
81
|
+
if (typeof value !== "object" || value === null) {
|
|
82
|
+
throw new TypeError(`Key path "${renderParsedKeyPath(keyPath)}" does not contain an object.`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
...value,
|
|
86
|
+
[key.name]: _setValueAtKeyPath(value[key.name], remainingPath, newValue),
|
|
87
|
+
};
|
|
88
|
+
default:
|
|
89
|
+
return assertExhaustive(key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
return newValue;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
export const setValueAtKeyPath = (value, keyPath, newValue) => _setValueAtKeyPath(value, parseKeyPath(keyPath), newValue);
|
|
@@ -6,6 +6,7 @@ import { type KeyPath } from "./keyPath.ts";
|
|
|
6
6
|
*/
|
|
7
7
|
export type SortOrder = "displayName" | {
|
|
8
8
|
keyPath: KeyPath;
|
|
9
|
+
isIndex?: boolean;
|
|
9
10
|
};
|
|
10
11
|
/**
|
|
11
12
|
* An instance with all values needed for sorting.
|
|
@@ -15,3 +16,11 @@ export type SortableInstance = [InstanceContainer, InstanceContainerOverview];
|
|
|
15
16
|
* Sorts instances according to the sort order defined for the entity.
|
|
16
17
|
*/
|
|
17
18
|
export declare const sortBySortOrder: <T extends SortableInstance>(instances: T[], sortOrder: SortOrder | undefined) => T[];
|
|
19
|
+
export declare const getSortOrderKeyPathForReorder: (decl: {
|
|
20
|
+
name: string;
|
|
21
|
+
sortOrder?: SortOrder;
|
|
22
|
+
}) => KeyPath;
|
|
23
|
+
export declare const isReorderableSortOrder: (sortOrder: SortOrder | undefined) => sortOrder is {
|
|
24
|
+
keyPath: KeyPath;
|
|
25
|
+
isIndex: true;
|
|
26
|
+
};
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
+
import { HTTPError } from "../../utils/error.js";
|
|
1
2
|
import { getValueAtKeyPath } from "./keyPath.js";
|
|
2
|
-
// | { entityMapKeyPath: KeyPath; keyPathInEntityMap: KeyPath }
|
|
3
3
|
const getSortOrderValue = (instanceContent, sortOrder) => {
|
|
4
4
|
if ("keyPath" in sortOrder) {
|
|
5
5
|
return getValueAtKeyPath(instanceContent, sortOrder.keyPath);
|
|
6
6
|
}
|
|
7
7
|
else {
|
|
8
|
-
// const entityMap = instance.getValueAtKeyPath(sortOrder.entityMapKeyPath)
|
|
9
|
-
// if (entityMap && typeof entityMap === "object") {
|
|
10
|
-
// const nestedInstance = InstanceContent.fromUnknown(entityMap)
|
|
11
|
-
// return nestedInstance.getValueAtKeyPath(sortOrder.keyPathInEntityMap)
|
|
12
|
-
// }
|
|
13
8
|
return;
|
|
14
9
|
}
|
|
15
10
|
};
|
|
@@ -36,3 +31,13 @@ export const sortBySortOrder = (instances, sortOrder) => {
|
|
|
36
31
|
.map(({ instance }) => instance);
|
|
37
32
|
}
|
|
38
33
|
};
|
|
34
|
+
export const getSortOrderKeyPathForReorder = (decl) => {
|
|
35
|
+
const { sortOrder } = decl;
|
|
36
|
+
if (isReorderableSortOrder(sortOrder)) {
|
|
37
|
+
return sortOrder.keyPath;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw new HTTPError(400, `Declaration "${decl.name}" does not have the sort order defined correctly. Please check the validity of the whole schema as any incorrect configuration should have been caught during schema intialization.`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export const isReorderableSortOrder = (sortOrder) => typeof sortOrder === "object" && sortOrder.isIndex === true;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const getErrorMessageForDisplay: (error: Error, indentation?: number) => string;
|
|
2
|
+
export declare const countError: (error: Error) => number;
|
|
3
|
+
export declare const countErrors: (errors: Error[]) => number;
|
|
4
|
+
export declare const wrapErrorsIfAny: (message: string, errors: Error[]) => AggregateError | undefined;
|
|
5
|
+
export declare class HTTPError extends Error {
|
|
6
|
+
code: number;
|
|
7
|
+
constructor(code: number, message: string);
|
|
8
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { applyIndentation } from "../../shared/utils/render.js";
|
|
2
|
+
import { UniqueConstraintError } from "./unique.js";
|
|
3
|
+
export const getErrorMessageForDisplay = (error, indentation = 2) => {
|
|
4
|
+
if (error instanceof AggregateError) {
|
|
5
|
+
return `${error.message}\n${applyIndentation(1, error.errors
|
|
6
|
+
.filter(subError => subError instanceof Error)
|
|
7
|
+
.map(subError => getErrorMessageForDisplay(subError, indentation))
|
|
8
|
+
.join("\n"), indentation)}`;
|
|
9
|
+
}
|
|
10
|
+
else if (error.cause instanceof Error) {
|
|
11
|
+
return `${error.message}\n${applyIndentation(1, getErrorMessageForDisplay(error.cause, indentation), indentation)}`;
|
|
12
|
+
}
|
|
13
|
+
else if (error instanceof UniqueConstraintError) {
|
|
14
|
+
return `${error.message}\n${applyIndentation(1, error.parts.join("\n"), indentation)}`;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
return error.message;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export const countError = (error) => error instanceof AggregateError
|
|
21
|
+
? countErrors(error.errors.filter(subError => subError instanceof Error))
|
|
22
|
+
: error.cause instanceof Error
|
|
23
|
+
? countError(error.cause)
|
|
24
|
+
: 1;
|
|
25
|
+
export const countErrors = (errors) => errors.reduce((count, error) => count + countError(error), 0);
|
|
26
|
+
export const wrapErrorsIfAny = (message, errors) => {
|
|
27
|
+
if (errors.length === 0) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return new AggregateError(errors, message);
|
|
31
|
+
};
|
|
32
|
+
export class HTTPError extends Error {
|
|
33
|
+
code;
|
|
34
|
+
constructor(code, message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.name = "HTTPError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mergeObjects } from "@elyukai/utils/object";
|
|
2
|
-
|
|
2
|
+
const EOL = await import("node:os").then(module => module.EOL).catch(() => "\n");
|
|
3
3
|
export const prefixLines = (prefix, text, includeEmptyLines = false) => text
|
|
4
4
|
.split(EOL)
|
|
5
5
|
.map(line => (includeEmptyLines || line.length > 0 ? prefix + line : line))
|
package/dist/src/web/web.js
CHANGED
|
@@ -728,7 +728,7 @@ const request = async (method, url, options)=>{
|
|
|
728
728
|
};
|
|
729
729
|
const api_getResource = async (url, options)=>request("GET", url, options);
|
|
730
730
|
const postResource = async (url, options)=>request("POST", url, options);
|
|
731
|
-
const
|
|
731
|
+
const api_putResource = async (url, options)=>request("PUT", url, options);
|
|
732
732
|
const deleteResource = async (url, options)=>request("DELETE", url, options);
|
|
733
733
|
const getAllDeclarations = async (locales, kind)=>api_getResource("/api/declarations", {
|
|
734
734
|
locales,
|
|
@@ -741,6 +741,15 @@ const getAllSingletonEntities = (locales)=>getAllDeclarations(locales, "Singleto
|
|
|
741
741
|
const getInstancesByEntityName = async (locales, name)=>api_getResource(`/api/declarations/${name}/instances`, {
|
|
742
742
|
locales
|
|
743
743
|
});
|
|
744
|
+
const reorderAllInstancesInEntity = async (locales, name, newOrder)=>{
|
|
745
|
+
const body = {
|
|
746
|
+
order: newOrder
|
|
747
|
+
};
|
|
748
|
+
return api_putResource(`/api/declarations/${name}/instances/reorder`, {
|
|
749
|
+
locales,
|
|
750
|
+
body
|
|
751
|
+
});
|
|
752
|
+
};
|
|
744
753
|
const getLocaleInstances = (locales, localeEntityName)=>localeEntityName ? getInstancesByEntityName(locales, localeEntityName) : Promise.resolve({
|
|
745
754
|
instances: [],
|
|
746
755
|
isLocaleEntity: true
|
|
@@ -764,7 +773,7 @@ const updateInstanceByEntityNameAndId = async (locales, name, id, content)=>{
|
|
|
764
773
|
const body = {
|
|
765
774
|
instance: content
|
|
766
775
|
};
|
|
767
|
-
return
|
|
776
|
+
return api_putResource(`/api/declarations/${name}/instances/${id}`, {
|
|
768
777
|
locales,
|
|
769
778
|
body
|
|
770
779
|
});
|
|
@@ -791,7 +800,7 @@ const updateSingletonInstanceByEntityName = async (locales, name, content)=>{
|
|
|
791
800
|
const body = {
|
|
792
801
|
instance: content
|
|
793
802
|
};
|
|
794
|
-
return
|
|
803
|
+
return api_putResource(`/api/declarations/${name}/instance`, {
|
|
795
804
|
locales,
|
|
796
805
|
body
|
|
797
806
|
});
|
|
@@ -2404,6 +2413,13 @@ const useMappedAPIResource = (apiFn, mapFn, ...args)=>{
|
|
|
2404
2413
|
mapFn,
|
|
2405
2414
|
...args
|
|
2406
2415
|
]);
|
|
2416
|
+
const setNewData = hooks_module_q((result)=>{
|
|
2417
|
+
setData(mapFn(result));
|
|
2418
|
+
}, [
|
|
2419
|
+
apiFn,
|
|
2420
|
+
mapFn,
|
|
2421
|
+
...args
|
|
2422
|
+
]);
|
|
2407
2423
|
hooks_module_h(()=>{
|
|
2408
2424
|
fetchData().catch(logAndAlertError);
|
|
2409
2425
|
}, [
|
|
@@ -2414,7 +2430,8 @@ const useMappedAPIResource = (apiFn, mapFn, ...args)=>{
|
|
|
2414
2430
|
]);
|
|
2415
2431
|
return [
|
|
2416
2432
|
data,
|
|
2417
|
-
reload
|
|
2433
|
+
reload,
|
|
2434
|
+
setNewData
|
|
2418
2435
|
];
|
|
2419
2436
|
};
|
|
2420
2437
|
const letterOrDigit = /[a-zA-Z0-9]/;
|
|
@@ -5262,7 +5279,7 @@ const IntegerTypeInput = ({ type, value, disabled, onChange })=>{
|
|
|
5262
5279
|
const numericValue = Number.parseInt(event.currentTarget.value, 10);
|
|
5263
5280
|
if (!Number.isNaN(numericValue)) onChange(numericValue);
|
|
5264
5281
|
},
|
|
5265
|
-
step:
|
|
5282
|
+
step: 1,
|
|
5266
5283
|
"aria-invalid": errors.length > 0,
|
|
5267
5284
|
disabled: disabled
|
|
5268
5285
|
}),
|
|
@@ -6633,6 +6650,8 @@ const CreateSingletonInstance = ()=>/*#__PURE__*/ jsxRuntime_module_u(SingletonI
|
|
|
6633
6650
|
onSubmit: CreateSingletonInstance_onSubmit,
|
|
6634
6651
|
onSave: CreateSingletonInstance_onSave
|
|
6635
6652
|
});
|
|
6653
|
+
await import("node:os").then((module)=>module.EOL).catch(()=>"\n");
|
|
6654
|
+
const isReorderableSortOrder = (sortOrder)=>"object" == typeof sortOrder && true === sortOrder.isIndex;
|
|
6636
6655
|
const Entity_localeMapper = (result)=>result.instances;
|
|
6637
6656
|
const mapInstances = (data)=>data.instances;
|
|
6638
6657
|
const Entity = ()=>{
|
|
@@ -6644,8 +6663,34 @@ const Entity = ()=>{
|
|
|
6644
6663
|
const gitClient = hooks_module_x(GitClientContext);
|
|
6645
6664
|
const { reloadEntities } = hooks_module_x(EntitiesContext);
|
|
6646
6665
|
const { declaration: entity, isLocaleEntity } = entityFromRoute ?? {};
|
|
6647
|
-
const [instances, reloadInstances] = useMappedAPIResource(getInstancesByEntityName, mapInstances, locales, entity?.name ?? name ?? "");
|
|
6666
|
+
const [instances, reloadInstances, setInstances] = useMappedAPIResource(getInstancesByEntityName, mapInstances, locales, entity?.name ?? name ?? "");
|
|
6648
6667
|
const [localeInstances, reloadLocaleInstances] = useMappedAPIResource(getLocaleInstances, Entity_localeMapper, locales, config.localeEntityName);
|
|
6668
|
+
const [inEditMode, setInEditMode] = hooks_module_d(false);
|
|
6669
|
+
const [inApplyingNewOrder, setInApplyingNewOrder] = hooks_module_d(false);
|
|
6670
|
+
const [orderedInstances, setOrderedInstances] = hooks_module_d([]);
|
|
6671
|
+
hooks_module_h(()=>{
|
|
6672
|
+
setOrderedInstances(instances ?? []);
|
|
6673
|
+
}, [
|
|
6674
|
+
instances
|
|
6675
|
+
]);
|
|
6676
|
+
const toggleEditMode = hooks_module_q(()=>{
|
|
6677
|
+
if (entity && !inApplyingNewOrder) if (inEditMode) {
|
|
6678
|
+
setInApplyingNewOrder(true);
|
|
6679
|
+
reorderAllInstancesInEntity(locales, entity.name, orderedInstances.map((i)=>i.id)).then((res)=>{
|
|
6680
|
+
setInstances(res);
|
|
6681
|
+
setInEditMode(false);
|
|
6682
|
+
}, logAndAlertError).finally(()=>{
|
|
6683
|
+
setInApplyingNewOrder(false);
|
|
6684
|
+
});
|
|
6685
|
+
} else setInEditMode(true);
|
|
6686
|
+
}, [
|
|
6687
|
+
entity,
|
|
6688
|
+
inApplyingNewOrder,
|
|
6689
|
+
inEditMode,
|
|
6690
|
+
locales,
|
|
6691
|
+
orderedInstances,
|
|
6692
|
+
setInstances
|
|
6693
|
+
]);
|
|
6649
6694
|
const { latestCommit } = hooks_module_x(GitClientContext) ?? {};
|
|
6650
6695
|
hooks_module_h(()=>{
|
|
6651
6696
|
reloadInstances().then(()=>reloadLocaleInstances()).catch(logAndAlertError);
|
|
@@ -6714,7 +6759,7 @@ const Entity = ()=>{
|
|
|
6714
6759
|
name: "undefined" === key ? "No matching locale" : localeInstances?.find((instance)=>instance.id === key)?.displayName ?? key,
|
|
6715
6760
|
instances: instancesByLocale[key] ?? []
|
|
6716
6761
|
})).filter((group)=>group.instances.length > 0);
|
|
6717
|
-
const instanceMapper = (instance)=>{
|
|
6762
|
+
const instanceMapper = (instance, index, instancesArr)=>{
|
|
6718
6763
|
const gitStatusForDisplay = getGitStatusForDisplay(instance.gitStatus);
|
|
6719
6764
|
return /*#__PURE__*/ jsxRuntime_module_u("li", {
|
|
6720
6765
|
id: `instance-${instance.id}`,
|
|
@@ -6729,7 +6774,28 @@ const Entity = ()=>{
|
|
|
6729
6774
|
class: "entries-item__subtitle entries-item__subtitle--id",
|
|
6730
6775
|
children: instance.id
|
|
6731
6776
|
}),
|
|
6732
|
-
/*#__PURE__*/ jsxRuntime_module_u("div", {
|
|
6777
|
+
inEditMode ? /*#__PURE__*/ jsxRuntime_module_u("div", {
|
|
6778
|
+
class: "entries-item__side",
|
|
6779
|
+
children: /*#__PURE__*/ jsxRuntime_module_u("div", {
|
|
6780
|
+
class: "btns",
|
|
6781
|
+
children: [
|
|
6782
|
+
/*#__PURE__*/ jsxRuntime_module_u("button", {
|
|
6783
|
+
disabled: index <= 0,
|
|
6784
|
+
onClick: ()=>{
|
|
6785
|
+
setOrderedInstances((ins)=>reorder(ins, index, index - 1));
|
|
6786
|
+
},
|
|
6787
|
+
children: "Move up"
|
|
6788
|
+
}),
|
|
6789
|
+
/*#__PURE__*/ jsxRuntime_module_u("button", {
|
|
6790
|
+
disabled: index >= instancesArr.length - 1,
|
|
6791
|
+
onClick: ()=>{
|
|
6792
|
+
setOrderedInstances((ins)=>reorder(ins, index, index + 1));
|
|
6793
|
+
},
|
|
6794
|
+
children: "Move down"
|
|
6795
|
+
})
|
|
6796
|
+
]
|
|
6797
|
+
})
|
|
6798
|
+
}) : /*#__PURE__*/ jsxRuntime_module_u("div", {
|
|
6733
6799
|
class: "entries-item__side",
|
|
6734
6800
|
children: [
|
|
6735
6801
|
/*#__PURE__*/ jsxRuntime_module_u(GitStatusIndicator, {
|
|
@@ -6773,10 +6839,19 @@ const Entity = ()=>{
|
|
|
6773
6839
|
/*#__PURE__*/ jsxRuntime_module_u("h1", {
|
|
6774
6840
|
children: toTitleCase(entity.namePlural)
|
|
6775
6841
|
}),
|
|
6776
|
-
/*#__PURE__*/ jsxRuntime_module_u("
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6842
|
+
/*#__PURE__*/ jsxRuntime_module_u("div", {
|
|
6843
|
+
className: "btns",
|
|
6844
|
+
children: [
|
|
6845
|
+
isReorderableSortOrder(entity.sortOrder) ? /*#__PURE__*/ jsxRuntime_module_u("button", {
|
|
6846
|
+
onClick: toggleEditMode,
|
|
6847
|
+
children: inEditMode ? "Apply new order" : "Reorder"
|
|
6848
|
+
}) : null,
|
|
6849
|
+
/*#__PURE__*/ jsxRuntime_module_u("a", {
|
|
6850
|
+
class: "btn btn--primary",
|
|
6851
|
+
href: `/entities/${entity.name}/instances/create`,
|
|
6852
|
+
children: "Add"
|
|
6853
|
+
})
|
|
6854
|
+
]
|
|
6780
6855
|
})
|
|
6781
6856
|
]
|
|
6782
6857
|
}),
|
|
@@ -6814,13 +6889,17 @@ const Entity = ()=>{
|
|
|
6814
6889
|
value: searchText,
|
|
6815
6890
|
onInput: (event)=>{
|
|
6816
6891
|
setSearchText(event.currentTarget.value);
|
|
6817
|
-
}
|
|
6892
|
+
},
|
|
6893
|
+
disabled: inEditMode
|
|
6818
6894
|
})
|
|
6819
6895
|
]
|
|
6820
6896
|
})
|
|
6821
6897
|
]
|
|
6822
6898
|
}),
|
|
6823
|
-
|
|
6899
|
+
inEditMode ? /*#__PURE__*/ jsxRuntime_module_u("ul", {
|
|
6900
|
+
class: "entries entries--instances",
|
|
6901
|
+
children: orderedInstances.map(instanceMapper)
|
|
6902
|
+
}) : isLocaleEntity || 1 === groupedInstances.length && groupedInstances[0]?.id !== "undefined" && locales[0] === groupedInstances[0]?.id ? /*#__PURE__*/ jsxRuntime_module_u("ul", {
|
|
6824
6903
|
class: "entries entries--instances",
|
|
6825
6904
|
children: filteredInstances.map(instanceMapper)
|
|
6826
6905
|
}) : /*#__PURE__*/ jsxRuntime_module_u("ul", {
|
package/package.json
CHANGED
package/public/css/styles.css
CHANGED
|
File without changes
|