tsondb 0.20.6 → 0.20.8

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.
@@ -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,15 @@ 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
- await db.format();
154
+ if (passedArguments.command.options?.check === true) {
155
+ debug(`running command: format --check`);
156
+ await db.checkFormat();
157
+ }
158
+ else {
159
+ debug(`running command: format`);
160
+ await db.format();
161
+ }
150
162
  break;
151
163
  }
152
164
  case "name": {
@@ -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<void>;
117
141
  /**
118
142
  * Reloads the data from disk into memory.
119
143
  */
@@ -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,31 @@ 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
+ }
310
+ else {
311
+ console.error(styleText("red", `${counter.toFixed()} formatting error${counter === 1 ? "" : "s"} found`, {
312
+ stream: stderr,
313
+ }));
314
+ }
315
+ }
290
316
  /**
291
317
  * Reloads the data from disk into memory.
292
318
  */
@@ -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 validateStringConstraints(type, value);
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 undefined;
24
+ return [];
25
25
  }
26
26
  switch (constraints.markdown) {
27
27
  case "inline":
@@ -682,7 +682,7 @@ const request = async (method, url, options)=>{
682
682
  if (!response.ok) throw new Error(await response.text());
683
683
  return (options.getResult ?? ((response)=>{
684
684
  if (response.headers.get("Content-Type")?.startsWith("application/json") === true) return response.json();
685
- return Promise.resolve();
685
+ else return Promise.resolve();
686
686
  }))(response);
687
687
  };
688
688
  const api_getResource = async (url, options)=>request("GET", url, options);
@@ -897,8 +897,7 @@ function signals_core_module_a(i) {
897
897
  i.n = t;
898
898
  if (32 & signals_core_module_o.f) i.S(t);
899
899
  return t;
900
- }
901
- if (-1 === t.i) {
900
+ } else if (-1 === t.i) {
902
901
  t.i = 0;
903
902
  if (void 0 !== t.n) {
904
903
  t.n.p = t.p;
@@ -2410,7 +2409,7 @@ const splitStringParts = (str)=>[
2410
2409
  ...acc.slice(0, -1),
2411
2410
  lastPart + char
2412
2411
  ];
2413
- return [
2412
+ else return [
2414
2413
  ...acc,
2415
2414
  char
2416
2415
  ];
@@ -2420,7 +2419,7 @@ const splitStringParts = (str)=>[
2420
2419
  lastPart + char
2421
2420
  ];
2422
2421
  if ("" === lastPart) return acc;
2423
- return [
2422
+ else return [
2424
2423
  ...acc,
2425
2424
  ""
2426
2425
  ];
@@ -2453,7 +2452,7 @@ const getSerializedDisplayNameFromEntityInstance = (entity, instance, defaultNam
2453
2452
  name: getValueAtPath(instance, displayNamePath) ?? defaultName,
2454
2453
  localeId: locales[0]
2455
2454
  };
2456
- {
2455
+ else {
2457
2456
  const localeMapPath = displayNamePath.pathToLocaleMap ?? "translations";
2458
2457
  const localeMap = getValueAtPath(instance, localeMapPath);
2459
2458
  const pathInLocaleMap = displayNamePath.pathInLocaleMap ?? "name";
@@ -2467,6 +2466,7 @@ const getSerializedDisplayNameFromEntityInstance = (entity, instance, defaultNam
2467
2466
  name: possibleName,
2468
2467
  localeId: locale
2469
2468
  };
2469
+ else return;
2470
2470
  }, void 0) ?? {
2471
2471
  name: getValueAtPath(localeMap[availableLocales[0]], pathInLocaleMap) ?? defaultName
2472
2472
  };
@@ -3281,7 +3281,7 @@ const asText = (content)=>({
3281
3281
  type: "text",
3282
3282
  content
3283
3283
  });
3284
- const inline_text = (syntaxStartCharacters)=>anyStopOn(syntaxStartCharacters).then((result)=>getSyntaxSetting.map((keepSyntax)=>asText(keepSyntax ? result : parseEscapedCharacters(result))));
3284
+ const inline_text = (syntaxStartCharacters)=>anyStopOn(syntaxStartCharacters).then((result)=>StateTParser.getsT((state)=>state.keepSyntax || state.preserveEscapes).map((keepSyntax)=>asText(keepSyntax ? result : parseEscapedCharacters(result))));
3285
3285
  const codeDelimiter = state_syntax(StateTParser.string("`"));
3286
3286
  const codeText = anyStopOn([
3287
3287
  "`"
@@ -3404,18 +3404,19 @@ const inlineNode = combineParsers([
3404
3404
  leafParser(defaultInlineSyntaxStartSequences)
3405
3405
  ]);
3406
3406
  const inlineMarkdown = inlineNode.many();
3407
- const _parseInlineMarkdown = (syntax, keepSyntax = false)=>{
3407
+ const inline_parseInlineMarkdown = (syntax, keepSyntax = false, preserveEscapes = false)=>{
3408
3408
  const results = inlineMarkdown.evalT({
3409
3409
  indentation: 0,
3410
- keepSyntax
3410
+ keepSyntax,
3411
+ preserveEscapes
3411
3412
  }).parse(syntax);
3412
3413
  if (!isNotEmpty(results)) throw new Error("Failed to parse");
3413
3414
  const [result, remaining] = results[0];
3414
3415
  if (remaining.length > 0) throw new Error(`Failed to parse the entire string. Remaining: "${remaining}"`);
3415
3416
  return result;
3416
3417
  };
3417
- const parseInlineMarkdown = (syntax)=>_parseInlineMarkdown(syntax, false);
3418
- const parseInlineMarkdownForSyntaxHighlighting = (syntax)=>_parseInlineMarkdown(syntax, true);
3418
+ const parseInlineMarkdown = (syntax)=>inline_parseInlineMarkdown(syntax, false);
3419
+ const parseInlineMarkdownForSyntaxHighlighting = (syntax)=>inline_parseInlineMarkdown(syntax, true);
3419
3420
  const isNullish = (value)=>null == value;
3420
3421
  const isNotNullish = (value)=>!isNullish(value);
3421
3422
  const sortObjectKeysByIndex = (obj, keys)=>Object.fromEntries([
@@ -3659,8 +3660,21 @@ const tableCaptionRowSyntax = sepByKeepFlat(StateTParser.string("|#").then((star
3659
3660
  },
3660
3661
  block_asText(endSpace + newline)
3661
3662
  ]))))))), block_indentation);
3662
- const tableHeaderRow = tableRow(tableContentCellGuard.then(()=>inlineMarkdown), false);
3663
- const tableHeaderRowSyntax = tableRowSyntax(tableContentCellGuard.then(()=>inlineMarkdown), false);
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);
3664
3678
  const tableSeparatorRow = tableRow(tableHeaderSeparatorCell.htoken());
3665
3679
  const tableSeparatorRowSyntax = tableRowSyntax(tableHeaderSeparatorCell.then((separator)=>anySpacesT.map((trailingSpaces)=>[
3666
3680
  {
@@ -3722,10 +3736,6 @@ const tableSectionPlainSeparatorRowSyntax = tableRowSyntax(tableSectionSeparator
3722
3736
  ])));
3723
3737
  const tableBodyRow = tableSectionSubheaderWithSeparatorRow.orFirstW(tableSectionPlainSeparatorRow).orFirstW(tableNormalRow);
3724
3738
  const tableBodyRowSyntax = tableSectionSubheaderWithSeparatorRowSyntax.orFirstW(tableSectionPlainSeparatorRowSyntax).orFirstW(tableNormalRowSyntax);
3725
- const mapCell = (cell)=>({
3726
- type: "tableCell",
3727
- content: trimLastNodeEnd(cell)
3728
- });
3729
3739
  const mapCellObj = (cell)=>omitUndefinedKeys({
3730
3740
  type: "tableCell",
3731
3741
  content: trimLastNodeEnd(cell.content),
@@ -3750,7 +3760,7 @@ const sectionRows = (rows)=>{
3750
3760
  }
3751
3761
  return acc;
3752
3762
  }, []);
3753
- return rows.map((row)=>({
3763
+ else return rows.map((row)=>({
3754
3764
  type: "tableRow",
3755
3765
  cells: row.map(mapCellObj)
3756
3766
  }));
@@ -3761,7 +3771,7 @@ const table = tableCaptionRow.then((caption)=>(0 === caption.length ? StateTPars
3761
3771
  columns: separators.map((cell)=>omitUndefinedKeys({
3762
3772
  alignment: getAlignmentFromSeparator(cell)
3763
3773
  })),
3764
- header: header.map(mapCell),
3774
+ header: header.map(mapCellObj),
3765
3775
  rows: sectionRows(rows)
3766
3776
  }))))));
3767
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)=>[
@@ -3789,10 +3799,11 @@ const finalBlockMarkdownSyntax = anyBlankLines.then((leadingsNewlines)=>blockMar
3789
3799
  content: "\n"
3790
3800
  } : node)
3791
3801
  ])));
