tsondb 0.1.3 → 0.3.0

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.
Files changed (110) hide show
  1. package/lib/ModelContainer.js +9 -7
  2. package/lib/Schema.js +1 -3
  3. package/lib/client/api.d.ts +14 -1
  4. package/lib/client/api.js +119 -0
  5. package/lib/client/components/Git.d.ts +2 -0
  6. package/lib/client/components/Git.js +116 -0
  7. package/lib/client/components/Layout.js +2 -1
  8. package/lib/client/components/typeInputs/ArrayTypeInput.js +3 -2
  9. package/lib/client/components/typeInputs/EnumTypeInput.d.ts +13 -0
  10. package/lib/client/components/typeInputs/{utils/EnumDeclField.js → EnumTypeInput.js} +13 -13
  11. package/lib/client/components/typeInputs/IncludeIdentifierTypeInput.js +1 -10
  12. package/lib/client/components/typeInputs/StringTypeInput.js +8 -5
  13. package/lib/client/components/typeInputs/TypeInput.js +3 -0
  14. package/lib/client/components/typeInputs/utils/Markdown.d.ts +6 -0
  15. package/lib/client/components/typeInputs/utils/Markdown.js +26 -0
  16. package/lib/client/hooks/useAPIResource.d.ts +1 -0
  17. package/lib/client/hooks/useAPIResource.js +2 -0
  18. package/lib/client/hooks/useMappedAPIResource.d.ts +1 -0
  19. package/lib/client/hooks/useMappedAPIResource.js +19 -0
  20. package/lib/client/routes/Entity.js +18 -24
  21. package/lib/client/routes/Home.js +3 -12
  22. package/lib/client/utils/typeSkeleton.js +10 -16
  23. package/lib/renderers/jsonschema/index.d.ts +1 -1
  24. package/lib/renderers/jsonschema/index.js +30 -7
  25. package/lib/renderers/jsonschema/render.d.ts +5 -1
  26. package/lib/renderers/jsonschema/render.js +43 -16
  27. package/lib/renderers/ts/index.d.ts +1 -1
  28. package/lib/renderers/ts/index.js +37 -6
  29. package/lib/renderers/ts/render.d.ts +2 -0
  30. package/lib/renderers/ts/render.js +59 -33
  31. package/lib/schema/Node.d.ts +2 -0
  32. package/lib/schema/Node.js +7 -1
  33. package/lib/schema/declarations/Declaration.d.ts +5 -3
  34. package/lib/schema/declarations/Declaration.js +14 -10
  35. package/lib/schema/declarations/EntityDecl.d.ts +6 -0
  36. package/lib/schema/declarations/EnumDecl.d.ts +11 -8
  37. package/lib/schema/declarations/EnumDecl.js +16 -68
  38. package/lib/schema/declarations/TypeAliasDecl.d.ts +4 -0
  39. package/lib/schema/declarations/TypeAliasDecl.js +1 -1
  40. package/lib/schema/index.d.ts +1 -0
  41. package/lib/schema/index.js +1 -0
  42. package/lib/schema/types/Type.d.ts +8 -2
  43. package/lib/schema/types/Type.js +57 -11
  44. package/lib/schema/types/generic/ArrayType.d.ts +2 -1
  45. package/lib/schema/types/generic/ArrayType.js +3 -2
  46. package/lib/schema/types/generic/EnumType.d.ts +38 -0
  47. package/lib/schema/types/generic/EnumType.js +96 -0
  48. package/lib/schema/types/generic/ObjectType.d.ts +6 -1
  49. package/lib/schema/types/generic/ObjectType.js +9 -4
  50. package/lib/schema/types/primitives/BooleanType.d.ts +2 -1
  51. package/lib/schema/types/primitives/BooleanType.js +1 -0
  52. package/lib/schema/types/primitives/DateType.d.ts +2 -1
  53. package/lib/schema/types/primitives/DateType.js +1 -0
  54. package/lib/schema/types/primitives/FloatType.d.ts +2 -1
  55. package/lib/schema/types/primitives/FloatType.js +1 -0
  56. package/lib/schema/types/primitives/IntegerType.d.ts +2 -1
  57. package/lib/schema/types/primitives/IntegerType.js +1 -0
  58. package/lib/schema/types/primitives/StringType.d.ts +2 -1
  59. package/lib/schema/types/primitives/StringType.js +1 -0
  60. package/lib/schema/types/references/GenericArgumentIdentifierType.d.ts +2 -1
  61. package/lib/schema/types/references/GenericArgumentIdentifierType.js +1 -0
  62. package/lib/schema/types/references/IncludeIdentifierType.d.ts +5 -3
  63. package/lib/schema/types/references/IncludeIdentifierType.js +18 -3
  64. package/lib/schema/types/references/NestedEntityMapType.d.ts +2 -1
  65. package/lib/schema/types/references/NestedEntityMapType.js +6 -13
  66. package/lib/schema/types/references/ReferenceIdentifierType.d.ts +6 -7
  67. package/lib/schema/types/references/ReferenceIdentifierType.js +5 -2
  68. package/lib/server/api/declarations.d.ts +1 -0
  69. package/lib/server/api/declarations.js +154 -0
  70. package/lib/server/api/git.d.ts +1 -0
  71. package/lib/server/api/git.js +174 -0
  72. package/lib/server/api/index.d.ts +1 -0
  73. package/lib/server/api/index.js +8 -0
  74. package/lib/server/api/instanceOperations.d.ts +6 -0
  75. package/lib/server/api/instanceOperations.js +82 -0
  76. package/lib/server/api/instances.d.ts +1 -0
  77. package/lib/server/api/instances.js +23 -0
  78. package/lib/server/index.d.ts +22 -1
  79. package/lib/server/index.js +11 -165
  80. package/lib/server/init.d.ts +5 -0
  81. package/lib/server/init.js +56 -0
  82. package/lib/shared/api.d.ts +12 -1
  83. package/lib/shared/utils/array.d.ts +19 -0
  84. package/lib/shared/utils/array.js +27 -0
  85. package/lib/shared/utils/git.d.ts +12 -0
  86. package/lib/shared/utils/git.js +98 -0
  87. package/lib/shared/utils/instances.d.ts +10 -0
  88. package/lib/shared/utils/instances.js +8 -1
  89. package/lib/shared/utils/markdown.d.ts +14 -0
  90. package/lib/shared/utils/markdown.js +42 -0
  91. package/lib/shared/utils/object.d.ts +1 -0
  92. package/lib/shared/utils/object.js +4 -0
  93. package/lib/shared/utils/string.d.ts +1 -0
  94. package/lib/shared/utils/string.js +9 -0
  95. package/lib/tsconfig.tsbuildinfo +1 -1
  96. package/lib/utils/git.d.ts +3 -0
  97. package/lib/utils/git.js +12 -0
  98. package/lib/utils/instances.d.ts +3 -2
  99. package/lib/utils/instances.js +9 -2
  100. package/lib/utils/path.d.ts +1 -0
  101. package/lib/utils/path.js +2 -0
  102. package/lib/utils/references.d.ts +7 -0
  103. package/lib/utils/references.js +40 -0
  104. package/lib/utils/render.d.ts +6 -1
  105. package/lib/utils/render.js +27 -1
  106. package/package.json +8 -2
  107. package/public/css/styles.css +238 -1
  108. package/lib/client/components/typeInputs/utils/EnumDeclField.d.ts +0 -13
  109. package/lib/server/instanceOperations.d.ts +0 -7
  110. package/lib/server/instanceOperations.js +0 -67
