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.
@@ -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(" ".repeat(spaces * indentLevel), text[1]),
34
+ prefixLines(getIndentation(spaces, indentLevel), text[1]),
34
35
  ];
@@ -0,0 +1,2 @@
1
+ export declare const extractParameterTypeNamesFromMessage: (message: string) => Record<string, string | null>;
2
+ export declare const mapParameterTypeNames: <T>(typeMap: Record<string, string | null>, map: (typeName: string | null) => T) => Record<string, T>;
@@ -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.7",
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",