tsondb 0.20.7 → 0.20.9
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/bin/tsondb.js +17 -2
- package/dist/src/node/index.d.ts +24 -0
- package/dist/src/node/index.js +31 -3
- package/dist/src/node/schema/treeOperations/validation.d.ts +4 -2
- package/dist/src/node/schema/treeOperations/validation.js +83 -11
- package/dist/src/node/server/index.js +1 -1
- package/dist/src/node/utils/files.d.ts +1 -0
- package/dist/src/node/utils/files.js +4 -1
- package/dist/src/shared/validation/string.js +2 -2
- package/dist/src/web/web.js +90 -22
- package/package.json +2 -2
|
@@ -48,6 +48,12 @@ const passedArguments = parseArguments({
|
|
|
48
48
|
},
|
|
49
49
|
format: {
|
|
50
50
|
name: "format",
|
|
51
|
+
options: {
|
|
52
|
+
check: {
|
|
53
|
+
name: "check",
|
|
54
|
+
type: Boolean,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
51
57
|
},
|
|
52
58
|
name: {
|
|
53
59
|
name: "name",
|
|
@@ -144,9 +150,18 @@ else {
|
|
|
144
150
|
break;
|
|
145
151
|
}
|
|
146
152
|
case "format": {
|
|
147
|
-
debug(`running command: format`);
|
|
148
153
|
const db = await createDB(config, config.validationOptions, true);
|
|
149
|
-
|
|
154
|
+
if (passedArguments.command.options?.check === true) {
|
|
155
|
+
debug(`running command: format --check`);
|
|
156
|
+
const result = await db.checkFormat();
|
|
157
|
+
if (!result) {
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
debug(`running command: format`);
|
|
163
|
+
await db.format();
|
|
164
|
+
}
|
|
150
165
|
break;
|
|
151
166
|
}
|
|
152
167
|
case "name": {
|
package/dist/src/node/index.d.ts
CHANGED
|
@@ -64,6 +64,26 @@ export interface ValidationOptions {
|
|
|
64
64
|
* If set, translations will be validated according to the provided options.
|
|
65
65
|
*/
|
|
66
66
|
checkTranslations?: TranslationValidationOptions;
|
|
67
|
+
/**
|
|
68
|
+
* Special options for validating Markdown content in the database.
|
|
69
|
+
*/
|
|
70
|
+
markdown?: Partial<MarkdownValidationOptions>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Special options for validating Markdown content in the database.
|
|
74
|
+
*/
|
|
75
|
+
export interface MarkdownValidationOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Checks for the existence of referenced entities and possibly instances in attributed strings. If the attribute names are not specified via an object, the default attribute names `entity` and `instance` will be used.
|
|
78
|
+
*/
|
|
79
|
+
checkInstanceReferencesInAttributedStrings?: boolean | {
|
|
80
|
+
entityKey: string;
|
|
81
|
+
instanceKey: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* If set, keys in attributed strings will be checked agains the provided list of keys or regular expression. Keys for `checkInstanceReferencesInAttributedStrings` are included automatically and do not need to be specified again.
|
|
85
|
+
*/
|
|
86
|
+
checkAttributedStringKeys?: string[] | RegExp;
|
|
67
87
|
}
|
|
68
88
|
/**
|
|
69
89
|
* Options for validating translations in the database.
|
|
@@ -114,6 +134,10 @@ export declare class TSONDB<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> {
|
|
|
114
134
|
* Formats the data on disk according to the current in-memory representation.
|
|
115
135
|
*/
|
|
116
136
|
format(): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Checks if the data on disk is formatted according to the current in-memory representation.
|
|
139
|
+
*/
|
|
140
|
+
checkFormat(): Promise<boolean>;
|
|
117
141
|
/**
|
|
118
142
|
* Reloads the data from disk into memory.
|
|
119
143
|
*/
|
package/dist/src/node/index.js
CHANGED
|
@@ -15,14 +15,14 @@ import {} from "./schema/dsl/index.js";
|
|
|
15
15
|
import {} from "./schema/generatedTypeHelpers.js";
|
|
16
16
|
import { isEntityDeclWithParentReference } from "./schema/guards.js";
|
|
17
17
|
import { serializeNode } from "./schema/treeOperations/serialization.js";
|
|
18
|
-
import { createReferenceValidator, validateDeclReferentialIntegrity, validateDeclStructuralIntegrity, } from "./schema/treeOperations/validation.js";
|
|
18
|
+
import { createEntityNameValidator, createReferenceValidator, validateDeclReferentialIntegrity, validateDeclStructuralIntegrity, } from "./schema/treeOperations/validation.js";
|
|
19
19
|
import { Transaction } from "./transaction.js";
|
|
20
20
|
import { checkCustomConstraintsForAllEntities } from "./utils/customConstraints.js";
|
|
21
21
|
import { DatabaseInMemory } from "./utils/databaseInMemory.js";
|
|
22
22
|
import { applyStepsToDisk } from "./utils/databaseOnDisk.js";
|
|
23
23
|
import { getAllInstanceOverviewsByEntityName, getDisplayNameFromEntityInstance, getInstanceOverview, getInstanceOverviewsByEntityName, } from "./utils/displayName.js";
|
|
24
24
|
import { countError, countErrors, getErrorMessageForDisplay, HTTPError, wrapErrorsIfAny, } from "./utils/error.js";
|
|
25
|
-
import { getFileNameForId, writeInstance } from "./utils/files.js";
|
|
25
|
+
import { formatInstance, getFileNameForId, readInstance, writeInstance } from "./utils/files.js";
|
|
26
26
|
import { attachGitStatusToDatabaseInMemory } from "./utils/git.js";
|
|
27
27
|
import { getReferencesToInstances, isReferencedByOtherInstances, } from "./utils/references.js";
|
|
28
28
|
import { checkUniqueConstraintsForAllEntities } from "./utils/unique.js";
|
|
@@ -210,11 +210,12 @@ export class TSONDB {
|
|
|
210
210
|
if (!skipStructuralValidation) {
|
|
211
211
|
if (checkReferentialIntegrity) {
|
|
212
212
|
debug("Checking referential integrity ...");
|
|
213
|
+
const entityNameValidator = createEntityNameValidator(this.#schema.isEntityName.bind(this.#schema), true);
|
|
213
214
|
const referenceValidator = createReferenceValidator(this.#schema.isEntityName.bind(this.#schema), data, true);
|
|
214
215
|
const referenceErrors = onlyEntities
|
|
215
216
|
.flatMap(entity => parallelizeErrors(data
|
|
216
217
|
.getAllInstanceContainersOfEntity(entity.name)
|
|
217
|
-
.map(instance => wrapErrorsIfAny(`in file ${styleText("white", `"${this.#dataRootPath}${sep}${styleText("bold", join(entity.name, getFileNameForId(instance.id)))}"`)}`, validateDeclReferentialIntegrity(validationContext, referenceValidator, [], entity, [], instance.content)))))
|
|
218
|
+
.map(instance => wrapErrorsIfAny(`in file ${styleText("white", `"${this.#dataRootPath}${sep}${styleText("bold", join(entity.name, getFileNameForId(instance.id)))}"`)}`, validateDeclReferentialIntegrity(validationContext, entityNameValidator, referenceValidator, [], entity, [], instance.content)))))
|
|
218
219
|
.toSorted((a, b) => a.message.localeCompare(b.message));
|
|
219
220
|
if (referenceErrors.length > 0) {
|
|
220
221
|
const errorCount = countErrors(referenceErrors);
|
|
@@ -287,6 +288,33 @@ export class TSONDB {
|
|
|
287
288
|
}, true);
|
|
288
289
|
debug("All data is formatted");
|
|
289
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Checks if the data on disk is formatted according to the current in-memory representation.
|
|
293
|
+
*/
|
|
294
|
+
async checkFormat() {
|
|
295
|
+
debug("Check database formatting ...");
|
|
296
|
+
let counter = 0;
|
|
297
|
+
await this.#data.forEachInstance(async (entityName, instance) => {
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
299
|
+
const entity = this.#schema.getEntity(entityName);
|
|
300
|
+
const instanceOnDisk = await readInstance(this.#dataRootPath, entity, instance.id);
|
|
301
|
+
const currentFormatted = formatInstance(entity, instance.content, this.#formatterOptions);
|
|
302
|
+
if (instanceOnDisk !== currentFormatted) {
|
|
303
|
+
console.log(`Instance ${styleText("yellow", instance.id)} of entity ${styleText("cyan", entityName)} is not formatted correctly.`);
|
|
304
|
+
counter++;
|
|
305
|
+
}
|
|
306
|
+
}, true);
|
|
307
|
+
if (counter === 0) {
|
|
308
|
+
console.log(styleText("green", "All data is formatted"));
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
console.error(styleText("red", `${counter.toFixed()} formatting error${counter === 1 ? "" : "s"} found`, {
|
|
313
|
+
stream: stderr,
|
|
314
|
+
}));
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
290
318
|
/**
|
|
291
319
|
* Reloads the data from disk into memory.
|
|
292
320
|
*/
|
|
@@ -7,13 +7,15 @@ export type IdentifierToCheck = {
|
|
|
7
7
|
name: string;
|
|
8
8
|
value: unknown;
|
|
9
9
|
};
|
|
10
|
+
export type EntityNameValidator = (entityName: unknown) => Error[];
|
|
10
11
|
export type ReferenceValidator = (entityName: string, instanceId: unknown) => ReferenceError[];
|
|
11
12
|
export interface ValidationContext {
|
|
12
13
|
useStyling: boolean;
|
|
13
14
|
validationOptions: ValidationOptions;
|
|
14
15
|
}
|
|
16
|
+
export declare const createEntityNameValidator: (isEntityName: (name: string) => boolean, useStyling: boolean) => EntityNameValidator;
|
|
15
17
|
export declare const createReferenceValidator: <EM extends AnyEntityMap>(isEntityName: (name: string) => name is Extract<keyof EM, string>, databaseInMemory: DatabaseInMemory<EM>, useStyling: boolean) => ReferenceValidator;
|
|
16
18
|
export declare const validateDeclStructuralIntegrity: (helpers: ValidationContext, inDecls: Decl[], decl: Decl, typeArgs: Type[], value: unknown) => TypeError[];
|
|
17
19
|
export declare const validateTypeStructuralIntegrity: (helpers: ValidationContext, inDecls: Decl[], type: Type, value: unknown) => TypeError[];
|
|
18
|
-
export declare const validateDeclReferentialIntegrity: (helpers: ValidationContext, checkReferentialIntegrity: ReferenceValidator, inDecls: Decl[], decl: Decl, typeArgs: Type[], value: unknown) => ReferenceError[];
|
|
19
|
-
export declare const validateTypeReferentialIntegrity: (helpers: ValidationContext, checkReferentialIntegrity: ReferenceValidator, inDecls: Decl[], type: Type, value: unknown) => ReferenceError[];
|
|
20
|
+
export declare const validateDeclReferentialIntegrity: (helpers: ValidationContext, checkEntityName: EntityNameValidator, checkReferentialIntegrity: ReferenceValidator, inDecls: Decl[], decl: Decl, typeArgs: Type[], value: unknown) => ReferenceError[];
|
|
21
|
+
export declare const validateTypeReferentialIntegrity: (helpers: ValidationContext, checkEntityName: EntityNameValidator, checkReferentialIntegrity: ReferenceValidator, inDecls: Decl[], type: Type, value: unknown) => ReferenceError[];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { validate as validateBlockMarkdown, validateInline as validateInlineMarkdown, } from "@elyukai/markdown/validate";
|
|
2
|
+
import { isNotNullish } from "@elyukai/utils/nullable";
|
|
1
3
|
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
2
4
|
import { MessageError, parseMessage, validate } from "messageformat";
|
|
3
5
|
import { ENUM_DISCRIMINATOR_KEY } from "../../../shared/schema/declarations/EnumDecl.js";
|
|
@@ -16,6 +18,9 @@ import { getTypeArgumentsRecord } from "../dsl/declarations/Decl.js";
|
|
|
16
18
|
import { createEntityIdentifierType } from "../dsl/declarations/EntityDecl.js";
|
|
17
19
|
import { isChildEntitiesType } from "../guards.js";
|
|
18
20
|
import { resolveTypeArguments } from "./typeResolution.js";
|
|
21
|
+
export const createEntityNameValidator = (isEntityName, useStyling) => entityName => typeof entityName === "string" && isEntityName(entityName)
|
|
22
|
+
? []
|
|
23
|
+
: [ReferenceError(`Invalid entity name ${json(entityName, useStyling)}`)];
|
|
19
24
|
export const createReferenceValidator = (isEntityName, databaseInMemory, useStyling) => (entityName, instanceId) => isEntityName(entityName)
|
|
20
25
|
? typeof instanceId === "string" &&
|
|
21
26
|
databaseInMemory.hasInstanceOfEntityById(entityName, instanceId)
|
|
@@ -86,6 +91,69 @@ const validateTranslationObjectStructuralIntegrity = (context, allKeysAreRequire
|
|
|
86
91
|
}),
|
|
87
92
|
]);
|
|
88
93
|
};
|
|
94
|
+
const DEFAULT_ENTITY_KEY = "entity";
|
|
95
|
+
const DEFAULT_INSTANCE_KEY = "instance";
|
|
96
|
+
const normalizeAttributedStringReferenceKeys = (option) => option === true ? { entityKey: DEFAULT_ENTITY_KEY, instanceKey: DEFAULT_INSTANCE_KEY } : option;
|
|
97
|
+
const createStructuralAttributedStringKeyValidator = (validationOptions) => {
|
|
98
|
+
const { checkInstanceReferencesInAttributedStrings: checkAttrRefs, checkAttributedStringKeys } = validationOptions.markdown ?? {};
|
|
99
|
+
if (checkAttributedStringKeys !== undefined) {
|
|
100
|
+
const generalTest = Array.isArray(checkAttributedStringKeys)
|
|
101
|
+
? key => checkAttributedStringKeys.includes(key)
|
|
102
|
+
: key => checkAttributedStringKeys.test(key);
|
|
103
|
+
if (checkAttrRefs === true || typeof checkAttrRefs === "object") {
|
|
104
|
+
const { entityKey, instanceKey } = normalizeAttributedStringReferenceKeys(checkAttrRefs);
|
|
105
|
+
const additionalValidKeys = [entityKey, instanceKey];
|
|
106
|
+
return key => generalTest(key) || additionalValidKeys.includes(key);
|
|
107
|
+
}
|
|
108
|
+
return generalTest;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
};
|
|
112
|
+
const createStructuralAttributedStringValidator = (validationOptions) => {
|
|
113
|
+
const keyValidator = createStructuralAttributedStringKeyValidator(validationOptions);
|
|
114
|
+
if (keyValidator === undefined) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return attributes => Object.keys(attributes)
|
|
118
|
+
.map(attributeName => keyValidator(attributeName)
|
|
119
|
+
? undefined
|
|
120
|
+
: new TypeError(`invalid attribute key "${attributeName}" in attributed string`))
|
|
121
|
+
.filter(isNotNullish);
|
|
122
|
+
};
|
|
123
|
+
const createReferentialAttributedStringValidator = (entityValidator, referenceValidator, validationOptions) => {
|
|
124
|
+
const { checkInstanceReferencesInAttributedStrings: checkAttrRefs } = validationOptions.markdown ?? {};
|
|
125
|
+
if (checkAttrRefs === true || typeof checkAttrRefs === "object") {
|
|
126
|
+
const { entityKey, instanceKey } = normalizeAttributedStringReferenceKeys(checkAttrRefs);
|
|
127
|
+
return attributes => {
|
|
128
|
+
if (entityKey in attributes) {
|
|
129
|
+
const entityName = attributes[entityKey];
|
|
130
|
+
const entityErrors = entityValidator(entityName);
|
|
131
|
+
if (entityErrors.length > 0) {
|
|
132
|
+
return entityErrors;
|
|
133
|
+
}
|
|
134
|
+
if (instanceKey in attributes && typeof entityName === "string") {
|
|
135
|
+
const instanceId = attributes[instanceKey];
|
|
136
|
+
return referenceValidator(entityName, instanceId);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return [];
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
};
|
|
144
|
+
const validateMarkdownString = (type, value, validator) => {
|
|
145
|
+
if (type.markdown !== undefined && typeof value === "string") {
|
|
146
|
+
switch (type.markdown) {
|
|
147
|
+
case "inline":
|
|
148
|
+
return validateInlineMarkdown(value, { validateAttributes: validator });
|
|
149
|
+
case "block":
|
|
150
|
+
return validateBlockMarkdown(value, { validateAttributes: validator });
|
|
151
|
+
default:
|
|
152
|
+
return assertExhaustive(type.markdown);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return [];
|
|
156
|
+
};
|
|
89
157
|
export const validateTypeStructuralIntegrity = (helpers, inDecls, type, value) => {
|
|
90
158
|
switch (type.kind) {
|
|
91
159
|
case NodeKind.ArrayType: {
|
|
@@ -149,7 +217,10 @@ export const validateTypeStructuralIntegrity = (helpers, inDecls, type, value) =
|
|
|
149
217
|
if (typeof value !== "string") {
|
|
150
218
|
return [TypeError(`expected a string, but got ${json(value, helpers.useStyling)}`)];
|
|
151
219
|
}
|
|
152
|
-
return
|
|
220
|
+
return parallelizeErrors([
|
|
221
|
+
...validateStringConstraints(type, value),
|
|
222
|
+
...validateMarkdownString(type, value, createStructuralAttributedStringValidator(helpers.validationOptions)),
|
|
223
|
+
]);
|
|
153
224
|
}
|
|
154
225
|
case NodeKind.TypeArgumentType: {
|
|
155
226
|
throw new TypeError(`generic argument "${type.argument.name}" has not been replaced with a concrete type`);
|
|
@@ -212,13 +283,13 @@ export const validateTypeStructuralIntegrity = (helpers, inDecls, type, value) =
|
|
|
212
283
|
return assertExhaustive(type);
|
|
213
284
|
}
|
|
214
285
|
};
|
|
215
|
-
export const validateDeclReferentialIntegrity = (helpers, checkReferentialIntegrity, inDecls, decl, typeArgs, value) => {
|
|
286
|
+
export const validateDeclReferentialIntegrity = (helpers, checkEntityName, checkReferentialIntegrity, inDecls, decl, typeArgs, value) => {
|
|
216
287
|
switch (decl.kind) {
|
|
217
288
|
case NodeKind.EntityDecl:
|
|
218
|
-
return validateTypeReferentialIntegrity(helpers, checkReferentialIntegrity, inDecls, decl.type.value, value);
|
|
289
|
+
return validateTypeReferentialIntegrity(helpers, checkEntityName, checkReferentialIntegrity, inDecls, decl.type.value, value);
|
|
219
290
|
case NodeKind.EnumDecl:
|
|
220
291
|
case NodeKind.TypeAliasDecl:
|
|
221
|
-
return validateTypeReferentialIntegrity(helpers, checkReferentialIntegrity, [...inDecls, decl], resolveTypeArguments(getTypeArgumentsRecord(decl, typeArgs), decl.type.value, [
|
|
292
|
+
return validateTypeReferentialIntegrity(helpers, checkEntityName, checkReferentialIntegrity, [...inDecls, decl], resolveTypeArguments(getTypeArgumentsRecord(decl, typeArgs), decl.type.value, [
|
|
222
293
|
...inDecls,
|
|
223
294
|
decl,
|
|
224
295
|
]), value);
|
|
@@ -226,11 +297,11 @@ export const validateDeclReferentialIntegrity = (helpers, checkReferentialIntegr
|
|
|
226
297
|
return assertExhaustive(decl);
|
|
227
298
|
}
|
|
228
299
|
};
|
|
229
|
-
export const validateTypeReferentialIntegrity = (helpers, checkReferentialIntegrity, inDecls, type, value) => {
|
|
300
|
+
export const validateTypeReferentialIntegrity = (helpers, checkEntityName, checkReferentialIntegrity, inDecls, type, value) => {
|
|
230
301
|
switch (type.kind) {
|
|
231
302
|
case NodeKind.ArrayType:
|
|
232
303
|
return Array.isArray(value)
|
|
233
|
-
? parallelizeErrors(value.map((item, index) => wrapErrorsIfAny(`at index ${key(index.toString(), helpers.useStyling)}`, validateTypeReferentialIntegrity(helpers, checkReferentialIntegrity, inDecls, type.items, item))))
|
|
304
|
+
? parallelizeErrors(value.map((item, index) => wrapErrorsIfAny(`at index ${key(index.toString(), helpers.useStyling)}`, validateTypeReferentialIntegrity(helpers, checkEntityName, checkReferentialIntegrity, inDecls, type.items, item))))
|
|
234
305
|
: [];
|
|
235
306
|
case NodeKind.ObjectType: {
|
|
236
307
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
@@ -242,7 +313,7 @@ export const validateTypeReferentialIntegrity = (helpers, checkReferentialIntegr
|
|
|
242
313
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
243
314
|
const prop = type.properties[propName];
|
|
244
315
|
if (value[propName] !== undefined) {
|
|
245
|
-
return wrapErrorsIfAny(`at object key ${key(`"${propName}"`, helpers.useStyling)}`, validateTypeReferentialIntegrity(helpers, checkReferentialIntegrity, inDecls, prop.type, value[propName]));
|
|
316
|
+
return wrapErrorsIfAny(`at object key ${key(`"${propName}"`, helpers.useStyling)}`, validateTypeReferentialIntegrity(helpers, checkEntityName, checkReferentialIntegrity, inDecls, prop.type, value[propName]));
|
|
246
317
|
}
|
|
247
318
|
return undefined;
|
|
248
319
|
}));
|
|
@@ -253,12 +324,12 @@ export const validateTypeReferentialIntegrity = (helpers, checkReferentialIntegr
|
|
|
253
324
|
case NodeKind.ReferenceIdentifierType:
|
|
254
325
|
return checkReferentialIntegrity(type.entity.name, value);
|
|
255
326
|
case NodeKind.IncludeIdentifierType:
|
|
256
|
-
return validateDeclReferentialIntegrity(helpers, checkReferentialIntegrity, inDecls, type.reference, type.args, value);
|
|
327
|
+
return validateDeclReferentialIntegrity(helpers, checkEntityName, checkReferentialIntegrity, inDecls, type.reference, type.args, value);
|
|
257
328
|
case NodeKind.NestedEntityMapType: {
|
|
258
329
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
259
330
|
return [];
|
|
260
331
|
}
|
|
261
|
-
return parallelizeErrors(Object.keys(value).map(propName => wrapErrorsIfAny(`at nested entity map ${entity(`"${type.name}"`, helpers.useStyling)} at key ${key(`"${propName}"`, helpers.useStyling)}`, validateTypeReferentialIntegrity(helpers, checkReferentialIntegrity, inDecls, type.type.value, value[propName]).concat(checkReferentialIntegrity(type.secondaryEntity.name, propName)))));
|
|
332
|
+
return parallelizeErrors(Object.keys(value).map(propName => wrapErrorsIfAny(`at nested entity map ${entity(`"${type.name}"`, helpers.useStyling)} at key ${key(`"${propName}"`, helpers.useStyling)}`, validateTypeReferentialIntegrity(helpers, checkEntityName, checkReferentialIntegrity, inDecls, type.type.value, value[propName]).concat(checkReferentialIntegrity(type.secondaryEntity.name, propName)))));
|
|
262
333
|
}
|
|
263
334
|
case NodeKind.EnumType: {
|
|
264
335
|
if (typeof value !== "object" ||
|
|
@@ -274,15 +345,16 @@ export const validateTypeReferentialIntegrity = (helpers, checkReferentialIntegr
|
|
|
274
345
|
type.values[enumCase].type !== null &&
|
|
275
346
|
enumCase in value
|
|
276
347
|
? parallelizeErrors([
|
|
277
|
-
wrapErrorsIfAny(`at enum case ${key(`"${enumCase}"`, helpers.useStyling)}`, validateTypeReferentialIntegrity(helpers, checkReferentialIntegrity, inDecls, type.values[enumCase].type, value[enumCase])),
|
|
348
|
+
wrapErrorsIfAny(`at enum case ${key(`"${enumCase}"`, helpers.useStyling)}`, validateTypeReferentialIntegrity(helpers, checkEntityName, checkReferentialIntegrity, inDecls, type.values[enumCase].type, value[enumCase])),
|
|
278
349
|
])
|
|
279
350
|
: [];
|
|
280
351
|
}
|
|
352
|
+
case NodeKind.StringType:
|
|
353
|
+
return validateMarkdownString(type, value, createReferentialAttributedStringValidator(checkEntityName, checkReferentialIntegrity, helpers.validationOptions));
|
|
281
354
|
case NodeKind.BooleanType:
|
|
282
355
|
case NodeKind.DateType:
|
|
283
356
|
case NodeKind.FloatType:
|
|
284
357
|
case NodeKind.IntegerType:
|
|
285
|
-
case NodeKind.StringType:
|
|
286
358
|
case NodeKind.ChildEntitiesType:
|
|
287
359
|
case NodeKind.TranslationObjectType:
|
|
288
360
|
return [];
|
|
@@ -35,7 +35,7 @@ export const createServer = (db, config) => {
|
|
|
35
35
|
defaultLocales,
|
|
36
36
|
locales: defaultLocales,
|
|
37
37
|
homeLayoutSections,
|
|
38
|
-
validationOptions: validationOptions ?? {},
|
|
38
|
+
validationOptions: { ...(validationOptions ?? {}), markdown: undefined },
|
|
39
39
|
};
|
|
40
40
|
app.use((req, _res, next) => {
|
|
41
41
|
debug("%s %s", req.method, req.originalUrl);
|
|
@@ -3,6 +3,7 @@ import type { FormatterOptions } from "../config.ts";
|
|
|
3
3
|
import { type EntityDecl } from "../schema/dsl/index.ts";
|
|
4
4
|
export declare const getFileNameForId: (id: string) => string;
|
|
5
5
|
export declare const getPathToInstance: (dataRoot: string, entityName: string, id: string) => string;
|
|
6
|
+
export declare const readInstance: (dataRoot: string, entity: EntityDecl, id: string) => Promise<string>;
|
|
6
7
|
export declare const writeInstance: (dataRoot: string, entity: EntityDecl, id: string, instance: InstanceContent, formatterOptions: Partial<FormatterOptions> | undefined) => Promise<void>;
|
|
7
8
|
export declare const deleteInstance: (dataRoot: string, entityName: string, id: string) => Promise<void>;
|
|
8
9
|
export declare const formatInstance: (entity: EntityDecl, instanceContent: InstanceContent, options: Partial<FormatterOptions> | undefined) => string;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import {} from "../schema/dsl/index.js";
|
|
4
4
|
import { formatValue } from "../schema/treeOperations/format.js";
|
|
5
5
|
export const getFileNameForId = (id) => `${id}.json`;
|
|
6
6
|
export const getPathToInstance = (dataRoot, entityName, id) => join(dataRoot, entityName, getFileNameForId(id));
|
|
7
|
+
export const readInstance = (dataRoot, entity, id) => readFile(getPathToInstance(dataRoot, entity.name, id), {
|
|
8
|
+
encoding: "utf-8",
|
|
9
|
+
});
|
|
7
10
|
export const writeInstance = (dataRoot, entity, id, instance, formatterOptions) => writeFile(getPathToInstance(dataRoot, entity.name, id), formatInstance(entity, instance, formatterOptions), {
|
|
8
11
|
encoding: "utf-8",
|
|
9
12
|
});
|
|
@@ -19,9 +19,9 @@ export const validateStringConstraints = (constraints, value) => parallelizeErro
|
|
|
19
19
|
? TypeError(`string does not match the pattern ${pattern.toString()}`)
|
|
20
20
|
: undefined;
|
|
21
21
|
})(),
|
|
22
|
-
(() => {
|
|
22
|
+
...(() => {
|
|
23
23
|
if (constraints.markdown === undefined) {
|
|
24
|
-
return
|
|
24
|
+
return [];
|
|
25
25
|
}
|
|
26
26
|
switch (constraints.markdown) {
|
|
27
27
|
case "inline":
|
package/dist/src/web/web.js
CHANGED
|
@@ -3404,7 +3404,7 @@ const inlineNode = combineParsers([
|
|
|
3404
3404
|
leafParser(defaultInlineSyntaxStartSequences)
|
|
3405
3405
|
]);
|
|
3406
3406
|
const inlineMarkdown = inlineNode.many();
|
|
3407
|
-
const
|
|
3407
|
+
const inline_parseInlineMarkdown = (syntax, keepSyntax = false, preserveEscapes = false)=>{
|
|
3408
3408
|
const results = inlineMarkdown.evalT({
|
|
3409
3409
|
indentation: 0,
|
|
3410
3410
|
keepSyntax,
|
|
@@ -3415,8 +3415,8 @@ const _parseInlineMarkdown = (syntax, keepSyntax = false, preserveEscapes = fals
|
|
|
3415
3415
|
if (remaining.length > 0) throw new Error(`Failed to parse the entire string. Remaining: "${remaining}"`);
|
|
3416
3416
|
return result;
|
|
3417
3417
|
};
|
|
3418
|
-
const parseInlineMarkdown = (syntax)=>
|
|
3419
|
-
const parseInlineMarkdownForSyntaxHighlighting = (syntax)=>
|
|
3418
|
+
const parseInlineMarkdown = (syntax)=>inline_parseInlineMarkdown(syntax, false);
|
|
3419
|
+
const parseInlineMarkdownForSyntaxHighlighting = (syntax)=>inline_parseInlineMarkdown(syntax, true);
|
|
3420
3420
|
const isNullish = (value)=>null == value;
|
|
3421
3421
|
const isNotNullish = (value)=>!isNullish(value);
|
|
3422
3422
|
const sortObjectKeysByIndex = (obj, keys)=>Object.fromEntries([
|
|
@@ -3660,8 +3660,21 @@ const tableCaptionRowSyntax = sepByKeepFlat(StateTParser.string("|#").then((star
|
|
|
3660
3660
|
},
|
|
3661
3661
|
block_asText(endSpace + newline)
|
|
3662
3662
|
]))))))), block_indentation);
|
|
3663
|
-
const tableHeaderRow = tableRow(tableContentCellGuard.then(()=>inlineMarkdown)
|
|
3664
|
-
|
|
3663
|
+
const tableHeaderRow = tableRow(tableContentCellGuard.then(()=>inlineMarkdown).htoken().then((content)=>StateTParser.lookahead(StateTParser.string("||")).then(()=>StateTParser.string("|")).many().map((span)=>({
|
|
3664
|
+
content,
|
|
3665
|
+
span: span.length > 0 ? span.length + 1 : void 0
|
|
3666
|
+
}))), false);
|
|
3667
|
+
const tableHeaderRowSyntax = tableRowSyntax(tableContentCellGuard.then(()=>inlineMarkdown.then((content)=>anySpacesT.then((trailingSpace)=>StateTParser.lookahead(StateTParser.string("||")).then(()=>StateTParser.string("|")).many().map((span)=>[
|
|
3668
|
+
...content,
|
|
3669
|
+
block_asText(trailingSpace),
|
|
3670
|
+
...span.length > 0 ? [
|
|
3671
|
+
{
|
|
3672
|
+
type: "syntax",
|
|
3673
|
+
blockType: "table",
|
|
3674
|
+
content: "|".repeat(span.length)
|
|
3675
|
+
}
|
|
3676
|
+
] : []
|
|
3677
|
+
])))), false);
|
|
3665
3678
|
const tableSeparatorRow = tableRow(tableHeaderSeparatorCell.htoken());
|
|
3666
3679
|
const tableSeparatorRowSyntax = tableRowSyntax(tableHeaderSeparatorCell.then((separator)=>anySpacesT.map((trailingSpaces)=>[
|
|
3667
3680
|
{
|
|
@@ -3723,10 +3736,6 @@ const tableSectionPlainSeparatorRowSyntax = tableRowSyntax(tableSectionSeparator
|
|
|
3723
3736
|
])));
|
|
3724
3737
|
const tableBodyRow = tableSectionSubheaderWithSeparatorRow.orFirstW(tableSectionPlainSeparatorRow).orFirstW(tableNormalRow);
|
|
3725
3738
|
const tableBodyRowSyntax = tableSectionSubheaderWithSeparatorRowSyntax.orFirstW(tableSectionPlainSeparatorRowSyntax).orFirstW(tableNormalRowSyntax);
|
|
3726
|
-
const mapCell = (cell)=>({
|
|
3727
|
-
type: "tableCell",
|
|
3728
|
-
content: trimLastNodeEnd(cell)
|
|
3729
|
-
});
|
|
3730
3739
|
const mapCellObj = (cell)=>omitUndefinedKeys({
|
|
3731
3740
|
type: "tableCell",
|
|
3732
3741
|
content: trimLastNodeEnd(cell.content),
|
|
@@ -3762,7 +3771,7 @@ const table = tableCaptionRow.then((caption)=>(0 === caption.length ? StateTPars
|
|
|
3762
3771
|
columns: separators.map((cell)=>omitUndefinedKeys({
|
|
3763
3772
|
alignment: getAlignmentFromSeparator(cell)
|
|
3764
3773
|
})),
|
|
3765
|
-
header: header.map(
|
|
3774
|
+
header: header.map(mapCellObj),
|
|
3766
3775
|
rows: sectionRows(rows)
|
|
3767
3776
|
}))))));
|
|
3768
3777
|
const tableSyntax = tableCaptionRowSyntax.then((caption)=>(0 === caption.length ? StateTParser.of([]) : block_indentation).then((indentBetweenCaptionAndHeader)=>tableHeaderRowSyntax.then((header)=>newlineT.then((headerNewline)=>tableSeparatorRowSyntax.then((separators)=>newlineT.then((separatorsNewline)=>sepBy1KeepFlat(tableBodyRowSyntax, newlineT.map(asSingleText)).map((rows)=>[
|
|
@@ -3790,7 +3799,7 @@ const finalBlockMarkdownSyntax = anyBlankLines.then((leadingsNewlines)=>blockMar
|
|
|
3790
3799
|
content: "\n"
|
|
3791
3800
|
} : node)
|
|
3792
3801
|
])));
|
|
3793
|
-
const
|
|
3802
|
+
const block_parseBlockMarkdown = (syntax, keepSyntax = false, preserveEscapes = false)=>{
|
|
3794
3803
|
const results = finalBlockMarkdown.evalT({
|
|
3795
3804
|
indentation: 0,
|
|
3796
3805
|
keepSyntax,
|
|
@@ -3812,7 +3821,7 @@ const _parseBlockMarkdown = (syntax, keepSyntax = false, preserveEscapes = false
|
|
|
3812
3821
|
];
|
|
3813
3822
|
return result;
|
|
3814
3823
|
};
|
|
3815
|
-
const parseBlockMarkdown = (syntax)=>
|
|
3824
|
+
const parseBlockMarkdown = (syntax)=>block_parseBlockMarkdown(syntax);
|
|
3816
3825
|
const parseBlockMarkdownForSyntaxHighlighting = (syntax)=>{
|
|
3817
3826
|
const results = finalBlockMarkdownSyntax.evalT({
|
|
3818
3827
|
indentation: 0,
|
|
@@ -5444,6 +5453,17 @@ const ReferenceIdentifierTypeInput = ({ type, value, instanceNamesByEntity, disa
|
|
|
5444
5453
|
]
|
|
5445
5454
|
});
|
|
5446
5455
|
};
|
|
5456
|
+
const getBuilderForNode = (builder, node)=>builder[node.type];
|
|
5457
|
+
const renderInline = (node, builder, ...args)=>{
|
|
5458
|
+
const parseInner = (node, ...args)=>getBuilderForNode(builder, node)(node, parseInner, ...args);
|
|
5459
|
+
return parseInner(node, ...args);
|
|
5460
|
+
};
|
|
5461
|
+
const render = (node, builder, ...args)=>{
|
|
5462
|
+
const parseInnerInline = (node, ...args)=>getBuilderForNode(builder, node)(node, parseInnerInline, ...args);
|
|
5463
|
+
const parseInner = (node, ...args)=>getBuilderForNode(builder, node)(node, parseInner, parseInnerInline, ...args);
|
|
5464
|
+
return parseInner(node, ...args);
|
|
5465
|
+
};
|
|
5466
|
+
const passThroughInnerFlat = (node, validateInner, ...args)=>node.content.flatMap((content)=>validateInner(content, ...args));
|
|
5447
5467
|
class InternalMarkdownError extends Error {
|
|
5448
5468
|
constructor(message){
|
|
5449
5469
|
super(message);
|
|
@@ -5471,31 +5491,79 @@ const positionInStringToColumnAndRow = (position, string)=>{
|
|
|
5471
5491
|
column
|
|
5472
5492
|
};
|
|
5473
5493
|
};
|
|
5474
|
-
const
|
|
5494
|
+
const emptyErrors = ()=>[];
|
|
5495
|
+
const inlineValidationMap = {
|
|
5496
|
+
attributed: (node, validateInner, options)=>passThroughInnerFlat(node, validateInner, options).concat(options?.validateAttributes?.(node.attributes) ?? []),
|
|
5497
|
+
bold: passThroughInnerFlat,
|
|
5498
|
+
italic: passThroughInnerFlat,
|
|
5499
|
+
link: passThroughInnerFlat,
|
|
5500
|
+
superscript: passThroughInnerFlat,
|
|
5501
|
+
code: emptyErrors,
|
|
5502
|
+
footnoteRef: emptyErrors,
|
|
5503
|
+
text: emptyErrors
|
|
5504
|
+
};
|
|
5505
|
+
const collectInlineValidationErrors = (nodes, options)=>nodes.flatMap((node)=>renderInline(node, inlineValidationMap, options));
|
|
5506
|
+
const blockValidationMap = {
|
|
5507
|
+
container: (node, validateInner, _validateInnerInline, options)=>node.content.flatMap((content)=>validateInner(content, options)),
|
|
5508
|
+
definitionList: (node, validateInner, validateInnerInline, options)=>node.content.flatMap((itemGroup)=>[
|
|
5509
|
+
...itemGroup.terms.flatMap((term)=>term.flatMap((content)=>validateInnerInline(content, options))),
|
|
5510
|
+
...itemGroup.descriptions.flatMap((description)=>description.flatMap((content)=>validateInner(content, options)))
|
|
5511
|
+
]),
|
|
5512
|
+
footnote: (node, validateInner, _validateInnerInline, options)=>node.content.flatMap((content)=>validateInner(content, options)),
|
|
5513
|
+
heading: (node, _validateInner, validateInnerInline, options)=>node.content.flatMap((content)=>validateInnerInline(content, options)),
|
|
5514
|
+
list: (node, validateInner, _validateInnerInline, options)=>node.content.flatMap((item)=>item.content.flatMap((content)=>validateInner(content, options))),
|
|
5515
|
+
paragraph: (node, _validateInner, validateInnerInline, options)=>node.content.flatMap((content)=>"break" === content.type ? [] : validateInnerInline(content, options)),
|
|
5516
|
+
table: (node, _validateInner, validateInnerInline, options)=>[
|
|
5517
|
+
...node.caption?.flatMap((captionLine)=>captionLine.flatMap((content)=>validateInnerInline(content, options))) ?? [],
|
|
5518
|
+
...node.header.flatMap((cell)=>cell.content.flatMap((content)=>validateInnerInline(content, options))),
|
|
5519
|
+
...checkTableRowsAreSections(node.rows) ? node.rows.flatMap((section)=>[
|
|
5520
|
+
...section.header?.flatMap((cell)=>cell.content.flatMap((content)=>validateInnerInline(content, options))) ?? [],
|
|
5521
|
+
...section.rows.flatMap((row)=>row.cells.flatMap((cell)=>cell.content.flatMap((content)=>validateInnerInline(content, options))))
|
|
5522
|
+
]) : node.rows.flatMap((row)=>row.cells.flatMap((cell)=>cell.content.flatMap((content)=>validateInnerInline(content, options))))
|
|
5523
|
+
]
|
|
5524
|
+
};
|
|
5525
|
+
const validationMap = {
|
|
5526
|
+
...inlineValidationMap,
|
|
5527
|
+
...blockValidationMap
|
|
5528
|
+
};
|
|
5529
|
+
const collectBlockValidationErrors = (nodes, options)=>nodes.flatMap((node)=>render(node, validationMap, options));
|
|
5530
|
+
const validateInline = (markdown, options)=>{
|
|
5475
5531
|
const results = inlineMarkdown.evalT({
|
|
5476
5532
|
indentation: 0,
|
|
5477
5533
|
keepSyntax: false,
|
|
5478
5534
|
preserveEscapes: false
|
|
5479
5535
|
}).parse(markdown);
|
|
5480
|
-
if (!isNotEmpty(results)) return
|
|
5481
|
-
|
|
5536
|
+
if (!isNotEmpty(results)) return [
|
|
5537
|
+
new InternalMarkdownError("Failed to parse inline Markdown")
|
|
5538
|
+
];
|
|
5539
|
+
const [nodes, remaining] = results[0];
|
|
5482
5540
|
if (remaining.length > 0) {
|
|
5483
5541
|
const { row, column } = positionInStringToColumnAndRow(markdown.length - remaining.length, markdown);
|
|
5484
|
-
return
|
|
5542
|
+
return [
|
|
5543
|
+
new MarkdownSyntaxError(row, column, remaining)
|
|
5544
|
+
];
|
|
5485
5545
|
}
|
|
5546
|
+
if (options?.validateAttributes !== void 0) return collectInlineValidationErrors(nodes, options);
|
|
5547
|
+
return [];
|
|
5486
5548
|
};
|
|
5487
|
-
const validate = (markdown)=>{
|
|
5549
|
+
const validate = (markdown, options)=>{
|
|
5488
5550
|
const results = finalBlockMarkdown.evalT({
|
|
5489
5551
|
indentation: 0,
|
|
5490
5552
|
keepSyntax: false,
|
|
5491
5553
|
preserveEscapes: false
|
|
5492
5554
|
}).parse(markdown);
|
|
5493
|
-
if (!isNotEmpty(results)
|
|
5494
|
-
|
|
5555
|
+
if (!isNotEmpty(results) || void 0 === results[0][0] && markdown.length > 0) return [
|
|
5556
|
+
new InternalMarkdownError("Failed to parse Markdown")
|
|
5557
|
+
];
|
|
5558
|
+
const [nodes = [], remaining] = results[0];
|
|
5495
5559
|
if (remaining.length > 0) {
|
|
5496
5560
|
const { row, column } = positionInStringToColumnAndRow(markdown.length - remaining.length, markdown);
|
|
5497
|
-
return
|
|
5561
|
+
return [
|
|
5562
|
+
new MarkdownSyntaxError(row, column, remaining)
|
|
5563
|
+
];
|
|
5498
5564
|
}
|
|
5565
|
+
if (options?.validateAttributes !== void 0) return collectBlockValidationErrors(nodes, options);
|
|
5566
|
+
return [];
|
|
5499
5567
|
};
|
|
5500
5568
|
const validateStringConstraints = (constraints, value)=>parallelizeErrors([
|
|
5501
5569
|
void 0 !== constraints.minLength && value.length < constraints.minLength ? RangeError(`expected a string with at least ${constraints.minLength.toString()} character${1 === constraints.minLength ? "" : "s"}, but got ${value.length.toString()} character${1 === value.length ? "" : "s"}`) : void 0,
|
|
@@ -5505,8 +5573,8 @@ const validateStringConstraints = (constraints, value)=>parallelizeErrors([
|
|
|
5505
5573
|
const pattern = "string" == typeof constraints.pattern ? new RegExp(constraints.pattern) : constraints.pattern;
|
|
5506
5574
|
return pattern.test(value) ? void 0 : TypeError(`string does not match the pattern ${pattern.toString()}`);
|
|
5507
5575
|
})(),
|
|
5508
|
-
(()=>{
|
|
5509
|
-
if (void 0 === constraints.markdown) return;
|
|
5576
|
+
...(()=>{
|
|
5577
|
+
if (void 0 === constraints.markdown) return [];
|
|
5510
5578
|
switch(constraints.markdown){
|
|
5511
5579
|
case "inline":
|
|
5512
5580
|
return validateInline(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsondb",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.9",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Lukas Obermann",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"typescript-eslint": "^8.70.1"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@elyukai/markdown": "^0.
|
|
56
|
+
"@elyukai/markdown": "^0.4.0",
|
|
57
57
|
"@elyukai/utils": "^0.3.6",
|
|
58
58
|
"@preact/signals": "^2.11.2",
|
|
59
59
|
"debug": "^4.4.3",
|