@@ -1,18 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
- import { useEffect, useState } from "preact/hooks";
3
2
  import { toTitleCase } from "../../shared/utils/string.js";
4
3
  import { getAllEntities } from "../api.js";
5
4
  import { Layout } from "../components/Layout.js";
5
+ import { useMappedAPIResource } from "../hooks/useMappedAPIResource.js";
6
6
  export const Home = () => {
7
- const [entities, setEntities] = useState([]);
8
- useEffect(() => {
9
- getAllEntities()
10
- .then(data => {
11
- setEntities(data.declarations.sort((a, b) => a.declaration.name.localeCompare(b.declaration.name)));
12
- })
13
- .catch(error => {
14
- console.error("Error fetching entities:", error);
15
- });
16
- }, []);
17
- return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsx("h1", { children: "Entities" }), _jsx("ul", { class: "entities", children: entities.map(entity => (_jsxs("li", { class: "entity-item", children: [_jsxs("div", { className: "title", children: [_jsx("h2", { children: toTitleCase(entity.declaration.name) }), entity.declaration.comment && _jsx("p", { children: entity.declaration.comment })] }), _jsxs("p", { class: "meta", children: [entity.instanceCount, " instance", entity.instanceCount === 1 ? "" : "s"] }), _jsx("div", { className: "btns", children: _jsx("a", { href: `/entities/${entity.declaration.name}`, class: "btn", children: "View" }) })] }, entity.declaration.name))) })] }));
7
+ const [entities] = useMappedAPIResource(getAllEntities, data => data.declarations.sort((a, b) => a.declaration.name.localeCompare(b.declaration.name)));
8
+ return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsx("h1", { children: "Entities" }), _jsx("ul", { class: "entities", children: (entities ?? []).map(entity => (_jsxs("li", { class: "entity-item", children: [_jsxs("div", { className: "title", children: [_jsx("h2", { children: toTitleCase(entity.declaration.name) }), entity.declaration.comment && _jsx("p", { children: entity.declaration.comment })] }), _jsxs("p", { class: "meta", children: [entity.instanceCount, " instance", entity.instanceCount === 1 ? "" : "s"] }), _jsx("div", { className: "btns", children: _jsx("a", { href: `/entities/${entity.declaration.name}`, class: "btn", children: "View" }) })] }, entity.declaration.name))) })] }));
18
9
  };
@@ -26,25 +26,19 @@ export const createTypeSkeleton = (getDeclFromDeclName, type) => {
26
26
  if (referencedDecl === undefined) {
27
27
  return undefined;
28
28
  }
29
- switch (referencedDecl.kind) {
30
- case "TypeAliasDecl":
31
- return createTypeSkeleton(getDeclFromDeclName, referencedDecl.type);
32
- case "EnumDecl": {
33
- const firstCase = Object.entries(referencedDecl.values)[0];
34
- if (firstCase[1] === null) {
35
- return { kind: firstCase[0] };
36
- }
37
- return {
38
- kind: firstCase[0],
39
- [firstCase[0]]: createTypeSkeleton(getDeclFromDeclName, firstCase[1]),
40
- };
41
- }
42
- default:
43
- return assertExhaustive(referencedDecl);
44
- }
29
+ return createTypeSkeleton(getDeclFromDeclName, referencedDecl.type);
45
30
  }
46
31
  case "NestedEntityMapType":
47
32
  return {};
33
+ case "EnumType": {
34
+ const firstCase = Object.entries(type.values)[0];
35
+ return {
36
+ kind: firstCase[0],
37
+ ...(firstCase[1].type === null
38
+ ? {}
39
+ : { [firstCase[0]]: createTypeSkeleton(getDeclFromDeclName, firstCase[1].type) }),
40
+ };
41
+ }
48
42
  default:
49
43
  return assertExhaustive(type);
50
44
  }
@@ -2,5 +2,5 @@ import { Output } from "../Output.js";
2
2
  import { JsonSchemaRendererOptions } from "./render.js";
3
3
  export declare const JsonSchemaOutput: (options: {
4
4
  targetPath: string;
5
- rendererOptions?: JsonSchemaRendererOptions;
5
+ rendererOptions?: Partial<JsonSchemaRendererOptions>;
6
6
  }) => Output;
