tsondb 0.20.4 → 0.20.6
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 +1 -1
- package/dist/src/node/config.d.ts +10 -0
- package/dist/src/node/index.d.ts +5 -0
- package/dist/src/node/index.js +4 -2
- package/dist/src/node/schema/generatedTypeHelpers.d.ts +3 -3
- package/dist/src/node/schema/treeOperations/format.d.ts +2 -1
- package/dist/src/node/schema/treeOperations/format.js +24 -7
- package/dist/src/node/server/index.d.ts +2 -2
- package/dist/src/node/server/index.js +2 -1
- package/dist/src/node/utils/databaseOnDisk.d.ts +2 -1
- package/dist/src/node/utils/databaseOnDisk.js +12 -12
- package/dist/src/node/utils/files.d.ts +3 -2
- package/dist/src/node/utils/files.js +2 -2
- package/dist/src/node/utils/unique.js +3 -3
- package/dist/src/shared/schema/utils/keyPath.d.ts +1 -0
- package/dist/src/shared/schema/utils/keyPath.js +2 -1
- package/dist/src/shared/validation/string.d.ts +2 -0
- package/dist/src/shared/validation/string.js +15 -0
- package/dist/src/web/web.js +289 -227
- package/package.json +16 -16
- package/public/css/styles.css +87 -3
|
@@ -120,7 +120,7 @@ else {
|
|
|
120
120
|
debug(`running command: serve`);
|
|
121
121
|
validateConfigForServer(config);
|
|
122
122
|
const db = await createDB(config, config.validationOptions, false);
|
|
123
|
-
createServer(db, config
|
|
123
|
+
createServer(db, config);
|
|
124
124
|
break;
|
|
125
125
|
}
|
|
126
126
|
case "validate": {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FormatterOptions as MarkdownFormatterOptions } from "@elyukai/markdown/format";
|
|
1
2
|
import type { DefaultTSONDBTypes, ValidationOptions } from "./index.ts";
|
|
2
3
|
import type { Output } from "./output.ts";
|
|
3
4
|
import type { EntityDecl } from "./schema/dsl/index.ts";
|
|
@@ -18,6 +19,13 @@ export type Config<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> = {
|
|
|
18
19
|
customStylesheetPath?: string;
|
|
19
20
|
customAssetsPath?: string;
|
|
20
21
|
validationOptions?: Partial<ValidationOptions>;
|
|
22
|
+
formattingOptions?: Partial<FormatterOptions>;
|
|
23
|
+
};
|
|
24
|
+
export type FormatterOptions = {
|
|
25
|
+
/**
|
|
26
|
+
* If set to false, Markdown formatting will be disabled. Otherwise, the default or, if provided, provided Markdown formatting options will be used to format Markdown strings.
|
|
27
|
+
*/
|
|
28
|
+
markdown?: Partial<MarkdownFormatterOptions> | false;
|
|
21
29
|
};
|
|
22
30
|
export type ServerOptions = {
|
|
23
31
|
port: number;
|
|
@@ -41,6 +49,7 @@ export declare const validateConfigForGeneration: (config: Config) => asserts co
|
|
|
41
49
|
export type DataConfig<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> = Config<T> & {
|
|
42
50
|
schema: Schema<T>;
|
|
43
51
|
dataRootPath: string;
|
|
52
|
+
formattingOptions?: Partial<FormatterOptions>;
|
|
44
53
|
};
|
|
45
54
|
export declare const validateConfigForData: (config: Config) => asserts config is DataConfig;
|
|
46
55
|
/**
|
|
@@ -53,6 +62,7 @@ export type ServerConfig<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> = Da
|
|
|
53
62
|
validationOptions?: Partial<ValidationOptions>;
|
|
54
63
|
customStylesheetPath?: string;
|
|
55
64
|
customAssetsPath?: string;
|
|
65
|
+
formattingOptions?: Partial<FormatterOptions>;
|
|
56
66
|
};
|
|
57
67
|
/**
|
|
58
68
|
* The configuration type required for validating the contents of the database.
|
package/dist/src/node/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { InstanceContainer, InstanceContainerOverview } from "../shared/utils/instances.ts";
|
|
2
|
+
import type { FormatterOptions } from "./config.ts";
|
|
2
3
|
import { Git } from "./git.js";
|
|
3
4
|
import type { Output } from "./output.ts";
|
|
4
5
|
import { type EntityDecl } from "./schema/dsl/index.ts";
|
|
@@ -42,6 +43,10 @@ export interface TSONDBOptions<T extends DefaultTSONDBTypes = DefaultTSONDBTypes
|
|
|
42
43
|
* Options for validating the data in the database.
|
|
43
44
|
*/
|
|
44
45
|
validationOptions?: Partial<ValidationOptions>;
|
|
46
|
+
/**
|
|
47
|
+
* Options for formatting the data in the database.
|
|
48
|
+
*/
|
|
49
|
+
formatterOptions?: Partial<FormatterOptions>;
|
|
45
50
|
}
|
|
46
51
|
/**
|
|
47
52
|
* Options for validating the data in the database.
|
package/dist/src/node/index.js
CHANGED
|
@@ -96,6 +96,7 @@ export class TSONDB {
|
|
|
96
96
|
#git;
|
|
97
97
|
#referencesToInstances;
|
|
98
98
|
#validationOptions;
|
|
99
|
+
#formatterOptions;
|
|
99
100
|
#gitWrapper;
|
|
100
101
|
#locked = false;
|
|
101
102
|
constructor(options) {
|
|
@@ -106,6 +107,7 @@ export class TSONDB {
|
|
|
106
107
|
this.#git = options.git;
|
|
107
108
|
this.#referencesToInstances = options.referencesToInstances;
|
|
108
109
|
this.#validationOptions = options.validationOptions;
|
|
110
|
+
this.#formatterOptions = options.formatterOptions;
|
|
109
111
|
this.#gitWrapper = Lazy.of(() => this.#git
|
|
110
112
|
? new Git(this, this.#git.client, this.#git.root, this.#dataRootPath, () => this.#data, (data) => {
|
|
111
113
|
this.#data = data;
|
|
@@ -281,7 +283,7 @@ export class TSONDB {
|
|
|
281
283
|
await this.#data.forEachInstance(async (entityName, instance) => {
|
|
282
284
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
283
285
|
const entity = this.#schema.getEntity(entityName);
|
|
284
|
-
await writeInstance(this.#dataRootPath, entity, instance.id, instance.content);
|
|
286
|
+
await writeInstance(this.#dataRootPath, entity, instance.id, instance.content, this.#formatterOptions);
|
|
285
287
|
}, true);
|
|
286
288
|
debug("All data is formatted");
|
|
287
289
|
}
|
|
@@ -331,7 +333,7 @@ export class TSONDB {
|
|
|
331
333
|
throw new AggregateError(errors, "Validation errors occurred");
|
|
332
334
|
}
|
|
333
335
|
debug("Applying changes to disk ...");
|
|
334
|
-
const diskResult = await applyStepsToDisk(this.#dataRootPath, steps);
|
|
336
|
+
const diskResult = await applyStepsToDisk(this.#dataRootPath, steps, this.#formatterOptions);
|
|
335
337
|
if (isError(diskResult)) {
|
|
336
338
|
debug("Error applying changes to disk: %s", diskResult.error.message);
|
|
337
339
|
throw diskResult.error;
|
|
@@ -55,16 +55,16 @@ export type RegisteredEnumOrTypeAlias<Name extends string, T = Register> = Regis
|
|
|
55
55
|
*/
|
|
56
56
|
export declare const Case: {
|
|
57
57
|
<T extends string>(type: T): Case<T>;
|
|
58
|
-
<T extends string, V extends
|
|
58
|
+
<T extends string, V extends AnyNonNullish | null>(type: T, value: V): Case<T, V>;
|
|
59
59
|
};
|
|
60
60
|
/**
|
|
61
61
|
* Type representing an enum case object.
|
|
62
62
|
*/
|
|
63
63
|
export type Case<K extends string, T = undefined> = {
|
|
64
|
-
[P in K]: T extends
|
|
64
|
+
[P in K]: T extends AnyNonNullish | null ? {
|
|
65
65
|
[Key in ENUM_DISCRIMINATOR_KEY]: P;
|
|
66
66
|
} & {
|
|
67
|
-
[Key in P]: Extract<T,
|
|
67
|
+
[Key in P]: Extract<T, AnyNonNullish | null>;
|
|
68
68
|
} : {
|
|
69
69
|
[Key in ENUM_DISCRIMINATOR_KEY]: P;
|
|
70
70
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { FormatterOptions } from "../../config.ts";
|
|
1
2
|
import type { Type } from "../dsl/index.ts";
|
|
2
3
|
/**
|
|
3
4
|
* Format the structure of a value to always look the same when serialized as JSON.
|
|
4
5
|
*/
|
|
5
|
-
export declare const formatValue: (type: Type, value: unknown) => unknown;
|
|
6
|
+
export declare const formatValue: (type: Type, value: unknown, options: Partial<FormatterOptions> | undefined) => unknown;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { format as formatMarkdown, formatInline as formatMarkdownInline, } from "@elyukai/markdown/format";
|
|
1
2
|
import { mapObject, sortObjectKeys, sortObjectKeysByIndex } from "@elyukai/utils/object";
|
|
2
3
|
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
3
4
|
import { ENUM_DISCRIMINATOR_KEY } from "../../../shared/schema/declarations/EnumDecl.js";
|
|
@@ -11,19 +12,21 @@ const formatTranslationObjectValue = (type, value) => typeof value === "object"
|
|
|
11
12
|
/**
|
|
12
13
|
* Format the structure of a value to always look the same when serialized as JSON.
|
|
13
14
|
*/
|
|
14
|
-
export const formatValue = (type, value) => {
|
|
15
|
+
export const formatValue = (type, value, options) => {
|
|
15
16
|
switch (type.kind) {
|
|
16
17
|
case NodeKind.ArrayType:
|
|
17
|
-
return Array.isArray(value)
|
|
18
|
+
return Array.isArray(value)
|
|
19
|
+
? value.map(item => formatValue(type.items, item, options))
|
|
20
|
+
: value;
|
|
18
21
|
case NodeKind.ObjectType:
|
|
19
22
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
20
|
-
? sortObjectKeysByIndex(mapObject(value, (item, key) => type.properties[key] ? formatValue(type.properties[key].type, item) : item), Object.keys(type.properties))
|
|
23
|
+
? sortObjectKeysByIndex(mapObject(value, (item, key) => type.properties[key] ? formatValue(type.properties[key].type, item, options) : item), Object.keys(type.properties))
|
|
21
24
|
: value;
|
|
22
25
|
case NodeKind.IncludeIdentifierType:
|
|
23
|
-
return formatValue(type.reference.type.value, value);
|
|
26
|
+
return formatValue(type.reference.type.value, value, options);
|
|
24
27
|
case NodeKind.NestedEntityMapType:
|
|
25
28
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
26
|
-
? sortObjectKeys(mapObject(value, item => formatValue(type.type.value, item)))
|
|
29
|
+
? sortObjectKeys(mapObject(value, item => formatValue(type.type.value, item, options)))
|
|
27
30
|
: value;
|
|
28
31
|
case NodeKind.EnumType: {
|
|
29
32
|
if (typeof value === "object" &&
|
|
@@ -38,7 +41,7 @@ export const formatValue = (type, value) => {
|
|
|
38
41
|
[ENUM_DISCRIMINATOR_KEY]: caseName,
|
|
39
42
|
...(caseValue == null || caseType == null
|
|
40
43
|
? {}
|
|
41
|
-
: { [caseName]: formatValue(caseType, caseValue) }),
|
|
44
|
+
: { [caseName]: formatValue(caseType, caseValue, options) }),
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
return value;
|
|
@@ -47,11 +50,25 @@ export const formatValue = (type, value) => {
|
|
|
47
50
|
return Array.isArray(value) ? value.toSorted() : value;
|
|
48
51
|
case NodeKind.TranslationObjectType:
|
|
49
52
|
return formatTranslationObjectValue(type.properties, value);
|
|
53
|
+
case NodeKind.StringType: {
|
|
54
|
+
if (options?.markdown === false) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
switch (type.markdown) {
|
|
58
|
+
case "inline":
|
|
59
|
+
return typeof value === "string" ? formatMarkdownInline(value.trim()) : value;
|
|
60
|
+
case "block":
|
|
61
|
+
return typeof value === "string"
|
|
62
|
+
? formatMarkdown(value.trimEnd(), options?.markdown)
|
|
63
|
+
: value;
|
|
64
|
+
default:
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
50
68
|
case NodeKind.BooleanType:
|
|
51
69
|
case NodeKind.DateType:
|
|
52
70
|
case NodeKind.FloatType:
|
|
53
71
|
case NodeKind.IntegerType:
|
|
54
|
-
case NodeKind.StringType:
|
|
55
72
|
case NodeKind.TypeArgumentType:
|
|
56
73
|
case NodeKind.ReferenceIdentifierType:
|
|
57
74
|
return value;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HomeLayoutSection } from "../config.ts";
|
|
1
|
+
import type { HomeLayoutSection, ServerConfig } from "../config.ts";
|
|
2
2
|
import type { TSONDB, ValidationOptions } from "../index.ts";
|
|
3
3
|
export type ServerOptions = {
|
|
4
4
|
port: number;
|
|
@@ -16,4 +16,4 @@ declare global {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
export declare const createServer: (db: TSONDB,
|
|
19
|
+
export declare const createServer: (db: TSONDB, config: Omit<ServerConfig, "locales">) => void;
|
|
@@ -15,7 +15,8 @@ const staticNodeModule = (moduleName) => {
|
|
|
15
15
|
}
|
|
16
16
|
return express.static(dirname(pathToPackageJson));
|
|
17
17
|
};
|
|
18
|
-
export const createServer = (db,
|
|
18
|
+
export const createServer = (db, config) => {
|
|
19
|
+
const { serverOptions: options, homeLayoutSections, validationOptions, customAssetsPath, customStylesheetPath, } = config;
|
|
19
20
|
const { port } = { ...defaultOptions, ...options };
|
|
20
21
|
const app = express();
|
|
21
22
|
app.use(express.static(join(import.meta.dirname, "../../../../public")));
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import type { FormatterOptions } from "../config.ts";
|
|
1
2
|
import type { TransactionStep } from "../transaction.ts";
|
|
2
|
-
export declare const applyStepsToDisk: (root: string, steps: TransactionStep[]) => Promise<import("@elyukai/utils/result").
|
|
3
|
+
export declare const applyStepsToDisk: (root: string, steps: TransactionStep[], formatterOptions: Partial<FormatterOptions> | undefined) => Promise<import("@elyukai/utils/result").Error<Error> | import("@elyukai/utils/result").Ok<void>>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { error, isError, ok } from "@elyukai/utils/result";
|
|
2
2
|
import * as DatabaseFilesystem from "./files.js";
|
|
3
|
-
const setInstanceOnDisk = async (root, entity, instanceId, instanceContent) => {
|
|
3
|
+
const setInstanceOnDisk = async (root, entity, instanceId, instanceContent, formatterOptions) => {
|
|
4
4
|
try {
|
|
5
|
-
await DatabaseFilesystem.writeInstance(root, entity, instanceId, instanceContent);
|
|
5
|
+
await DatabaseFilesystem.writeInstance(root, entity, instanceId, instanceContent, formatterOptions);
|
|
6
6
|
return ok();
|
|
7
7
|
}
|
|
8
8
|
catch (e) {
|
|
@@ -18,41 +18,41 @@ const deleteInstanceOnDisk = async (root, entityName, instanceId) => {
|
|
|
18
18
|
return error(e);
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
|
-
const rollbackChanges = async (root, steps) => {
|
|
21
|
+
const rollbackChanges = async (root, steps, formatterOptions) => {
|
|
22
22
|
for (const step of steps) {
|
|
23
|
-
const res = await runReverseStepAction(root, step);
|
|
23
|
+
const res = await runReverseStepAction(root, step, formatterOptions);
|
|
24
24
|
if (isError(res)) {
|
|
25
25
|
return res;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
return ok();
|
|
29
29
|
};
|
|
30
|
-
const runStepAction = (root, step) => {
|
|
30
|
+
const runStepAction = (root, step, formatterOptions) => {
|
|
31
31
|
switch (step.kind) {
|
|
32
32
|
case "create":
|
|
33
33
|
case "update":
|
|
34
|
-
return setInstanceOnDisk(root, step.entity, step.instanceId, step.instanceContent);
|
|
34
|
+
return setInstanceOnDisk(root, step.entity, step.instanceId, step.instanceContent, formatterOptions);
|
|
35
35
|
case "delete":
|
|
36
36
|
return deleteInstanceOnDisk(root, step.entity.name, step.instanceId);
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
-
const runReverseStepAction = (root, step) => {
|
|
39
|
+
const runReverseStepAction = (root, step, formatterOptions) => {
|
|
40
40
|
switch (step.kind) {
|
|
41
41
|
case "create":
|
|
42
42
|
return deleteInstanceOnDisk(root, step.entity.name, step.instanceId);
|
|
43
43
|
case "update":
|
|
44
|
-
return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance);
|
|
44
|
+
return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance, formatterOptions);
|
|
45
45
|
case "delete":
|
|
46
|
-
return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance);
|
|
46
|
+
return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance, formatterOptions);
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
-
export const applyStepsToDisk = async (root, steps) => {
|
|
49
|
+
export const applyStepsToDisk = async (root, steps, formatterOptions) => {
|
|
50
50
|
for (let i = 0; i < steps.length; i++) {
|
|
51
51
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
52
52
|
const step = steps[i];
|
|
53
|
-
const res = await runStepAction(root, step);
|
|
53
|
+
const res = await runStepAction(root, step, formatterOptions);
|
|
54
54
|
if (isError(res)) {
|
|
55
|
-
await rollbackChanges(root, steps.slice(0, i));
|
|
55
|
+
await rollbackChanges(root, steps.slice(0, i), formatterOptions);
|
|
56
56
|
return res;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { InstanceContent } from "../../shared/utils/instances.ts";
|
|
2
|
+
import type { FormatterOptions } from "../config.ts";
|
|
2
3
|
import { type EntityDecl } from "../schema/dsl/index.ts";
|
|
3
4
|
export declare const getFileNameForId: (id: string) => string;
|
|
4
5
|
export declare const getPathToInstance: (dataRoot: string, entityName: string, id: string) => string;
|
|
5
|
-
export declare const writeInstance: (dataRoot: string, entity: EntityDecl, id: string, instance: InstanceContent) => Promise<void>;
|
|
6
|
+
export declare const writeInstance: (dataRoot: string, entity: EntityDecl, id: string, instance: InstanceContent, formatterOptions: Partial<FormatterOptions> | undefined) => Promise<void>;
|
|
6
7
|
export declare const deleteInstance: (dataRoot: string, entityName: string, id: string) => Promise<void>;
|
|
7
|
-
export declare const formatInstance: (entity: EntityDecl, instanceContent: InstanceContent) => string;
|
|
8
|
+
export declare const formatInstance: (entity: EntityDecl, instanceContent: InstanceContent, options: Partial<FormatterOptions> | undefined) => string;
|
|
@@ -4,8 +4,8 @@ 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 writeInstance = (dataRoot, entity, id, instance) => writeFile(getPathToInstance(dataRoot, entity.name, id), formatInstance(entity, instance), {
|
|
7
|
+
export const writeInstance = (dataRoot, entity, id, instance, formatterOptions) => writeFile(getPathToInstance(dataRoot, entity.name, id), formatInstance(entity, instance, formatterOptions), {
|
|
8
8
|
encoding: "utf-8",
|
|
9
9
|
});
|
|
10
10
|
export const deleteInstance = (dataRoot, entityName, id) => rm(getPathToInstance(dataRoot, entityName, id));
|
|
11
|
-
export const formatInstance = (entity, instanceContent) => JSON.stringify(formatValue(entity.type.value, instanceContent), undefined, 2) + "\n";
|
|
11
|
+
export const formatInstance = (entity, instanceContent, options) => JSON.stringify(formatValue(entity.type.value, instanceContent, options), undefined, 2) + "\n";
|
|
@@ -2,7 +2,7 @@ import { anySameIndices } from "@elyukai/utils/array/filters";
|
|
|
2
2
|
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
|
-
import { getValueAtKeyPath, renderKeyPath } from "../../shared/schema/utils/keyPath.js";
|
|
5
|
+
import { getValueAtKeyPath, getValueAtKeyPathIfDefined, renderKeyPath, } from "../../shared/schema/utils/keyPath.js";
|
|
6
6
|
import {} from "./databaseInMemory.js";
|
|
7
7
|
export class UniqueConstraintError extends Error {
|
|
8
8
|
parts;
|
|
@@ -42,7 +42,7 @@ export const checkUniqueConstraintsForEntity = (entity, instances, instanceOverv
|
|
|
42
42
|
const index = instances.flatMap(({ id, content }) => flatCombine(normalizedConstraint.map(elem => {
|
|
43
43
|
if ("keyPath" in elem) {
|
|
44
44
|
return [
|
|
45
|
-
|
|
45
|
+
getValueAtKeyPathIfDefined(content, elem.keyPath) ??
|
|
46
46
|
(elem.keyPathFallback
|
|
47
47
|
? getValueAtKeyPath(content, elem.keyPathFallback)
|
|
48
48
|
: undefined),
|
|
@@ -51,7 +51,7 @@ export const checkUniqueConstraintsForEntity = (entity, instances, instanceOverv
|
|
|
51
51
|
else {
|
|
52
52
|
return Object.entries(getValueAtKeyPath(content, elem.entityMapKeyPath)).map(([nestedId, nestedContent]) => [
|
|
53
53
|
nestedId,
|
|
54
|
-
|
|
54
|
+
getValueAtKeyPathIfDefined(nestedContent, elem.keyPathInEntityMap) ??
|
|
55
55
|
(elem.keyPathInEntityMapFallback
|
|
56
56
|
? getValueAtKeyPath(nestedContent, elem.keyPathInEntityMapFallback)
|
|
57
57
|
: undefined),
|
|
@@ -14,3 +14,4 @@ export declare const renderKeyPath: (keyPath: KeyPath) => string;
|
|
|
14
14
|
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
|
+
export declare const getValueAtKeyPathIfDefined: (value: unknown, keyPath: KeyPath) => unknown;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isNotEmpty } from "@elyukai/utils/array/nonEmpty";
|
|
2
2
|
import { error, isOk, ok, reduce } from "@elyukai/utils/result";
|
|
3
|
-
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
3
|
+
import { assertExhaustive, trySafe } from "@elyukai/utils/typeSafety";
|
|
4
4
|
const intPattern = /^(?:0|[1-9][0-9])*$/;
|
|
5
5
|
export const parseKeyPath = (keyPath) => normalizeKeyPath(keyPath).map(part => {
|
|
6
6
|
if (intPattern.test(part)) {
|
|
@@ -63,3 +63,4 @@ export const getValueAtKeyPath = (value, keyPath, throwOnPathMismatch = false) =
|
|
|
63
63
|
? ok(value[name])
|
|
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
|
+
export const getValueAtKeyPathIfDefined = (value, keyPath) => trySafe(() => getValueAtKeyPath(value, keyPath, true));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { MarkdownStringOption } from "../schema/types/StringType.ts";
|
|
1
2
|
export interface StringConstraints {
|
|
2
3
|
minLength?: number;
|
|
3
4
|
maxLength?: number;
|
|
4
5
|
pattern?: string | RegExp;
|
|
6
|
+
markdown?: MarkdownStringOption;
|
|
5
7
|
}
|
|
6
8
|
export declare const validateStringConstraints: (constraints: StringConstraints, value: string) => Error[];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { validate, validateInline } from "@elyukai/markdown/validate";
|
|
2
|
+
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
1
3
|
import { parallelizeErrors } from "../utils/validation.js";
|
|
2
4
|
export const validateStringConstraints = (constraints, value) => parallelizeErrors([
|
|
3
5
|
constraints.minLength !== undefined && value.length < constraints.minLength
|
|
@@ -17,4 +19,17 @@ export const validateStringConstraints = (constraints, value) => parallelizeErro
|
|
|
17
19
|
? TypeError(`string does not match the pattern ${pattern.toString()}`)
|
|
18
20
|
: undefined;
|
|
19
21
|
})(),
|
|
22
|
+
(() => {
|
|
23
|
+
if (constraints.markdown === undefined) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
switch (constraints.markdown) {
|
|
27
|
+
case "inline":
|
|
28
|
+
return validateInline(value);
|
|
29
|
+
case "block":
|
|
30
|
+
return validate(value);
|
|
31
|
+
default:
|
|
32
|
+
return assertExhaustive(constraints.markdown);
|
|
33
|
+
}
|
|
34
|
+
})(),
|
|
20
35
|
]);
|