tsondb 0.12.7 → 0.12.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.
- package/dist/src/node/renderers/ts/render.d.ts +6 -0
- package/dist/src/node/renderers/ts/render.js +35 -2
- package/dist/src/node/schema/Schema.js +9 -1
- package/dist/src/node/utils/render.d.ts +1 -0
- package/dist/src/node/utils/render.js +2 -1
- package/dist/src/shared/utils/translation.d.ts +2 -0
- package/dist/src/shared/utils/translation.js +66 -0
- package/package.json +2 -1
|
@@ -5,5 +5,11 @@ export type TypeScriptRendererOptions = {
|
|
|
5
5
|
preserveFiles: boolean;
|
|
6
6
|
generateEntityMapType: boolean;
|
|
7
7
|
addIdentifierToEntities: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Infer translation parameter types from the message strings in a {@link TranslationObjectType TranslationObject} as branded types.
|
|
10
|
+
*/
|
|
11
|
+
inferTranslationParameters?: {
|
|
12
|
+
format: "mf2";
|
|
13
|
+
};
|
|
8
14
|
};
|
|
9
15
|
export declare const render: (options: Partial<TypeScriptRendererOptions> | undefined, declarations: readonly Decl[]) => string;
|
|
@@ -3,6 +3,7 @@ import { dirname, relative } from "node:path";
|
|
|
3
3
|
import { discriminatorKey } from "../../../shared/enum.js";
|
|
4
4
|
import { unique } from "../../../shared/utils/array.js";
|
|
5
5
|
import { toCamelCase } from "../../../shared/utils/string.js";
|
|
6
|
+
import { extractParameterTypeNamesFromMessage, mapParameterTypeNames, } from "../../../shared/utils/translation.js";
|
|
6
7
|
import { assertExhaustive } from "../../../shared/utils/typeSafety.js";
|
|
7
8
|
import { asDecl } from "../../schema/declarations/Declaration.js";
|
|
8
9
|
import { addEphemeralUUIDToType, createEntityIdentifierTypeAsDecl, isEntityDecl, } from "../../schema/declarations/EntityDecl.js";
|
|
@@ -14,7 +15,7 @@ import { getTypeOfKey } from "../../schema/types/generic/TranslationObjectType.j
|
|
|
14
15
|
import { isNestedEntityMapType } from "../../schema/types/references/NestedEntityMapType.js";
|
|
15
16
|
import { ReferenceIdentifierType } from "../../schema/types/references/ReferenceIdentifierType.js";
|
|
16
17
|
import { ensureSpecialDirStart } from "../../utils/path.js";
|
|
17
|
-
import { combineSyntaxes, emptyRenderResult, indent, prefixLines, syntax, } from "../../utils/render.js";
|
|
18
|
+
import { combineSyntaxes, emptyRenderResult, getIndentation, indent, prefixLines, syntax, } from "../../utils/render.js";
|
|
18
19
|
const defaultOptions = {
|
|
19
20
|
indentation: 2,
|
|
20
21
|
objectTypeKeyword: "interface",
|
|
@@ -62,8 +63,34 @@ const renderEnumType = (options, type) => combineSyntaxes(Object.entries(type.va
|
|
|
62
63
|
? ""
|
|
63
64
|
: syntax `${EOL}${caseName}: ${renderType(options, caseDef.type)}`}`)}${EOL}}`)));
|
|
64
65
|
const renderChildEntitiesType = (options, type) => renderType(options, ArrayType(ReferenceIdentifierType(type.entity), { uniqueItems: true }));
|
|
66
|
+
const mapTypeNameToType = (typeName) => {
|
|
67
|
+
switch (typeName) {
|
|
68
|
+
case "string":
|
|
69
|
+
return "string";
|
|
70
|
+
case "number":
|
|
71
|
+
case "integer":
|
|
72
|
+
return "number";
|
|
73
|
+
case "date":
|
|
74
|
+
case "datetime":
|
|
75
|
+
case "time":
|
|
76
|
+
return "Date";
|
|
77
|
+
case null:
|
|
78
|
+
default:
|
|
79
|
+
return "StringableTranslationParameter";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const renderTranslationParameterBrand = (options, params) => {
|
|
83
|
+
if (options.inferTranslationParameters === undefined) {
|
|
84
|
+
return emptyRenderResult;
|
|
85
|
+
}
|
|
86
|
+
const entries = Object.entries(params);
|
|
87
|
+
if (entries.length === 0) {
|
|
88
|
+
return emptyRenderResult;
|
|
89
|
+
}
|
|
90
|
+
return syntax ` & { __params: { ${entries.map(([name, type]) => `"${name}": ${type}`).join("; ")} } }`;
|
|
91
|
+
};
|
|
65
92
|
const renderTranslationObjectType = (options, type) => {
|
|
66
|
-
return wrapAsObject(options, combineSyntaxes(Object.entries(type.properties).map(([name, config]) => syntax `"${name.replace('"', '\\"')}"${type.allKeysAreRequired ? "" : "?"}: ${renderType(options, getTypeOfKey(config, type))}`), EOL));
|
|
93
|
+
return wrapAsObject(options, combineSyntaxes(Object.entries(type.properties).map(([name, config]) => syntax `"${name.replace('"', '\\"')}"${type.allKeysAreRequired ? "" : "?"}: ${renderType(options, getTypeOfKey(config, type))}${renderTranslationParameterBrand(options, mapParameterTypeNames(extractParameterTypeNamesFromMessage(name), mapTypeNameToType))}`), EOL));
|
|
67
94
|
};
|
|
68
95
|
const renderType = (options, type) => {
|
|
69
96
|
switch (type.kind) {
|
|
@@ -135,6 +162,11 @@ const renderEntityMapType = (options, declarations) => syntax `export type Entit
|
|
|
135
162
|
.filter(isEntityDecl)
|
|
136
163
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
137
164
|
.map(decl => syntax `${decl.name}: ${decl.name}`), EOL))}${EOL}}${EOL + EOL}`;
|
|
165
|
+
const renderStringableTranslationParameterType = (options) => options.inferTranslationParameters?.format === "mf2"
|
|
166
|
+
? "export type StringableTranslationParameter = {\n" +
|
|
167
|
+
prefixLines(getIndentation(options.indentation, 1), "toString(): string") +
|
|
168
|
+
"\n}\n\n"
|
|
169
|
+
: "";
|
|
138
170
|
export const render = (options = defaultOptions, declarations) => {
|
|
139
171
|
const finalOptions = { ...defaultOptions, ...options };
|
|
140
172
|
const [_, entityMap] = finalOptions.generateEntityMapType
|
|
@@ -154,6 +186,7 @@ export const render = (options = defaultOptions, declarations) => {
|
|
|
154
186
|
return undefined;
|
|
155
187
|
}, declarations));
|
|
156
188
|
return (entityMap +
|
|
189
|
+
renderStringableTranslationParameterType(finalOptions) +
|
|
157
190
|
(finalOptions.preserveFiles
|
|
158
191
|
? (declarations[0] === undefined ? "" : renderImports(declarations[0].sourceUrl, imports)) +
|
|
159
192
|
content
|
|
@@ -20,6 +20,13 @@ const checkDuplicateIdentifier = (existingDecls, decl) => {
|
|
|
20
20
|
throw new Error(`Duplicate declaration name "${decl.name}" in "${decl.sourceUrl}" and "${existingDeclWithSameName.sourceUrl}". Make sure declaration names are globally unique.`);
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
+
// const checkReservedIdentifier = (decl: NestedDecl) => {
|
|
24
|
+
// if (RESERVED_DECLARATION_IDENTIFIER.includes(decl.name)) {
|
|
25
|
+
// throw new Error(
|
|
26
|
+
// `Declaration "${decl.name}" in "${decl.sourceUrl}" uses a reserved identifier name.`,
|
|
27
|
+
// )
|
|
28
|
+
// }
|
|
29
|
+
// }
|
|
23
30
|
const checkParameterNamesShadowing = (decls) => {
|
|
24
31
|
for (const decl of decls) {
|
|
25
32
|
for (const param of getParameterNames(decl)) {
|
|
@@ -219,9 +226,10 @@ export const Schema = (declarations, localeEntity) => {
|
|
|
219
226
|
debug("collecting nested declarations ...");
|
|
220
227
|
const allDecls = addDeclarations([], localeEntity ? declarations.concat(localeEntity) : declarations);
|
|
221
228
|
debug("found %d nested declarations", allDecls.length);
|
|
222
|
-
debug("checking for duplicate identifiers ...");
|
|
229
|
+
debug("checking for duplicate identifiers ..."); // debug("checking for duplicate or reserved identifiers ...")
|
|
223
230
|
allDecls.forEach((decl, declIndex) => {
|
|
224
231
|
checkDuplicateIdentifier(allDecls.slice(0, declIndex), decl);
|
|
232
|
+
// checkReservedIdentifier(decl)
|
|
225
233
|
});
|
|
226
234
|
const allDeclsWithoutNestedEntities = allDecls.filter(decl => decl.kind !== "NestedEntity");
|
|
227
235
|
debug("checking name shadowing ...");
|
|
@@ -7,4 +7,5 @@ export type RenderResult = [imports: {
|
|
|
7
7
|
export declare const emptyRenderResult: RenderResult;
|
|
8
8
|
export declare const combineSyntaxes: (syntaxes: (RenderResult | string | undefined)[], separator?: string) => RenderResult;
|
|
9
9
|
export declare const syntax: (strings: TemplateStringsArray, ...values: (RenderResult | string | undefined)[]) => RenderResult;
|
|
10
|
+
export declare const getIndentation: (spaces: number, indentLevel: number) => string;
|
|
10
11
|
export declare const indent: (spaces: number, indentLevel: number, text: RenderResult) => RenderResult;
|
|
@@ -28,7 +28,8 @@ export const syntax = (strings, ...values) => strings.reduce((acc, str, i) => {
|
|
|
28
28
|
return [acc[0], acc[1] + str.replace(/\n/g, EOL)];
|
|
29
29
|
}
|
|
30
30
|
}, emptyRenderResult);
|
|
31
|
+
export const getIndentation = (spaces, indentLevel) => " ".repeat(spaces * indentLevel);
|
|
31
32
|
export const indent = (spaces, indentLevel, text) => [
|
|
32
33
|
text[0],
|
|
33
|
-
prefixLines(
|
|
34
|
+
prefixLines(getIndentation(spaces, indentLevel), text[1]),
|
|
34
35
|
];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MessageError, parseMessage } from "messageformat";
|
|
2
|
+
import { assertExhaustive } from "./typeSafety.js";
|
|
3
|
+
const mergeAssoc = (acc, key, value) => {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- if there is a previous value and the current result is null, keep the more specific one
|
|
5
|
+
return { ...acc, [key]: value ?? acc[key] };
|
|
6
|
+
};
|
|
7
|
+
const reduceMapAssoc = (map, acc, item) => {
|
|
8
|
+
const result = map(item);
|
|
9
|
+
if (result) {
|
|
10
|
+
return mergeAssoc(acc, result[0], result[1]);
|
|
11
|
+
}
|
|
12
|
+
return acc;
|
|
13
|
+
};
|
|
14
|
+
const reduceADT = (cases, acc, item) => {
|
|
15
|
+
const caseFn = cases[item.type];
|
|
16
|
+
return caseFn?.(acc, item) ?? acc;
|
|
17
|
+
};
|
|
18
|
+
const extractParameterFromDeclaration = (decl) => {
|
|
19
|
+
switch (decl.type) {
|
|
20
|
+
case "input":
|
|
21
|
+
return [decl.name, decl.value.functionRef?.name ?? null];
|
|
22
|
+
case "local":
|
|
23
|
+
return undefined;
|
|
24
|
+
default:
|
|
25
|
+
return assertExhaustive(decl);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const extractParametersFromDeclarations = (decls) => decls.reduce((acc, decl) => reduceMapAssoc(extractParameterFromDeclaration, acc, decl), {});
|
|
29
|
+
const reduceParametersFromPattern = (acc, pattern) => pattern.reduce((acc, element) => {
|
|
30
|
+
if (typeof element === "string") {
|
|
31
|
+
return acc;
|
|
32
|
+
}
|
|
33
|
+
return reduceADT({
|
|
34
|
+
expression: (acc, element) => {
|
|
35
|
+
if (!element.arg) {
|
|
36
|
+
return acc;
|
|
37
|
+
}
|
|
38
|
+
return reduceADT({
|
|
39
|
+
variable: (acc, variable) => mergeAssoc(acc, variable.name, element.functionRef?.name ?? null),
|
|
40
|
+
}, acc, element.arg);
|
|
41
|
+
},
|
|
42
|
+
}, acc, element);
|
|
43
|
+
}, acc);
|
|
44
|
+
export const extractParameterTypeNamesFromMessage = (message) => {
|
|
45
|
+
try {
|
|
46
|
+
const dataModel = parseMessage(message);
|
|
47
|
+
switch (dataModel.type) {
|
|
48
|
+
case "message":
|
|
49
|
+
return reduceParametersFromPattern(extractParametersFromDeclarations(dataModel.declarations), dataModel.pattern);
|
|
50
|
+
case "select": {
|
|
51
|
+
return dataModel.selectors.reduce((acc, variable) => mergeAssoc(acc, variable.name, null), dataModel.variants.reduce((acc, variant) => reduceParametersFromPattern(acc, variant.value), extractParametersFromDeclarations(dataModel.declarations)));
|
|
52
|
+
}
|
|
53
|
+
default:
|
|
54
|
+
return assertExhaustive(dataModel);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof MessageError) {
|
|
59
|
+
throw new MessageError(error.message, message);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export const mapParameterTypeNames = (typeMap, map) => Object.fromEntries(Object.entries(typeMap).map(([key, typeName]) => [key, map(typeName)]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsondb",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.8",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Lukas Obermann",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"@preact/signals": "^2.5.1",
|
|
51
51
|
"debug": "^4.4.3",
|
|
52
52
|
"express": "^5.1.0",
|
|
53
|
+
"messageformat": "^4.0.0",
|
|
53
54
|
"preact": "^10.27.2",
|
|
54
55
|
"preact-iso": "^2.11.0",
|
|
55
56
|
"simple-cli-args": "^0.1.3",
|