@@ -1,12 +1,35 @@
1
- import { mkdir, writeFile } from "fs/promises";
2
- import { dirname } from "path";
3
- import { resolveTypeArgumentsInDecls } from "../../schema/index.js";
1
+ import { mkdir, rm, writeFile } from "node:fs/promises";
2
+ import { basename, dirname, extname, join, relative } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { groupDeclarationsBySourceUrl, resolveTypeArgumentsInDecls } from "../../schema/index.js";
5
+ import { commonPrefix } from "../../shared/utils/string.js";
4
6
  import { render } from "./render.js";
7
+ const extension = ".schema.json";
5
8
  export const JsonSchemaOutput = (options) => ({
6
9
  run: async (schema) => {
7
- await mkdir(dirname(options.targetPath), { recursive: true });
8
- await writeFile(options.targetPath, render(options.rendererOptions, resolveTypeArgumentsInDecls(schema.declarations)), {
9
- encoding: "utf-8",
10
- });
10
+ if (options.rendererOptions?.preserveFiles === true) {
11
+ await rm(options.targetPath, { recursive: true, force: true });
12
+ await mkdir(options.targetPath, { recursive: true });
13
+ const declarationsBySourceUrl = groupDeclarationsBySourceUrl(resolveTypeArgumentsInDecls(schema.declarations));
14
+ const sourceRootPath = fileURLToPath(commonPrefix(...Object.keys(declarationsBySourceUrl)));
15
+ if (sourceRootPath) {
16
+ for (const [sourceUrl, decls] of Object.entries(declarationsBySourceUrl)) {
17
+ const sourcePath = fileURLToPath(sourceUrl);
18
+ const relativePath = dirname(relative(sourceRootPath, sourcePath));
19
+ const newDir = join(options.targetPath, relativePath);
20
+ const newPath = join(newDir, basename(sourcePath, extname(sourcePath)) + extension);
21
+ await mkdir(newDir, { recursive: true });
22
+ await writeFile(newPath, render(options.rendererOptions, decls), {
23
+ encoding: "utf-8",
24
+ });
25
+ }
26
+ }
27
+ }
28
+ else {
29
+ await mkdir(dirname(options.targetPath), { recursive: true });
30
+ await writeFile(options.targetPath, render(options.rendererOptions, resolveTypeArgumentsInDecls(schema.declarations)), {
31
+ encoding: "utf-8",
32
+ });
33
+ }
11
34
  },
12
35
  });
@@ -1,5 +1,9 @@
1
1
  import { Decl } from "../../schema/declarations/Declaration.js";
2
2
  export type JsonSchemaRendererOptions = {
3
- indentation: number;
3
+ format: "minified" | "tabs" | {
4
+ kind: "spaces";
5
+ indentation?: number;
6
+ };
7
+ preserveFiles: boolean;
4
8
  };
5
9
  export declare const render: (options: Partial<JsonSchemaRendererOptions> | undefined, declarations: readonly Decl[]) => string;
@@ -1,3 +1,4 @@
1
+ import { dirname, relative } from "node:path";
1
2
  import { addEphemeralUUIDToType, createEntityIdentifierTypeAsDecl, isEntityDecl, } from "../../schema/declarations/EntityDecl.js";
2
3
  import { TypeAliasDecl } from "../../schema/declarations/TypeAliasDecl.js";
3
4
  import { flatMapAuxiliaryDecls, NodeKind } from "../../schema/Node.js";
@@ -5,8 +6,11 @@ import { isNestedEntityMapType, } from "../../schema/types/references/NestedEnti
5
6
  import { getParentDecl } from "../../schema/types/Type.js";
6
7
  import { discriminatorKey } from "../../shared/enum.js";
7
8
  import { assertExhaustive } from "../../shared/utils/typeSafety.js";
9
+ import { ensureSpecialDirStart } from "../../utils/path.js";
10
+ const defaultIndentation = 2;
8
11
  const defaultOptions = {
9
- indentation: 2,
12
+ format: { kind: "spaces" },
13
+ preserveFiles: false,
10
14
  };