3792
- const parseBlockMarkdown = (syntax)=>{
3802
+ const block_parseBlockMarkdown = (syntax, keepSyntax = false, preserveEscapes = false)=>{
3793
3803
  const results = finalBlockMarkdown.evalT({
3794
3804
  indentation: 0,
3795
- keepSyntax: false
3805
+ keepSyntax,
3806
+ preserveEscapes
3796
3807
  }).parse(syntax);
3797
3808
  if (!isNotEmpty(results)) throw new Error("Failed to parse");
3798
3809
  const [result = [], remaining] = results[0];
@@ -3810,10 +3821,12 @@ const parseBlockMarkdown = (syntax)=>{
3810
3821
  ];
3811
3822
  return result;
3812
3823
  };
3824
+ const parseBlockMarkdown = (syntax)=>block_parseBlockMarkdown(syntax);
3813
3825
  const parseBlockMarkdownForSyntaxHighlighting = (syntax)=>{
3814
3826
  const results = finalBlockMarkdownSyntax.evalT({
3815
3827
  indentation: 0,
3816
- keepSyntax: true
3828
+ keepSyntax: true,
3829
+ preserveEscapes: false
3817
3830
  }).parse(syntax);
3818
3831
  if (!isNotEmpty(results)) throw new Error("Failed to parse");
3819
3832
  const [result, remaining] = results[0];
@@ -5440,6 +5453,17 @@ const ReferenceIdentifierTypeInput = ({ type, value, instanceNamesByEntity, disa
5440
5453
  ]
5441
5454
  });
5442
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));
5443
5467
  class InternalMarkdownError extends Error {
5444
5468
  constructor(message){
5445
5469
  super(message);
@@ -5467,29 +5491,79 @@ const positionInStringToColumnAndRow = (position, string)=>{
5467
5491
  column
5468
5492
  };
5469
5493
  };
5470
- const validateInline = (markdown)=>{
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)=>{
5471
5531
  const results = inlineMarkdown.evalT({
5472
5532
  indentation: 0,
5473
- keepSyntax: false
5533
+ keepSyntax: false,
5534
+ preserveEscapes: false
5474
5535
  }).parse(markdown);
5475
- if (!isNotEmpty(results)) return new InternalMarkdownError("Failed to parse inline Markdown");
5476
- const [_, remaining] = results[0];
5536
+ if (!isNotEmpty(results)) return [
5537
+ new InternalMarkdownError("Failed to parse inline Markdown")
5538
+ ];
5539
+ const [nodes, remaining] = results[0];
5477
5540
  if (remaining.length > 0) {
5478
5541
  const { row, column } = positionInStringToColumnAndRow(markdown.length - remaining.length, markdown);
5479
- return new MarkdownSyntaxError(row, column, remaining);
5542
+ return [
5543
+ new MarkdownSyntaxError(row, column, remaining)
5544
+ ];
5480
5545
  }
5546
+ if (options?.validateAttributes !== void 0) return collectInlineValidationErrors(nodes, options);
5547
+ return [];
5481
5548
  };
5482
- const validate = (markdown)=>{
5549
+ const validate = (markdown, options)=>{
5483
5550
  const results = finalBlockMarkdown.evalT({
5484
5551
  indentation: 0,
5485
- keepSyntax: false
5552
+ keepSyntax: false,
5553
+ preserveEscapes: false
5486
5554
  }).parse(markdown);
5487
- if (!isNotEmpty(results)) return new InternalMarkdownError("Failed to parse Markdown");
5488
- const [_, remaining] = results[0];
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];
5489
5559
  if (remaining.length > 0) {
5490
5560
  const { row, column } = positionInStringToColumnAndRow(markdown.length - remaining.length, markdown);
5491
- return new MarkdownSyntaxError(row, column, remaining);
5561
+ return [
5562
+ new MarkdownSyntaxError(row, column, remaining)
5563
+ ];
5492
5564
  }
5565
+ if (options?.validateAttributes !== void 0) return collectBlockValidationErrors(nodes, options);
5566
+ return [];
5493
5567
  };
5494
5568
  const validateStringConstraints = (constraints, value)=>parallelizeErrors([
5495
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,
@@ -5499,8 +5573,8 @@ const validateStringConstraints = (constraints, value)=>parallelizeErrors([
5499
5573
  const pattern = "string" == typeof constraints.pattern ? new RegExp(constraints.pattern) : constraints.pattern;
5500
5574
  return pattern.test(value) ? void 0 : TypeError(`string does not match the pattern ${pattern.toString()}`);
5501
5575
  })(),
5502
- (()=>{
5503
- if (void 0 === constraints.markdown) return;
5576
+ ...(()=>{
5577
+ if (void 0 === constraints.markdown) return [];
5504
5578
  switch(constraints.markdown){
5505
5579
  case "inline":
5506
5580
  return validateInline(value);
@@ -5515,7 +5589,7 @@ const BlockMarkdownHighlighting = ({ node })=>{
5515
5589
  if ("syntax" !== node.type) return /*#__PURE__*/ jsxRuntime_module_u(InlineMarkdown, {
5516
5590
  node: node
5517
5591
  });
5518
- switch(node.blockType){
5592
+ else switch(node.blockType){
5519
5593
  case "heading":
5520
5594
  return /*#__PURE__*/ jsxRuntime_module_u("span", {
5521
5595
  class: "heading-marker",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsondb",
3
- "version": "0.20.6",
3
+ "version": "0.20.8",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "Lukas Obermann",
@@ -37,23 +37,23 @@
37
37
  "devDependencies": {
38
38
  "@eslint/js": "^9.39.2",
39
39
  "@rsbuild/plugin-preact": "^2.1.0",
40
- "@rslib/core": "^1.0.0",
40
+ "@rslib/core": "^1.0.1",
41
41
  "@types/debug": "^4.1.13",
42
42
  "@types/express": "^5.0.6",
43
- "@types/node": "^22.20.2",
43
+ "@types/node": "^26.6.2",
44
44
  "@typescript/native": "npm:typescript@^7.0.2",
45
- "commit-and-tag-version": "^13.2.0",
45
+ "commit-and-tag-version": "^13.2.1",
46
46
  "eslint": "^9.39.4",
47
47
  "eslint-plugin-react": "^7.37.5",
48
48
  "eslint-plugin-react-hooks": "^7.1.1",
49
49
  "globals": "^17.12.0",
50
- "prettier": "^3.9.6",
51
- "tsx": "^4.23.13",
50
+ "prettier": "^3.9.8",
51
+ "tsx": "^4.23.15",
52
52
  "typescript": "npm:@typescript/typescript6@^6.0.2",
53
- "typescript-eslint": "^8.70.0"
53
+ "typescript-eslint": "^8.70.1"
54
54
  },
55
55
  "dependencies": {
56
- "@elyukai/markdown": "^0.3.3",
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",