11
15
  const renderArrayType = (options, type) => ({
12
16
  type: "array",
@@ -19,7 +23,11 @@ const renderObjectType = (options, type) => ({
19
23
  type: "object",
20
24
  properties: Object.fromEntries(Object.entries(type.properties).map(([name, config]) => [
21
25
  name,
22
- { description: config.comment, ...renderType(options, config.type) },
26
+ {
27
+ description: config.comment,
28
+ deprecated: config.isDeprecated,
29
+ ...renderType(options, config.type),
30
+ },
23
31
  ])),
24
32
  required: Object.entries(type.properties)
25
33
  .filter(([, config]) => config.isRequired)
@@ -65,15 +73,34 @@ const renderGenericArgumentIdentifierType = (_options, _type) => {
65
73
  const renderReferenceIdentifierType = (_options, type) => ({
66
74
  $ref: `#/$defs/${type.entity.name}_ID`,
67
75
  });
68
- const renderIncludeIdentifierType = (_options, type) => ({
69
- $ref: `#/$defs/${type.reference.name}`,
70
- });
76
+ const renderIncludeIdentifierType = (options, type) => {
77
+ const sourceUrl = getParentDecl(type)?.sourceUrl ?? "";
78
+ const filePath = options.preserveFiles && sourceUrl !== type.reference.sourceUrl
79
+ ? ensureSpecialDirStart(relative(dirname(sourceUrl), type.reference.sourceUrl))
80
+ : "";
81
+ return {
82
+ $ref: `${filePath}#/$defs/${type.reference.name}`,
83
+ };
84
+ };
71
85
  const renderNestedEntityMapType = (_options, type) => ({
72
86
  type: "object",
73
87
  additionalProperties: {
74
88
  $ref: `#/$defs/${type.name}`,
75
89
  },
76
90
  });
91
+ const renderEnumType = (options, type) => ({
92
+ oneOf: Object.entries(type.values).map(([caseName, caseDef]) => ({
93
+ type: "object",
94
+ deprecated: caseDef.isDeprecated,
95
+ properties: {
96
+ [discriminatorKey]: {
97
+ const: caseName,
98
+ },
99
+ ...(caseDef.type === null ? {} : { [caseName]: renderType(options, caseDef.type) }),
100
+ },
101
+ required: [discriminatorKey, ...(caseDef === null ? [] : [caseName])],
102
+ })),
103
+ });
77
104
  const renderType = (options, type) => {
78
105
  switch (type.kind) {
79
106
  case NodeKind.ArrayType:
@@ -98,29 +125,25 @@ const renderType = (options, type) => {
98
125
  return renderIncludeIdentifierType(options, type);
99
126
  case NodeKind.NestedEntityMapType:
100
127
  return renderNestedEntityMapType(options, type);
128
+ case NodeKind.EnumType:
129
+ return renderEnumType(options, type);
101
130
  default:
102
131
  return assertExhaustive(type, "Unknown type");
103
132
  }
104
133
  };
105
134
  const renderEntityDecl = (options, decl) => ({
106
135
  description: decl.comment,
136
+ deprecated: decl.isDeprecated,
107
137
  ...renderType(options, addEphemeralUUIDToType(decl)),
108
138
  });
109
139
  const renderEnumDecl = (options, decl) => ({
110
140
  description: decl.comment,
111
- oneOf: Object.entries(decl.values.value).map(([caseName, caseDef]) => ({
112
- type: "object",
113
- properties: {
114
- [discriminatorKey]: {
115
- const: caseName,
116
- },
117
- ...(caseDef === null ? {} : { [caseName]: renderType(options, caseDef) }),
118
- },
119
- required: [discriminatorKey, ...(caseDef === null ? [] : [caseName])],
120
- })),
141
+ deprecated: decl.isDeprecated,
142
+ ...renderEnumType(options, decl.type.value),
121
143
  });
122
144
  const renderTypeAliasDecl = (options, decl) => ({
123
145
  description: decl.comment,
146
+ deprecated: decl.isDeprecated,
124
147
  ...renderType(options, decl.type.value),
125
148
  });
126
149
  const renderDecl = (options, decl) => {
@@ -152,5 +175,9 @@ export const render = (options = defaultOptions, declarations) => {
152
175
  }
153
176
  return undefined;
154
177
  }, declarations)),
155
- }, undefined, finalOptions.indentation);
178
+ }, undefined, finalOptions.format === "minified"
179
+ ? undefined
180
+ : finalOptions.format === "tabs"
181
+ ? "\t"
182
+ : finalOptions.format.indentation ?? defaultIndentation);
156
183
  };
@@ -2,5 +2,5 @@ import { Output } from "../Output.js";
2
2
  import { TypeScriptRendererOptions } from "./render.js";
3
3
  export declare const TypeScriptOutput: (options: {
4
4
  targetPath: string;
5
- rendererOptions?: TypeScriptRendererOptions;
5
+ rendererOptions?: Partial<TypeScriptRendererOptions>;
6
6
  }) => Output;
@@ -1,11 +1,42 @@
1
- import { mkdir, writeFile } from "fs/promises";
2
- import { dirname } from "path";
1
+ import Debug from "debug";
2
+ import { mkdir, rm, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, extname, join, relative } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { groupDeclarationsBySourceUrl } from "../../schema/declarations/Declaration.js";
6
+ import { commonPrefix } from "../../shared/utils/string.js";
3
7
  import { render } from "./render.js";
8
+ const debug = Debug("tsondb:renderer:ts");
9
+ const extension = ".d.ts";
4
10
  export const TypeScriptOutput = (options) => ({
5
11
  run: async (schema) => {
6
- await mkdir(dirname(options.targetPath), { recursive: true });
7
- await writeFile(options.targetPath, render(options.rendererOptions, schema.declarations), {
8
- encoding: "utf-8",
9
- });
12
+ if (options.rendererOptions?.preserveFiles === true) {
13
+ debug("emitting declarations to multiple files...");
14
+ await rm(options.targetPath, { recursive: true, force: true });
15
+ await mkdir(options.targetPath, { recursive: true });
16
+ const declarationsBySourceUrl = groupDeclarationsBySourceUrl(schema.declarations);
17
+ const sourceRootPath = fileURLToPath(commonPrefix(...Object.keys(declarationsBySourceUrl)));
18
+ debug("common source root path: %s", sourceRootPath);
19
+ if (sourceRootPath) {
20
+ for (const [sourceUrl, decls] of Object.entries(declarationsBySourceUrl)) {
21
+ const sourcePath = fileURLToPath(sourceUrl);
22
+ const relativePath = dirname(relative(sourceRootPath, sourcePath));
23
+ const newDir = join(options.targetPath, relativePath);
24
+ const newPath = join(newDir, basename(sourcePath, extname(sourcePath)) + extension);
25
+ await mkdir(newDir, { recursive: true });
26
+ await writeFile(newPath, render(options.rendererOptions, decls), {
27
+ encoding: "utf-8",
28
+ });
29
+ }
30
+ debug("emitted declaration files to %s", options.targetPath);
31
+ }
32
+ }
33
+ else {
34
+ debug("emitting declarations to single file...");
35
+ await mkdir(dirname(options.targetPath), { recursive: true });
36
+ await writeFile(options.targetPath, render(options.rendererOptions, schema.declarations), {
37
+ encoding: "utf-8",
38
+ });
39
+ debug("emitted declarations to %s", options.targetPath);
40
+ }
10
41
  },
11
42
  });
@@ -1,5 +1,7 @@
1
1
  import { Decl } from "../../schema/declarations/Declaration.js";
2
2
  export type TypeScriptRendererOptions = {
3
3
  indentation: number;
4
+ objectTypeKeyword: "interface" | "type";
5
+ preserveFiles: boolean;
4
6
  };
5
7
  export declare const render: (options: Partial<TypeScriptRendererOptions> | undefined, declarations: readonly Decl[]) => string;
@@ -1,4 +1,5 @@
1
1
  import { EOL } from "node:os";
2
+ import { dirname, relative } from "node:path";
2
3
  import { addEphemeralUUIDToType, createEntityIdentifierTypeAsDecl, isEntityDecl, } from "../../schema/declarations/EntityDecl.js";
3
4
  import { TypeAliasDecl } from "../../schema/declarations/TypeAliasDecl.js";
4
5
  import { flatMapAuxiliaryDecls, NodeKind } from "../../schema/Node.js";
@@ -8,38 +9,51 @@ import { getParentDecl } from "../../schema/types/Type.js";
8
9
  import { discriminatorKey } from "../../shared/enum.js";
9
10
  import { toCamelCase } from "../../shared/utils/string.js";
10
11
  import { assertExhaustive } from "../../shared/utils/typeSafety.js";
11
- import { applyIndentation, joinSyntax, prefixLines, syntax } from "../../utils/render.js";
12
+ import { ensureSpecialDirStart } from "../../utils/path.js";
13
+ import { combineSyntaxes, indent, prefixLines, syntax } from "../../utils/render.js";
12
14
  const defaultOptions = {
13
15
  indentation: 2,
16
+ objectTypeKeyword: "interface",
17
+ preserveFiles: false,
14
18
  };
15
- const renderDocumentation = (comment) => comment === undefined
19
+ const renderDocumentation = (comment, isDeprecated) => syntax `${comment === undefined
16
20
  ? ""
17
21
  : syntax `/**
18
- ${prefixLines(" * ", comment, true)}
22
+ ${prefixLines(" * ", comment, true)}${isDeprecated
23
+ ? syntax `
24
+ * @deprecated`
25
+ : ""}
19
26
  */
20
- `;
21
- const renderTypeParameters = (options, params) => params.length === 0
27
+ `}`;
28
+ const renderTypeParameters = (options, params) => syntax `${params.length === 0
22
29
  ? ""
23
- : `<${params
24
- .map(param => param.constraint === undefined
30
+ : syntax `<${combineSyntaxes(params.map(param => param.constraint === undefined
25
31
  ? param.name
26
- : joinSyntax(param.name, " extends ", renderType(options, param.constraint)))
27
- .join(", ")}>`;
28
- const renderArrayType = (options, type) => `${renderType(options, type.items)}[]`;
29
- const wrapAsObject = (options, syntax) => joinSyntax("{", EOL, applyIndentation(1, syntax, options.indentation), EOL, "}");
32
+ : syntax `${param.name} extends ${renderType(options, param.constraint)}`), ", ")}>`}`;
33
+ const renderArrayType = (options, type) => syntax `${renderType(options, type.items)}[]`;
34
+ const wrapAsObject = (options, str) => syntax `{${EOL}${indent(options.indentation, 1, str)}${EOL}}`;
30
35
  const renderObjectType = (options, type) => {
31
- return wrapAsObject(options, Object.entries(type.properties)
32
- .map(([name, config]) => joinSyntax(renderDocumentation(config.comment), name, config.isRequired ? "" : "?", ": ", renderType(options, config.type)))
33
- .join(Object.values(type.properties).some(prop => prop.comment !== undefined) ? EOL + EOL : EOL));
36
+ return wrapAsObject(options, combineSyntaxes(Object.entries(type.properties).map(([name, config]) => syntax `${renderDocumentation(config.comment, config.isDeprecated)}${name}${config.isRequired ? "" : "?"}: ${renderType(options, config.type)}`), Object.values(type.properties).some(prop => prop.comment !== undefined) ? EOL + EOL : EOL));
34
37
  };
35
- const renderBooleanType = (_options, _type) => "boolean";
36
- const renderDateType = (_options, _type) => "Date";
37
- const renderNumericType = (_options, _type) => "number";
38
- const renderStringType = (_options, _type) => "string";
39
- const renderGenericArgumentIdentifierType = (_options, type) => type.argument.name;
40
- const renderReferenceIdentifierType = (_options, type) => type.entity.name + "_ID";
41
- const renderIncludeIdentifierType = (_options, type) => type.reference.name;
38
+ const renderBooleanType = (_options, _type) => syntax `boolean`;
39
+ const renderDateType = (_options, _type) => syntax `Date`;
40
+ const renderNumericType = (_options, _type) => syntax `number`;
41
+ const renderStringType = (_options, _type) => syntax `string`;
42
+ const renderGenericArgumentIdentifierType = (_options, type) => syntax `${type.argument.name}`;
43
+ const renderReferenceIdentifierType = (_options, type) => [
44
+ { [type.entity.sourceUrl]: [type.entity.name + "_ID"] },
45
+ type.entity.name + "_ID",
46
+ ];
47
+ const renderIncludeIdentifierType = (options, type) => combineSyntaxes([
48
+ [{ [type.reference.sourceUrl]: [type.reference.name] }, type.reference.name],
49
+ type.args.length === 0
50
+ ? ""
51
+ : syntax `<${combineSyntaxes(type.args.map(arg => renderType(options, arg)), ", ")}>`,
52
+ ]);
42
53
  const renderNestedEntityMapType = (options, type) => wrapAsObject(options, syntax `[${toCamelCase(type.secondaryEntity.name)}Id: string]: ${type.name}`);
54
+ const renderEnumType = (options, type) => combineSyntaxes(Object.entries(type.values).map(([caseName, caseDef]) => indent(options.indentation, 1, syntax `${EOL}| {${EOL}${indent(options.indentation, 1, syntax `${discriminatorKey}: "${caseName}"${caseDef.type === null
55
+ ? ""
56
+ : syntax `${EOL}${caseName}: ${renderType(options, caseDef.type)}`}`)}${EOL}}`)));
43
57
  const renderType = (options, type) => {
44
58
  switch (type.kind) {
45
59
  case NodeKind.ArrayType:
@@ -64,20 +78,17 @@ const renderType = (options, type) => {
64
78
  return renderIncludeIdentifierType(options, type);
65
79
  case NodeKind.NestedEntityMapType:
66
80
  return renderNestedEntityMapType(options, type);
81
+ case NodeKind.EnumType:
82
+ return renderEnumType(options, type);
67
83
  default:
68
84
  return assertExhaustive(type, "Unknown type");
69
85
  }
70
86
  };
71
- const renderEntityDecl = (options, decl) => joinSyntax(renderDocumentation(decl.comment), "export interface ", decl.name, " ", renderType(options, addEphemeralUUIDToType(decl)));
72
- const renderEnumDecl = (options, decl) => joinSyntax(renderDocumentation(decl.comment), "export type ", decl.name, renderTypeParameters(options, decl.parameters), " =", ...Object.entries(decl.values.value).map(([caseName, caseDef]) => applyIndentation(1, joinSyntax(EOL, "| {", EOL, applyIndentation(1, joinSyntax(`${discriminatorKey}: "${caseName}"`, caseDef === null
73
- ? ""
74
- : joinSyntax(EOL, caseName + ": ", renderType(options, caseDef))), options.indentation), EOL, "}"), options.indentation)));
75
- const renderTypeAliasDecl = (options, decl) => {
76
- const type = decl.type.value;
77
- return isObjectType(type)
78
- ? joinSyntax(renderDocumentation(decl.comment), "export interface ", decl.name, renderTypeParameters(options, decl.parameters), " ", renderType(options, type))
79
- : joinSyntax(renderDocumentation(decl.comment), "export type ", decl.name, renderTypeParameters(options, decl.parameters), " = ", renderType(options, type));
80
- };
87
+ const renderEntityDecl = (options, decl) => syntax `${renderDocumentation(decl.comment, decl.isDeprecated)}export ${options.objectTypeKeyword} ${decl.name} ${options.objectTypeKeyword === "type" ? "= " : ""}${renderType(options, addEphemeralUUIDToType(decl))}`;
88
+ const renderEnumDecl = (options, decl) => syntax `${renderDocumentation(decl.comment, decl.isDeprecated)}export type ${decl.name}${renderTypeParameters(options, decl.parameters)} =${renderEnumType(options, decl.type.value)}`;
89
+ const renderTypeAliasDecl = (options, decl) => isObjectType(decl.type.value)
90
+ ? syntax `${renderDocumentation(decl.comment, decl.isDeprecated)}export ${options.objectTypeKeyword} ${decl.name}${renderTypeParameters(options, decl.parameters)} ${options.objectTypeKeyword === "type" ? "= " : ""}${renderType(options, decl.type.value)}`
91
+ : syntax `${renderDocumentation(decl.comment, decl.isDeprecated)}export type ${decl.name}${renderTypeParameters(options, decl.parameters)} = ${renderType(options, decl.type.value)}`;
81
92
  const renderDecl = (options, decl) => {
82
93
  switch (decl.kind) {
83
94
  case NodeKind.EntityDecl:
@@ -90,10 +101,22 @@ const renderDecl = (options, decl) => {
90
101
  return assertExhaustive(decl, "Unknown declaration");
91
102
  }
92
103
  };
93
- const renderDeclarations = (options, declarations) => declarations.map(decl => renderDecl(options, decl)).join(EOL + EOL);
104
+ const renderDeclarations = (options, declarations) => combineSyntaxes(declarations.map(decl => renderDecl(options, decl)), EOL + EOL);
105
+ const renderImports = (currentUrl, imports) => {
106
+ const importsSyntax = Object.entries(imports)
107
+ .filter(([sourceUrl]) => sourceUrl !== currentUrl)
108
+ .map(([sourceUrl, names]) => [
109
+ ensureSpecialDirStart(relative(dirname(currentUrl), sourceUrl)),
110
+ names,
111
+ ])
112
+ .toSorted(([sourceUrlA], [sourceUrlB]) => sourceUrlA.localeCompare(sourceUrlB))
113
+ .map(([sourceUrl, names]) => `import { ${names.toSorted((a, b) => a.localeCompare(b)).join(", ")} } from "${sourceUrl}"`)
114
+ .join(EOL);
115
+ return importsSyntax.length > 0 ? importsSyntax + EOL + EOL : "";
116
+ };
94
117
  export const render = (options = defaultOptions, declarations) => {
95
118
  const finalOptions = { ...defaultOptions, ...options };
96
- return renderDeclarations(finalOptions, flatMapAuxiliaryDecls(node => {
119
+ const [imports, content] = renderDeclarations(finalOptions, flatMapAuxiliaryDecls(node => {
97
120
  if (isNestedEntityMapType(node)) {
98
121
  return TypeAliasDecl(getParentDecl(node)?.sourceUrl ?? "", {
99
122
  name: node.name,
@@ -106,4 +129,7 @@ export const render = (options = defaultOptions, declarations) => {
106
129
  }
107
130
  return undefined;
108
131
  }, declarations));
132
+ return finalOptions.preserveFiles
133
+ ? renderImports(declarations[0].sourceUrl, imports) + content
134
+ : content;
109
135
  };
@@ -4,6 +4,7 @@ import { Type } from "./types/Type.js";
4
4
  export interface NodeKind {
5
5
  EntityDecl: "EntityDecl";
6
6
  EnumDecl: "EnumDecl";
7
+ EnumCaseDecl: "EnumCaseDecl";
7
8
  TypeAliasDecl: "TypeAliasDecl";
8
9
  MemberDecl: "MemberDecl";
9
10
  ArrayType: "ArrayType";
@@ -18,6 +19,7 @@ export interface NodeKind {
18
19
  ReferenceIdentifierType: "ReferenceIdentifierType";
19
20
  IncludeIdentifierType: "IncludeIdentifierType";
20
21
  NestedEntityMapType: "NestedEntityMapType";
22
+ EnumType: "EnumType";
21
23
  }
22
24
  export declare const NodeKind: NodeKind;
23
25
  export interface BaseNode {
@@ -3,6 +3,7 @@ import { enumOfObject } from "../utils/enum.js";
3
3
  export const NodeKind = enumOfObject({
4
4
  EntityDecl: null,
5
5
  EnumDecl: null,
6
+ EnumCaseDecl: null,
6
7
  TypeAliasDecl: null,
7
8
  MemberDecl: null,
8
9
  ArrayType: null,
@@ -17,6 +18,7 @@ export const NodeKind = enumOfObject({
17
18
  ReferenceIdentifierType: null,
18
19
  IncludeIdentifierType: null,
19
20
  NestedEntityMapType: null,
21
+ EnumType: null,
20
22
  });
21
23
  export const flatMapAuxiliaryDecls = (callbackFn, declarations) => {
22
24
  const mapNodeTree = (callbackFn, node, decls) => {
@@ -27,7 +29,7 @@ export const flatMapAuxiliaryDecls = (callbackFn, declarations) => {
27
29
  }
28
30
  case NodeKind.EnumDecl: {
29
31
  const newDecls = callbackFn(node, decls);
30
- return Object.values(node.values.value).reduce((newDeclsAcc, caseDef) => caseDef === null ? newDecls : mapNodeTree(callbackFn, caseDef, newDeclsAcc), newDecls);
32
+ return mapNodeTree(callbackFn, node.type.value, newDecls);
31
33
  }
32
34
  case NodeKind.TypeAliasDecl: {
33
35
  const newDecls = callbackFn(node, decls);
@@ -51,6 +53,10 @@ export const flatMapAuxiliaryDecls = (callbackFn, declarations) => {
51
53
  case NodeKind.IncludeIdentifierType:
52
54
  case NodeKind.NestedEntityMapType:
53
55
  return callbackFn(node, decls);
56
+ case NodeKind.EnumType: {
57
+ const newDecls = callbackFn(node, decls);
58
+ return Object.values(node.values).reduce((newDeclsAcc, caseDef) => caseDef.type === null ? newDecls : mapNodeTree(callbackFn, caseDef.type, newDeclsAcc), newDecls);
59
+ }
54
60
  default:
55
61
  return assertExhaustive(node);
56
62
  }
@@ -1,5 +1,6 @@
1
1
  import { BaseNode, GetReferences, Node, Serializer } from "../Node.js";
2
2
  import { SerializedTypeParameter, TypeParameter } from "../parameters/TypeParameter.js";
3
+ import { EnumCaseDecl, SerializedEnumCaseDecl } from "../types/generic/EnumType.js";
3
4
  import { ObjectType, SerializedObjectType } from "../types/generic/ObjectType.js";
4
5
  import { SerializedType, Type } from "../types/Type.js";
5
6
  import { ValidatorHelpers } from "../validation/type.js";
@@ -16,12 +17,12 @@ export declare const getParameterNames: (decl: Decl) => string[];
16
17
  export declare const getTypeArgumentsRecord: <Params extends TypeParameter[]>(decl: DeclP<Params>, args: TypeArguments<Params>) => Record<string, Type>;
17
18
  export type Decl = EntityDecl | EnumDecl | TypeAliasDecl;
18
19
  export type SerializedDecl = SerializedEntityDecl | SerializedEnumDecl | SerializedTypeAliasDecl;
19
- export type DeclP<Params extends TypeParameter[] = TypeParameter[]> = EntityDecl<string, ObjectType> | EnumDecl<string, Record<string, Type | null>, Params> | TypeAliasDecl<string, Type, Params>;
20
- export type SerializedDeclP<Params extends SerializedTypeParameter[] = SerializedTypeParameter[]> = SerializedEntityDecl<string, SerializedObjectType> | SerializedEnumDecl<string, Record<string, SerializedType | null>, Params> | SerializedTypeAliasDecl<string, SerializedType, Params>;
20
+ export type DeclP<Params extends TypeParameter[] = TypeParameter[]> = EntityDecl<string, ObjectType> | EnumDecl<string, Record<string, EnumCaseDecl>, Params> | TypeAliasDecl<string, Type, Params>;
21
+ export type SerializedDeclP<Params extends SerializedTypeParameter[] = SerializedTypeParameter[]> = SerializedEntityDecl<string, SerializedObjectType> | SerializedEnumDecl<string, Record<string, SerializedEnumCaseDecl>, Params> | SerializedTypeAliasDecl<string, SerializedType, Params>;
21
22
  export type SecondaryDecl = EnumDecl | TypeAliasDecl;
22
23
  export type SerializedSecondaryDecl = SerializedEnumDecl | SerializedTypeAliasDecl;
23
24
  export declare const getNestedDeclarations: GetNestedDeclarations;
24
- export type GetNestedDeclarations<T extends Node = Node> = (isDeclarationAdded: (decl: Decl) => boolean, node: T) => Decl[];
25
+ export type GetNestedDeclarations<T extends Node = Node> = (addedDecls: Decl[], node: T) => Decl[];
25
26
  export declare const isDecl: (node: Node) => node is Decl;
26
27
  export interface BaseDecl<Name extends string = string, Params extends TypeParameter[] = TypeParameter[]> extends BaseNode {
27
28
  sourceUrl: string;
@@ -42,3 +43,4 @@ export declare const isDeclWithoutTypeParameters: (decl: Decl) => decl is DeclP<
42
43
  export declare const resolveTypeArgumentsInDecls: (decls: readonly Decl[]) => DeclP<[]>[];
43
44
  export declare const serializeDecl: Serializer<Decl, SerializedDecl>;
44
45
  export declare const getReferencesForDecl: GetReferences<Decl>;
46
+ export declare const groupDeclarationsBySourceUrl: (decls: readonly Decl[]) => Partial<Record<string, Decl[]>>;
@@ -1,6 +1,7 @@
1
1
  import { assertExhaustive } from "../../shared/utils/typeSafety.js";
2
2
  import { NodeKind } from "../Node.js";
3
3
  import { getNestedDeclarationsInArrayType } from "../types/generic/ArrayType.js";
4
+ import { getNestedDeclarationsInEnumType, } from "../types/generic/EnumType.js";
4
5
  import { getNestedDeclarationsInObjectType, } from "../types/generic/ObjectType.js";
5
6
  import { getNestedDeclarationsInIncludeIdentifierType } from "../types/references/IncludeIdentifierType.js";
6
7
  import { getNestedDeclarationsInNestedEntityMapType } from "../types/references/NestedEntityMapType.js";
@@ -10,31 +11,33 @@ import { getNestedDeclarationsInEnumDecl, getReferencesForEnumDecl, isEnumDecl,
10
11
  import { getNestedDeclarationsInTypeAliasDecl, getReferencesForTypeAliasDecl, isTypeAliasDecl, resolveTypeArgumentsInTypeAliasDecl, serializeTypeAliasDecl, validateTypeAliasDecl, } from "./TypeAliasDecl.js";
11
12
  export const getParameterNames = (decl) => decl.parameters.map(param => param.name);
12
13
  export const getTypeArgumentsRecord = (decl, args) => Object.fromEntries(args.map((arg, i) => [decl.parameters[i].name, arg]));
13
- export const getNestedDeclarations = (isDeclAdded, node) => {
14
+ export const getNestedDeclarations = (addedDecls, node) => {
14
15
  switch (node.kind) {
15
16
  case NodeKind.EntityDecl:
16
- return isDeclAdded(node) ? [] : getNestedDeclarationsInEntityDecl(isDeclAdded, node);
17
+ return getNestedDeclarationsInEntityDecl(addedDecls, node);
17
18
  case NodeKind.EnumDecl:
18
- return isDeclAdded(node) ? [] : getNestedDeclarationsInEnumDecl(isDeclAdded, node);
19
+ return getNestedDeclarationsInEnumDecl(addedDecls, node);
19
20
  case NodeKind.TypeAliasDecl:
20
- return isDeclAdded(node) ? [] : getNestedDeclarationsInTypeAliasDecl(isDeclAdded, node);
21
+ return getNestedDeclarationsInTypeAliasDecl(addedDecls, node);
21
22
  case NodeKind.ArrayType:
22
- return getNestedDeclarationsInArrayType(isDeclAdded, node);
23
+ return getNestedDeclarationsInArrayType(addedDecls, node);
23
24
  case NodeKind.ObjectType:
24
- return getNestedDeclarationsInObjectType(isDeclAdded, node);
25
+ return getNestedDeclarationsInObjectType(addedDecls, node);
25
26
  case NodeKind.BooleanType:
26
27
  case NodeKind.DateType:
27
28
  case NodeKind.FloatType:
28
29
  case NodeKind.IntegerType:
29
30
  case NodeKind.StringType:
30
31
  case NodeKind.GenericArgumentIdentifierType:
31
- return [];
32
+ return addedDecls;
32
33
  case NodeKind.ReferenceIdentifierType:
33
- return getNestedDeclarationsInReferenceIdentifierType(isDeclAdded, node);
34
+ return getNestedDeclarationsInReferenceIdentifierType(addedDecls, node);
34
35
  case NodeKind.IncludeIdentifierType:
35
- return getNestedDeclarationsInIncludeIdentifierType(isDeclAdded, node);
36
+ return getNestedDeclarationsInIncludeIdentifierType(addedDecls, node);
36
37
  case NodeKind.NestedEntityMapType:
37
- return getNestedDeclarationsInNestedEntityMapType(isDeclAdded, node);
38
+ return getNestedDeclarationsInNestedEntityMapType(addedDecls, node);
39
+ case NodeKind.EnumType:
40
+ return getNestedDeclarationsInEnumType(addedDecls, node);
38
41
  default:
39
42
  return assertExhaustive(node);
40
43
  }
@@ -96,3 +99,4 @@ export const getReferencesForDecl = (decl, value) => {
96
99
  return assertExhaustive(decl);
97
100
  }
98
101
  };
102
+ export const groupDeclarationsBySourceUrl = (decls) => Object.groupBy(decls, decl => decl.sourceUrl);
@@ -9,6 +9,7 @@ import { BaseDecl, GetNestedDeclarations, SerializedBaseDecl } from "./Declarati
9
9
  import { TypeAliasDecl } from "./TypeAliasDecl.js";
10
10
  export interface EntityDecl<Name extends string = string, T extends ObjectType = ObjectType> extends BaseDecl<Name, []> {
11
11
  kind: NodeKind["EntityDecl"];
12
+ namePlural: string;
12
13
  type: Lazy<T>;
13
14
  /**
14
15
  * @default "name"
@@ -23,9 +24,11 @@ export interface EntityDecl<Name extends string = string, T extends ObjectType =
23
24
  */
24
25
  pathInLocaleMap?: string;
25
26
  };
27
+ isDeprecated?: boolean;
26
28
  }
27
29
  export interface SerializedEntityDecl<Name extends string = string, T extends SerializedObjectType = SerializedObjectType> extends SerializedBaseDecl<Name, []> {
28
30
  kind: NodeKind["EntityDecl"];
31
+ namePlural: string;
29
32
  type: T;
30
33
  /**
31
34
  * @default "name"
@@ -40,9 +43,11 @@ export interface SerializedEntityDecl<Name extends string = string, T extends Se
40
43
  */
41
44
  pathInLocaleMap?: string;
42
45
  };
46
+ isDeprecated?: boolean;
43
47
  }
44
48
  export declare const EntityDecl: <Name extends string, T extends ObjectType>(sourceUrl: string, options: {
45
49
  name: Name;
50
+ namePlural: string;
46
51
  comment?: string;
47
52
  type: () => T;
48
53
  /**
@@ -58,6 +63,7 @@ export declare const EntityDecl: <Name extends string, T extends ObjectType>(sou
58
63
  */
59
64
  pathInLocaleMap?: string;
60
65
  };
66
+ isDeprecated?: boolean;
61
67
  }) => EntityDecl<Name, T>;
62
68
  export { EntityDecl as Entity };
63
69
  export declare const isEntityDecl: (node: Node) => node is EntityDecl;