@contentstorage/core 0.3.30 → 0.3.32
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/helpers/populateTextWithVariables.d.ts +1 -0
- package/dist/helpers/populateTextWithVariables.js +10 -0
- package/dist/lib/configLoader.js +7 -6
- package/dist/lib/contentManagement.d.ts +5 -2
- package/dist/lib/contentManagement.js +24 -20
- package/dist/scripts/generate-types.js +4 -4
- package/dist/scripts/pull-content.js +0 -4
- package/dist/type-generation/get-interfaces.d.ts +5 -0
- package/dist/type-generation/get-interfaces.js +97 -0
- package/dist/type-generation/get-names.d.ts +2 -0
- package/dist/type-generation/get-names.js +142 -0
- package/dist/type-generation/get-type-structure.d.ts +4 -0
- package/dist/type-generation/get-type-structure.js +270 -0
- package/dist/type-generation/index.d.ts +2 -0
- package/dist/type-generation/index.js +34 -0
- package/dist/type-generation/model.d.ts +39 -0
- package/dist/type-generation/model.js +7 -0
- package/dist/type-generation/util.d.ts +10 -0
- package/dist/type-generation/util.js +49 -0
- package/package.json +3 -2
- package/dist/helpers/defineConfig.d.ts +0 -6
- package/dist/helpers/defineConfig.js +0 -19
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function populateTextWithVariables(text: string, variables: Record<string, any>, textKey: string): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function populateTextWithVariables(text, variables, textKey) {
|
|
2
|
+
return text.replace(/\{(\w+)}/g, (placeholder, variableName) => {
|
|
3
|
+
if (Object.prototype.hasOwnProperty.call(variables, variableName)) {
|
|
4
|
+
const value = variables[variableName];
|
|
5
|
+
return String(value);
|
|
6
|
+
}
|
|
7
|
+
console.warn(`[getText] Variable "${variableName}" for text id "${textKey}" not found in provided variables. Placeholder "${placeholder}" will be replaced with an empty string.`);
|
|
8
|
+
return placeholder;
|
|
9
|
+
});
|
|
10
|
+
}
|
package/dist/lib/configLoader.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
+
import chalk from 'chalk';
|
|
3
4
|
const DEFAULT_CONFIG = {
|
|
4
5
|
languageCodes: [],
|
|
5
6
|
contentDir: path.join('src', 'content', 'json'),
|
|
@@ -13,19 +14,19 @@ export async function loadConfig() {
|
|
|
13
14
|
// Use require for JS config file
|
|
14
15
|
const loadedModule = await import(configPath);
|
|
15
16
|
userConfig = loadedModule.default || loadedModule;
|
|
16
|
-
console.log('Loaded config', JSON.stringify(userConfig));
|
|
17
|
-
console.log(`Loaded configuration from ${configPath}`);
|
|
17
|
+
console.log(chalk.blue('Loaded config', JSON.stringify(userConfig)));
|
|
18
|
+
console.log(chalk.blue(`Loaded configuration from ${configPath}`));
|
|
18
19
|
}
|
|
19
20
|
catch (error) {
|
|
20
|
-
console.error(`Error loading configuration from ${configPath}:`, error);
|
|
21
|
+
console.error(chalk.red(`Error loading configuration from ${configPath}:`, error));
|
|
21
22
|
// Decide if you want to proceed with defaults or exit
|
|
22
23
|
// For now, we'll proceed with defaults but warn
|
|
23
|
-
console.warn('Proceeding with default configuration.');
|
|
24
|
+
console.warn(chalk.yellow('Proceeding with default configuration.'));
|
|
24
25
|
userConfig = {}; // Reset in case of partial load failure
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
else {
|
|
28
|
-
console.log('No content.config.js found. Using default configuration.');
|
|
29
|
+
console.log(chalk.blue('No content.config.js found. Using default configuration.'));
|
|
29
30
|
}
|
|
30
31
|
const mergedConfig = {
|
|
31
32
|
...DEFAULT_CONFIG,
|
|
@@ -33,7 +34,7 @@ export async function loadConfig() {
|
|
|
33
34
|
};
|
|
34
35
|
// Validate required fields
|
|
35
36
|
if (!mergedConfig.contentKey) {
|
|
36
|
-
console.error('Error: Configuration is missing the required "contentKey" property.');
|
|
37
|
+
console.error(chalk.red('Error: Configuration is missing the required "contentKey" property.'));
|
|
37
38
|
process.exit(1);
|
|
38
39
|
}
|
|
39
40
|
const finalConfig = {
|
|
@@ -11,12 +11,15 @@ export declare function setContentLanguage(contentJson: object): void;
|
|
|
11
11
|
* `setContentLanguage()` must be called successfully before using this.
|
|
12
12
|
*
|
|
13
13
|
* @param pathString A dot-notation path string (e.g., 'HomePage.Login'). Autocompletion is provided.
|
|
14
|
+
* @param variables Variables help to render dynamic content inside text strings
|
|
14
15
|
* @param fallbackValue Optional string to return if the path is not found or the value is not a string.
|
|
15
16
|
* If not provided, and path is not found/value not string, undefined is returned.
|
|
16
17
|
* @returns The text string from the JSON, or the fallbackValue, or undefined.
|
|
17
18
|
*/
|
|
18
|
-
export declare function getText(pathString:
|
|
19
|
+
export declare function getText<Path extends keyof ContentStructure>(pathString: Path, variables?: ContentStructure[Path] extends {
|
|
20
|
+
variables: infer Vars;
|
|
21
|
+
} ? keyof Vars : Record<string, any>): string | undefined;
|
|
19
22
|
export declare function getImage(pathString: keyof ContentStructure, fallbackValue?: ImageObject): ImageObject | undefined;
|
|
20
23
|
export declare function getVariation<Path extends keyof ContentStructure>(pathString: Path, variationKey?: ContentStructure[Path] extends {
|
|
21
24
|
data: infer D;
|
|
22
|
-
} ? keyof D : string,
|
|
25
|
+
} ? keyof D : string, variables?: Record<string, any>): string | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { populateTextWithVariables } from '../helpers/populateTextWithVariables.js';
|
|
1
2
|
let activeContent = null;
|
|
2
3
|
/**
|
|
3
4
|
* Loads and sets the content for a specific language.
|
|
@@ -23,15 +24,16 @@ export function setContentLanguage(contentJson) {
|
|
|
23
24
|
* `setContentLanguage()` must be called successfully before using this.
|
|
24
25
|
*
|
|
25
26
|
* @param pathString A dot-notation path string (e.g., 'HomePage.Login'). Autocompletion is provided.
|
|
27
|
+
* @param variables Variables help to render dynamic content inside text strings
|
|
26
28
|
* @param fallbackValue Optional string to return if the path is not found or the value is not a string.
|
|
27
29
|
* If not provided, and path is not found/value not string, undefined is returned.
|
|
28
30
|
* @returns The text string from the JSON, or the fallbackValue, or undefined.
|
|
29
31
|
*/
|
|
30
|
-
export function getText(pathString,
|
|
32
|
+
export function getText(pathString, variables) {
|
|
31
33
|
if (!activeContent) {
|
|
32
34
|
const msg = `[Contentstorage] getText: Content not loaded (Key: "${String(pathString)}"). Ensure setContentLanguage() was called and completed successfully.`;
|
|
33
35
|
console.warn(msg);
|
|
34
|
-
return
|
|
36
|
+
return '';
|
|
35
37
|
}
|
|
36
38
|
const keys = pathString.split('.');
|
|
37
39
|
let current = activeContent;
|
|
@@ -42,16 +44,19 @@ export function getText(pathString, fallbackValue) {
|
|
|
42
44
|
else {
|
|
43
45
|
const msg = `[Contentstorage] getText: Path "${String(pathString)}" not found in loaded content.`;
|
|
44
46
|
console.warn(msg);
|
|
45
|
-
return
|
|
47
|
+
return '';
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
if (typeof current === 'string') {
|
|
49
|
-
|
|
51
|
+
if (!variables || Object.keys(variables).length === 0) {
|
|
52
|
+
return current;
|
|
53
|
+
}
|
|
54
|
+
return populateTextWithVariables(current, variables, pathString);
|
|
50
55
|
}
|
|
51
56
|
else {
|
|
52
57
|
const msg = `[Contentstorage] getText: Value at path "${String(pathString)}" is not a string (actual type: ${typeof current}).`;
|
|
53
58
|
console.warn(msg);
|
|
54
|
-
return
|
|
59
|
+
return '';
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
export function getImage(pathString, fallbackValue) {
|
|
@@ -85,11 +90,11 @@ export function getImage(pathString, fallbackValue) {
|
|
|
85
90
|
return fallbackValue;
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
|
-
export function getVariation(pathString, variationKey,
|
|
93
|
+
export function getVariation(pathString, variationKey, variables) {
|
|
89
94
|
if (!activeContent) {
|
|
90
95
|
const msg = `[Contentstorage] getVariation: Content not loaded (Key: "${pathString}", Variation: "${variationKey?.toString()}"). Ensure setContentLanguage() was called and completed successfully.`;
|
|
91
96
|
console.warn(msg);
|
|
92
|
-
return
|
|
97
|
+
return '';
|
|
93
98
|
}
|
|
94
99
|
const keys = pathString.split('.');
|
|
95
100
|
let current = activeContent;
|
|
@@ -100,7 +105,7 @@ export function getVariation(pathString, variationKey, fallbackString) {
|
|
|
100
105
|
else {
|
|
101
106
|
const msg = `[Contentstorage] getVariation: Path "${pathString}" for variation object not found in loaded content.`;
|
|
102
107
|
console.warn(msg);
|
|
103
|
-
return
|
|
108
|
+
return '';
|
|
104
109
|
}
|
|
105
110
|
}
|
|
106
111
|
if (current &&
|
|
@@ -113,7 +118,11 @@ export function getVariation(pathString, variationKey, fallbackString) {
|
|
|
113
118
|
typeof variationKey === 'string' &&
|
|
114
119
|
variationKey in variationObject.data) {
|
|
115
120
|
if (typeof variationObject.data[variationKey] === 'string') {
|
|
116
|
-
|
|
121
|
+
const current = variationObject.data[variationKey];
|
|
122
|
+
if (!variables || Object.keys(variables).length === 0) {
|
|
123
|
+
return current;
|
|
124
|
+
}
|
|
125
|
+
return populateTextWithVariables(current, variables, pathString);
|
|
117
126
|
}
|
|
118
127
|
else {
|
|
119
128
|
const msg = `[Contentstorage] getVariation: Variation value for key "${variationKey}" at path "${pathString}" is not a string (actual type: ${typeof variationObject.data[variationKey]}).`;
|
|
@@ -124,25 +133,20 @@ export function getVariation(pathString, variationKey, fallbackString) {
|
|
|
124
133
|
if ('default' in variationObject.data && typeof variationKey === 'string') {
|
|
125
134
|
if (typeof variationObject.data.default === 'string') {
|
|
126
135
|
if (variationKey && variationKey !== 'default') {
|
|
127
|
-
|
|
128
|
-
const msg = `[Contentstorage] getVariation: Variation key "${variationKey}" not found at path "${pathString}". Returning 'default' variation.`;
|
|
129
|
-
console.warn(msg);
|
|
136
|
+
console.warn(`[Contentstorage] getVariation: Variation key "${variationKey}" not found at path "${pathString}". Returning 'default' variation.`);
|
|
130
137
|
}
|
|
131
138
|
return variationObject.data.default;
|
|
132
139
|
}
|
|
133
140
|
else {
|
|
134
|
-
|
|
135
|
-
console.warn(msg);
|
|
141
|
+
console.warn(`[Contentstorage] getVariation: 'default' variation value at path "${pathString}" is not a string (actual type: ${typeof variationObject.data.default}).`);
|
|
136
142
|
}
|
|
137
143
|
}
|
|
138
144
|
// If neither specific key nor 'default' is found or valid
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return fallbackString;
|
|
145
|
+
console.warn(`[Contentstorage] getVariation: Neither variation key "${variationKey?.toString()}" nor 'default' variation found or valid at path "${pathString}".`);
|
|
146
|
+
return '';
|
|
142
147
|
}
|
|
143
148
|
else {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return fallbackString;
|
|
149
|
+
console.warn(`[Contentstorage] getVariation: Value at path "${pathString}" is not a valid variation object (actual value: ${JSON.stringify(current)}).`);
|
|
150
|
+
return '';
|
|
147
151
|
}
|
|
148
152
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// ^
|
|
2
|
+
// ^ Ensures the script is executed with Node.js
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import axios from 'axios';
|
|
6
|
-
import jsonToTS from 'json-to-ts'; // Import the library
|
|
7
6
|
import chalk from 'chalk'; // Optional: for colored output
|
|
8
7
|
import { loadConfig } from '../lib/configLoader.js';
|
|
9
8
|
import { flattenJson } from '../helpers/flattenJson.js';
|
|
10
9
|
import { CONTENTSTORAGE_CONFIG } from '../contentstorage-config.js';
|
|
10
|
+
import { jsonToTS } from '../type-generation/index.js';
|
|
11
11
|
export async function generateTypes() {
|
|
12
12
|
console.log(chalk.blue('Starting type generation...'));
|
|
13
13
|
const config = await loadConfig();
|
|
@@ -104,9 +104,9 @@ export async function generateTypes() {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
// Generate TypeScript interfaces using json-to-ts
|
|
107
|
-
const rootTypeName = 'ContentRoot';
|
|
107
|
+
const rootTypeName = 'ContentRoot';
|
|
108
108
|
console.log(chalk.blue(`Generating TypeScript types with root name '${rootTypeName}'...`));
|
|
109
|
-
const typeDeclarations = jsonToTS
|
|
109
|
+
const typeDeclarations = jsonToTS(jsonObject, {
|
|
110
110
|
rootName: rootTypeName,
|
|
111
111
|
});
|
|
112
112
|
if (!typeDeclarations || typeDeclarations.length === 0) {
|
|
@@ -44,11 +44,7 @@ export async function pullContent() {
|
|
|
44
44
|
Array.isArray(jsonData) /* jsonData === null is already covered */) {
|
|
45
45
|
throw new Error(`Expected a single JSON object from ${fileUrl} for language ${languageCode}, but received type ${Array.isArray(jsonData) ? 'array' : typeof jsonData}. Cannot save the file.`);
|
|
46
46
|
}
|
|
47
|
-
console.log(chalk.green(`Received and json for ${languageCode}. Saving to ${outputPath}`));
|
|
48
|
-
await fs.writeFile(outputPath, JSON.stringify(jsonData, null, 2));
|
|
49
|
-
console.log(chalk.green(`Successfully saved ${outputPath}`));
|
|
50
47
|
console.log(chalk.green(`Received JSON for ${languageCode}. Saving to ${outputPath}`));
|
|
51
|
-
// Write the JSON data to the file
|
|
52
48
|
await fs.writeFile(outputPath, JSON.stringify(jsonData, null, 2));
|
|
53
49
|
console.log(chalk.green(`Successfully saved ${outputPath}`));
|
|
54
50
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { InterfaceDescription, NameEntry, TypeStructure } from './model.js';
|
|
2
|
+
export declare function getInterfaceStringFromDescription({ name, typeMap, isRoot, }: InterfaceDescription & {
|
|
3
|
+
isRoot?: boolean;
|
|
4
|
+
}): string;
|
|
5
|
+
export declare function getInterfaceDescriptions(typeStructure: TypeStructure, names: NameEntry[]): InterfaceDescription[];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { findTypeById, isHash, isNonArrayUnion } from './util.js';
|
|
2
|
+
function isKeyNameValid(keyName) {
|
|
3
|
+
const regex = /^[a-zA-Z_][a-zA-Z\d_]*$/;
|
|
4
|
+
return regex.test(keyName);
|
|
5
|
+
}
|
|
6
|
+
function parseKeyMetaData(key) {
|
|
7
|
+
const isOptional = key.endsWith('--?');
|
|
8
|
+
if (isOptional) {
|
|
9
|
+
return {
|
|
10
|
+
isOptional,
|
|
11
|
+
keyValue: key.slice(0, -3),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return {
|
|
16
|
+
isOptional,
|
|
17
|
+
keyValue: key,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function findNameById(id, names) {
|
|
22
|
+
// @ts-expect-error
|
|
23
|
+
return names.find((_) => _.id === id).name;
|
|
24
|
+
}
|
|
25
|
+
function removeUndefinedFromUnion(unionTypeName) {
|
|
26
|
+
const typeNames = unionTypeName.split(' | ');
|
|
27
|
+
const undefinedIndex = typeNames.indexOf('undefined');
|
|
28
|
+
typeNames.splice(undefinedIndex, 1);
|
|
29
|
+
return typeNames.join(' | ');
|
|
30
|
+
}
|
|
31
|
+
function replaceTypeObjIdsWithNames(typeObj, names) {
|
|
32
|
+
return (Object.entries(typeObj)
|
|
33
|
+
// quote key if is invalid and question mark if optional from array merging
|
|
34
|
+
.map(([key, type]) => {
|
|
35
|
+
const { isOptional, keyValue } = parseKeyMetaData(key);
|
|
36
|
+
const isValid = isKeyNameValid(keyValue);
|
|
37
|
+
const validName = isValid ? keyValue : `'${keyValue}'`;
|
|
38
|
+
return isOptional
|
|
39
|
+
? [`${validName}?`, type, isOptional]
|
|
40
|
+
: [validName, type, isOptional];
|
|
41
|
+
})
|
|
42
|
+
// replace hashes with names referencing the hashes
|
|
43
|
+
.map(([key, type, isOptional]) => {
|
|
44
|
+
if (!isHash(type)) {
|
|
45
|
+
return [key, type, isOptional];
|
|
46
|
+
}
|
|
47
|
+
const newType = findNameById(type, names);
|
|
48
|
+
return [key, newType, isOptional];
|
|
49
|
+
})
|
|
50
|
+
// if union has undefined, remove undefined and make type optional
|
|
51
|
+
.map(([key, type, isOptional]) => {
|
|
52
|
+
if (!(isNonArrayUnion(type) && type.includes('undefined'))) {
|
|
53
|
+
return [key, type, isOptional];
|
|
54
|
+
}
|
|
55
|
+
const newType = removeUndefinedFromUnion(type);
|
|
56
|
+
const newKey = isOptional ? key : `${key}?`; // if already optional dont add question mark
|
|
57
|
+
return [newKey, newType, isOptional];
|
|
58
|
+
})
|
|
59
|
+
// make undefined optional and set type as any
|
|
60
|
+
.map(([key, type, isOptional]) => {
|
|
61
|
+
if (type !== 'undefined') {
|
|
62
|
+
return [key, type, isOptional];
|
|
63
|
+
}
|
|
64
|
+
const newType = 'any';
|
|
65
|
+
const newKey = isOptional ? key : `${key}?`; // if already optional dont add question mark
|
|
66
|
+
return [newKey, newType, isOptional];
|
|
67
|
+
})
|
|
68
|
+
.reduce((agg, [key, value]) => {
|
|
69
|
+
// @ts-expect-error
|
|
70
|
+
agg[key] = value;
|
|
71
|
+
return agg;
|
|
72
|
+
}, {}));
|
|
73
|
+
}
|
|
74
|
+
export function getInterfaceStringFromDescription({ name, typeMap, isRoot, }) {
|
|
75
|
+
const stringTypeMap = Object.entries(typeMap)
|
|
76
|
+
.map(([key, name]) => ` ${key}: ${name};\n`)
|
|
77
|
+
.reduce((a, b) => (a += b), '');
|
|
78
|
+
const declarationKeyWord = 'interface';
|
|
79
|
+
let interfaceString = `${isRoot ? 'export ' : ''}${declarationKeyWord} ${name} {\n`;
|
|
80
|
+
interfaceString += stringTypeMap;
|
|
81
|
+
interfaceString += '}';
|
|
82
|
+
return interfaceString;
|
|
83
|
+
}
|
|
84
|
+
export function getInterfaceDescriptions(typeStructure, names) {
|
|
85
|
+
return names
|
|
86
|
+
.map(({ id, name }) => {
|
|
87
|
+
const typeDescription = findTypeById(id, typeStructure.types);
|
|
88
|
+
if (typeDescription.typeObj) {
|
|
89
|
+
const typeMap = replaceTypeObjIdsWithNames(typeDescription.typeObj, names);
|
|
90
|
+
return { name, typeMap };
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
.filter((_) => _ !== null);
|
|
97
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @ts-expect-error
|
|
2
|
+
import * as pluralize from "pluralize";
|
|
3
|
+
import { TypeGroup } from './model.js';
|
|
4
|
+
import { findTypeById, getTypeDescriptionGroup, isHash, parseKeyMetaData } from './util.js';
|
|
5
|
+
function getName({ rootTypeId, types }, keyName, names, isInsideArray
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
) {
|
|
8
|
+
const typeDesc = types.find((_) => _.id === rootTypeId);
|
|
9
|
+
switch (getTypeDescriptionGroup(typeDesc)) {
|
|
10
|
+
case TypeGroup.Array:
|
|
11
|
+
// @ts-expect-error
|
|
12
|
+
typeDesc.arrayOfTypes.forEach((typeIdOrPrimitive, i) => {
|
|
13
|
+
getName({ rootTypeId: typeIdOrPrimitive, types },
|
|
14
|
+
// to differenttiate array types
|
|
15
|
+
i === 0 ? keyName : `${keyName}${i + 1}`, names, true);
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
rootName: getNameById(typeDesc.id, keyName, isInsideArray, types, names),
|
|
19
|
+
names,
|
|
20
|
+
};
|
|
21
|
+
case TypeGroup.Object:
|
|
22
|
+
Object.entries(typeDesc.typeObj).forEach(([key, value]) => {
|
|
23
|
+
getName({ rootTypeId: value, types }, key, names, false);
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
rootName: getNameById(typeDesc.id, keyName, isInsideArray, types, names),
|
|
27
|
+
names,
|
|
28
|
+
};
|
|
29
|
+
case TypeGroup.Primitive:
|
|
30
|
+
// in this case rootTypeId is primitive type string (string, null, number, boolean)
|
|
31
|
+
return {
|
|
32
|
+
rootName: rootTypeId,
|
|
33
|
+
names,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function getNames(typeStructure, rootName = "RootObject") {
|
|
38
|
+
return getName(typeStructure, rootName, [], false).names.reverse();
|
|
39
|
+
}
|
|
40
|
+
function getNameById(id, keyName, isInsideArray, types, nameMap) {
|
|
41
|
+
const nameEntry = nameMap.find((_) => _.id === id);
|
|
42
|
+
if (nameEntry) {
|
|
43
|
+
return nameEntry.name;
|
|
44
|
+
}
|
|
45
|
+
const typeDesc = findTypeById(id, types);
|
|
46
|
+
const group = getTypeDescriptionGroup(typeDesc);
|
|
47
|
+
let name;
|
|
48
|
+
switch (group) {
|
|
49
|
+
case TypeGroup.Array:
|
|
50
|
+
name = typeDesc.isUnion ? getArrayName(typeDesc, types, nameMap) : formatArrayName(typeDesc, types, nameMap);
|
|
51
|
+
break;
|
|
52
|
+
case TypeGroup.Object:
|
|
53
|
+
/**
|
|
54
|
+
* picking name for type in array requires to singularize that type name,
|
|
55
|
+
* and if not then no need to singularize
|
|
56
|
+
*/
|
|
57
|
+
name = [keyName]
|
|
58
|
+
.map((key) => parseKeyMetaData(key).keyValue)
|
|
59
|
+
.map((name) => (isInsideArray ? pluralize.singular(name) : name))
|
|
60
|
+
.map(pascalCase)
|
|
61
|
+
.map(normalizeInvalidTypeName)
|
|
62
|
+
.map(pascalCase) // needed because removed symbols might leave first character uncapitalized
|
|
63
|
+
.map((name) => uniqueByIncrement(name, nameMap.map(({ name }) => name)))
|
|
64
|
+
.pop();
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
nameMap.push({ id, name });
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
return name;
|
|
71
|
+
}
|
|
72
|
+
function pascalCase(name) {
|
|
73
|
+
return name
|
|
74
|
+
.split(/\s+/g)
|
|
75
|
+
.filter((_) => _ !== "")
|
|
76
|
+
.map(capitalize)
|
|
77
|
+
.reduce((a, b) => a + b, "");
|
|
78
|
+
}
|
|
79
|
+
function capitalize(name) {
|
|
80
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
81
|
+
}
|
|
82
|
+
function normalizeInvalidTypeName(name) {
|
|
83
|
+
if (/^[a-zA-Z][a-zA-Z0-9]*$/.test(name)) {
|
|
84
|
+
return name;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const noSymbolsName = name.replace(/[^a-zA-Z0-9]/g, "");
|
|
88
|
+
const startsWithWordCharacter = /^[a-zA-Z]/.test(noSymbolsName);
|
|
89
|
+
return startsWithWordCharacter ? noSymbolsName : `_${noSymbolsName}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function uniqueByIncrement(name, names) {
|
|
93
|
+
for (let i = 0; i < 1000; i++) {
|
|
94
|
+
const nameProposal = i === 0 ? name : `${name}${i + 1}`;
|
|
95
|
+
if (!names.includes(nameProposal)) {
|
|
96
|
+
return nameProposal;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function getArrayName(typeDesc, types, nameMap) {
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
if (typeDesc.arrayOfTypes.length === 0) {
|
|
103
|
+
return "any";
|
|
104
|
+
}
|
|
105
|
+
else { // @ts-ignore
|
|
106
|
+
if (typeDesc.arrayOfTypes.length === 1) {
|
|
107
|
+
// @ts-ignore
|
|
108
|
+
const [idOrPrimitive] = typeDesc.arrayOfTypes;
|
|
109
|
+
return convertToReadableType(idOrPrimitive, types, nameMap);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return unionToString(typeDesc, types, nameMap);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function convertToReadableType(idOrPrimitive, types, nameMap) {
|
|
117
|
+
return isHash(idOrPrimitive)
|
|
118
|
+
? // array keyName makes no difference in picking name for type
|
|
119
|
+
// @ts-ignore
|
|
120
|
+
getNameById(idOrPrimitive, null, true, types, nameMap)
|
|
121
|
+
: idOrPrimitive;
|
|
122
|
+
}
|
|
123
|
+
function unionToString(typeDesc, types, nameMap) {
|
|
124
|
+
// @ts-ignore
|
|
125
|
+
return typeDesc.arrayOfTypes.reduce((acc, type, i) => {
|
|
126
|
+
const readableTypeName = convertToReadableType(type, types, nameMap);
|
|
127
|
+
return i === 0 ? readableTypeName : `${acc} | ${readableTypeName}`;
|
|
128
|
+
}, "");
|
|
129
|
+
}
|
|
130
|
+
function formatArrayName(typeDesc, types, nameMap) {
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
const innerTypeId = typeDesc.arrayOfTypes[0];
|
|
133
|
+
// const isMultipleTypeArray = findTypeById(innerTypeId, types).arrayOfTypes.length > 1
|
|
134
|
+
const isMultipleTypeArray = isHash(innerTypeId) &&
|
|
135
|
+
findTypeById(innerTypeId, types).isUnion &&
|
|
136
|
+
// @ts-ignore
|
|
137
|
+
findTypeById(innerTypeId, types).arrayOfTypes.length > 1;
|
|
138
|
+
const readableInnerType = getArrayName(typeDesc, types, nameMap);
|
|
139
|
+
return isMultipleTypeArray
|
|
140
|
+
? `(${readableInnerType})[]` // add semicolons for union type
|
|
141
|
+
: `${readableInnerType}[]`;
|
|
142
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { TypeDescription, TypeStructure } from './model.js';
|
|
2
|
+
export declare function getTypeStructure(targetObj: any, // object that we want to create types for
|
|
3
|
+
types?: TypeDescription[]): TypeStructure;
|
|
4
|
+
export declare function optimizeTypeStructure(typeStructure: TypeStructure): void;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import { TypeGroup } from './model.js';
|
|
3
|
+
import { findTypeById, getTypeDescriptionGroup, isArray, isDate, isHash, isObject, onlyUnique, } from './util.js';
|
|
4
|
+
function createTypeDescription(typeObj, isUnion) {
|
|
5
|
+
if (isArray(typeObj)) {
|
|
6
|
+
return {
|
|
7
|
+
id: Hash(JSON.stringify([...typeObj, isUnion])),
|
|
8
|
+
arrayOfTypes: typeObj,
|
|
9
|
+
isUnion,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
return {
|
|
14
|
+
id: Hash(JSON.stringify(typeObj)),
|
|
15
|
+
typeObj,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function getIdByType(typeObj, types, isUnion = false) {
|
|
20
|
+
let typeDesc = types.find((el) => {
|
|
21
|
+
return typeObjectMatchesTypeDesc(typeObj, el, isUnion);
|
|
22
|
+
});
|
|
23
|
+
if (!typeDesc) {
|
|
24
|
+
typeDesc = createTypeDescription(typeObj, isUnion);
|
|
25
|
+
types.push(typeDesc);
|
|
26
|
+
}
|
|
27
|
+
return typeDesc.id;
|
|
28
|
+
}
|
|
29
|
+
function Hash(content) {
|
|
30
|
+
// Create a new SHA-1 hash object
|
|
31
|
+
const sha1Hash = crypto.createHash('sha1');
|
|
32
|
+
// Update the hash object with the content
|
|
33
|
+
sha1Hash.update(content);
|
|
34
|
+
// Calculate the digest in hexadecimal format
|
|
35
|
+
return sha1Hash.digest('hex');
|
|
36
|
+
}
|
|
37
|
+
function typeObjectMatchesTypeDesc(typeObj, typeDesc, isUnion) {
|
|
38
|
+
if (isArray(typeObj)) {
|
|
39
|
+
return (
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
arraysContainSameElements(typeObj, typeDesc.arrayOfTypes) &&
|
|
42
|
+
typeDesc.isUnion === isUnion);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
return objectsHaveSameEntries(typeObj, typeDesc.typeObj);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function arraysContainSameElements(arr1, arr2) {
|
|
49
|
+
if (arr1 === undefined || arr2 === undefined)
|
|
50
|
+
return false;
|
|
51
|
+
return arr1.sort().join('') === arr2.sort().join('');
|
|
52
|
+
}
|
|
53
|
+
function objectsHaveSameEntries(obj1, obj2) {
|
|
54
|
+
if (obj1 === undefined || obj2 === undefined)
|
|
55
|
+
return false;
|
|
56
|
+
const entries1 = Object.entries(obj1);
|
|
57
|
+
const entries2 = Object.entries(obj2);
|
|
58
|
+
const sameLength = entries1.length === entries2.length;
|
|
59
|
+
const sameTypes = entries1.every(([key, value]) => {
|
|
60
|
+
return obj2[key] === value;
|
|
61
|
+
});
|
|
62
|
+
return sameLength && sameTypes;
|
|
63
|
+
}
|
|
64
|
+
function getSimpleTypeName(value) {
|
|
65
|
+
if (value === null) {
|
|
66
|
+
return 'null';
|
|
67
|
+
}
|
|
68
|
+
else if (value instanceof Date) {
|
|
69
|
+
return 'Date';
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
return typeof value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getTypeGroup(value) {
|
|
76
|
+
if (isDate(value)) {
|
|
77
|
+
return TypeGroup.Date;
|
|
78
|
+
}
|
|
79
|
+
else if (isArray(value)) {
|
|
80
|
+
return TypeGroup.Array;
|
|
81
|
+
}
|
|
82
|
+
else if (isObject(value)) {
|
|
83
|
+
return TypeGroup.Object;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return TypeGroup.Primitive;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function createTypeObject(obj, types) {
|
|
90
|
+
return Object.entries(obj).reduce((typeObj, [key, value]) => {
|
|
91
|
+
const { rootTypeId } = getTypeStructure(value, types);
|
|
92
|
+
return {
|
|
93
|
+
...typeObj,
|
|
94
|
+
[key]: rootTypeId,
|
|
95
|
+
};
|
|
96
|
+
}, {});
|
|
97
|
+
}
|
|
98
|
+
function getMergedObjects(typesOfArray, types) {
|
|
99
|
+
const typeObjects = typesOfArray.map((typeDesc) => typeDesc.typeObj);
|
|
100
|
+
const allKeys = typeObjects
|
|
101
|
+
// @ts-expect-error
|
|
102
|
+
.map((typeObj) => Object.keys(typeObj))
|
|
103
|
+
.reduce((a, b) => [...a, ...b], [])
|
|
104
|
+
.filter(onlyUnique);
|
|
105
|
+
const commonKeys = typeObjects.reduce((commonKeys, typeObj) => {
|
|
106
|
+
// @ts-expect-error
|
|
107
|
+
const keys = Object.keys(typeObj);
|
|
108
|
+
return commonKeys.filter((key) => keys.includes(key));
|
|
109
|
+
}, allKeys);
|
|
110
|
+
const getKeyType = (key) => {
|
|
111
|
+
const typesOfKey = typeObjects
|
|
112
|
+
.filter((typeObj) => {
|
|
113
|
+
return Object.keys(typeObj).includes(key);
|
|
114
|
+
})
|
|
115
|
+
.map((typeObj) => typeObj[key])
|
|
116
|
+
.filter(onlyUnique);
|
|
117
|
+
if (typesOfKey.length === 1) {
|
|
118
|
+
return typesOfKey.pop();
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
return getInnerArrayType(typesOfKey, types);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const typeObj = allKeys.reduce((obj, key) => {
|
|
125
|
+
const isMandatory = commonKeys.includes(key);
|
|
126
|
+
const type = getKeyType(key);
|
|
127
|
+
const keyValue = isMandatory ? key : toOptionalKey(key);
|
|
128
|
+
return {
|
|
129
|
+
...obj,
|
|
130
|
+
[keyValue]: type,
|
|
131
|
+
};
|
|
132
|
+
}, {});
|
|
133
|
+
return getIdByType(typeObj, types, true);
|
|
134
|
+
}
|
|
135
|
+
function toOptionalKey(key) {
|
|
136
|
+
return key.endsWith('--?') ? key : `${key}--?`;
|
|
137
|
+
}
|
|
138
|
+
function getMergedArrays(typesOfArray, types) {
|
|
139
|
+
// @ts-expect-error
|
|
140
|
+
const idsOfArrayTypes = typesOfArray
|
|
141
|
+
?.map((typeDesc) => typeDesc.arrayOfTypes)
|
|
142
|
+
// @ts-expect-error
|
|
143
|
+
.reduce((a, b) => [...a, ...b], [])
|
|
144
|
+
.filter(onlyUnique);
|
|
145
|
+
if (idsOfArrayTypes.length === 1) {
|
|
146
|
+
return getIdByType([idsOfArrayTypes.pop()], types);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
return getIdByType([getInnerArrayType(idsOfArrayTypes, types)], types);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// we merge union types example: (number | string), null -> (number | string | null)
|
|
153
|
+
function getMergedUnion(typesOfArray, types) {
|
|
154
|
+
const innerUnionsTypes = typesOfArray
|
|
155
|
+
.map((id) => {
|
|
156
|
+
return findTypeById(id, types);
|
|
157
|
+
})
|
|
158
|
+
.filter((_) => !!_ && _.isUnion)
|
|
159
|
+
.map((_) => _.arrayOfTypes)
|
|
160
|
+
.reduce((a, b) => [...a, ...b], []);
|
|
161
|
+
const primitiveTypes = typesOfArray.filter((id) => !findTypeById(id, types) || !findTypeById(id, types).isUnion); // primitives or not union
|
|
162
|
+
return getIdByType([...innerUnionsTypes, ...primitiveTypes], types, true);
|
|
163
|
+
}
|
|
164
|
+
function getInnerArrayType(typesOfArray, types) {
|
|
165
|
+
// return inner array type
|
|
166
|
+
const containsUndefined = typesOfArray.includes('undefined');
|
|
167
|
+
const arrayTypesDescriptions = typesOfArray
|
|
168
|
+
.map((id) => findTypeById(id, types))
|
|
169
|
+
.filter((_) => !!_);
|
|
170
|
+
const allArrayType = arrayTypesDescriptions.filter((typeDesc) => getTypeDescriptionGroup(typeDesc) === TypeGroup.Array).length === typesOfArray.length;
|
|
171
|
+
const allArrayTypeWithUndefined = arrayTypesDescriptions.filter((typeDesc) => getTypeDescriptionGroup(typeDesc) === TypeGroup.Array).length +
|
|
172
|
+
1 ===
|
|
173
|
+
typesOfArray.length && containsUndefined;
|
|
174
|
+
const allObjectTypeWithUndefined = arrayTypesDescriptions.filter((typeDesc) => getTypeDescriptionGroup(typeDesc) === TypeGroup.Object).length +
|
|
175
|
+
1 ===
|
|
176
|
+
typesOfArray.length && containsUndefined;
|
|
177
|
+
const allObjectType = arrayTypesDescriptions.filter((typeDesc) => getTypeDescriptionGroup(typeDesc) === TypeGroup.Object).length === typesOfArray.length;
|
|
178
|
+
if (typesOfArray.length === 0) {
|
|
179
|
+
// no types in array -> empty union type
|
|
180
|
+
return getIdByType([], types, true);
|
|
181
|
+
}
|
|
182
|
+
if (typesOfArray.length === 1) {
|
|
183
|
+
// one type in array -> that will be our inner type
|
|
184
|
+
return typesOfArray.pop();
|
|
185
|
+
}
|
|
186
|
+
if (typesOfArray.length > 1) {
|
|
187
|
+
// multiple types in merge array
|
|
188
|
+
// if all are object we can merge them and return merged object as inner type
|
|
189
|
+
if (allObjectType)
|
|
190
|
+
return getMergedObjects(arrayTypesDescriptions, types);
|
|
191
|
+
// if all are array we can merge them and return merged array as inner type
|
|
192
|
+
if (allArrayType)
|
|
193
|
+
return getMergedArrays(arrayTypesDescriptions, types);
|
|
194
|
+
// all array types with posibble undefined, result type = undefined | (*mergedArray*)[]
|
|
195
|
+
if (allArrayTypeWithUndefined) {
|
|
196
|
+
return getMergedUnion([getMergedArrays(arrayTypesDescriptions, types), 'undefined'], types);
|
|
197
|
+
}
|
|
198
|
+
// all object types with posibble undefined, result type = undefined | *mergedObject*
|
|
199
|
+
if (allObjectTypeWithUndefined) {
|
|
200
|
+
return getMergedUnion([getMergedObjects(arrayTypesDescriptions, types), 'undefined'], types);
|
|
201
|
+
}
|
|
202
|
+
// if they are mixed or all primitive we cant merge them so we return as mixed union type
|
|
203
|
+
return getMergedUnion(typesOfArray, types);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export function getTypeStructure(targetObj, // object that we want to create types for
|
|
207
|
+
types = []) {
|
|
208
|
+
switch (getTypeGroup(targetObj)) {
|
|
209
|
+
case TypeGroup.Array:
|
|
210
|
+
const typesOfArray = targetObj
|
|
211
|
+
.map((_) => getTypeStructure(_, types).rootTypeId)
|
|
212
|
+
.filter(onlyUnique);
|
|
213
|
+
const arrayInnerTypeId = getInnerArrayType(typesOfArray, types); // create "union type of array types"
|
|
214
|
+
const typeId = getIdByType([arrayInnerTypeId], types); // create type "array of union type"
|
|
215
|
+
return {
|
|
216
|
+
rootTypeId: typeId,
|
|
217
|
+
types,
|
|
218
|
+
};
|
|
219
|
+
case TypeGroup.Object:
|
|
220
|
+
const typeObj = createTypeObject(targetObj, types);
|
|
221
|
+
const objType = getIdByType(typeObj, types);
|
|
222
|
+
return {
|
|
223
|
+
rootTypeId: objType,
|
|
224
|
+
types,
|
|
225
|
+
};
|
|
226
|
+
case TypeGroup.Primitive:
|
|
227
|
+
return {
|
|
228
|
+
rootTypeId: getSimpleTypeName(targetObj),
|
|
229
|
+
types,
|
|
230
|
+
};
|
|
231
|
+
case TypeGroup.Date:
|
|
232
|
+
const dateType = getSimpleTypeName(targetObj);
|
|
233
|
+
return {
|
|
234
|
+
rootTypeId: dateType,
|
|
235
|
+
types,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function getAllUsedTypeIds({ rootTypeId, types }) {
|
|
240
|
+
const typeDesc = types.find((_) => _.id === rootTypeId);
|
|
241
|
+
const subTypes = (typeDesc) => {
|
|
242
|
+
switch (getTypeDescriptionGroup(typeDesc)) {
|
|
243
|
+
case TypeGroup.Array:
|
|
244
|
+
const arrSubTypes = typeDesc.arrayOfTypes
|
|
245
|
+
.filter(isHash)
|
|
246
|
+
.map((typeId) => {
|
|
247
|
+
const typeDesc = types.find((_) => _.id === typeId);
|
|
248
|
+
return subTypes(typeDesc);
|
|
249
|
+
})
|
|
250
|
+
.reduce((a, b) => [...a, ...b], []);
|
|
251
|
+
return [typeDesc.id, ...arrSubTypes];
|
|
252
|
+
case TypeGroup.Object:
|
|
253
|
+
const objSubTypes = Object.values(typeDesc.typeObj)
|
|
254
|
+
// @ts-expect-error
|
|
255
|
+
.filter(isHash)
|
|
256
|
+
.map((typeId) => {
|
|
257
|
+
const typeDesc = types.find((_) => _.id === typeId);
|
|
258
|
+
return subTypes(typeDesc);
|
|
259
|
+
})
|
|
260
|
+
.reduce((a, b) => [...a, ...b], []);
|
|
261
|
+
return [typeDesc.id, ...objSubTypes];
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
return subTypes(typeDesc);
|
|
265
|
+
}
|
|
266
|
+
export function optimizeTypeStructure(typeStructure) {
|
|
267
|
+
const usedTypeIds = getAllUsedTypeIds(typeStructure);
|
|
268
|
+
const optimizedTypes = typeStructure.types.filter((typeDesc) => usedTypeIds.includes(typeDesc.id));
|
|
269
|
+
typeStructure.types = optimizedTypes;
|
|
270
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isArray, isObject } from './util.js';
|
|
2
|
+
import { getTypeStructure, optimizeTypeStructure, } from './get-type-structure.js';
|
|
3
|
+
import { getNames } from './get-names.js';
|
|
4
|
+
import { getInterfaceDescriptions, getInterfaceStringFromDescription, } from './get-interfaces.js';
|
|
5
|
+
export function jsonToTS(json, userOptions) {
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
rootName: 'RootObject',
|
|
8
|
+
};
|
|
9
|
+
const options = {
|
|
10
|
+
...defaultOptions,
|
|
11
|
+
...userOptions,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Parsing currently works with (Objects) and (Array of Objects) not and primitive types and mixed arrays etc..
|
|
15
|
+
* so we shall validate, so we dont start parsing non Object type
|
|
16
|
+
*/
|
|
17
|
+
const isArrayOfObjects = isArray(json) &&
|
|
18
|
+
json.length > 0 &&
|
|
19
|
+
json.reduce((a, b) => a && isObject(b), true);
|
|
20
|
+
if (!(isObject(json) || isArrayOfObjects)) {
|
|
21
|
+
throw new Error('Only (Object) and (Array of Object) are supported');
|
|
22
|
+
}
|
|
23
|
+
const typeStructure = getTypeStructure(json);
|
|
24
|
+
/**
|
|
25
|
+
* due to merging array types some types are switched out for merged ones
|
|
26
|
+
* so we delete the unused ones here
|
|
27
|
+
*/
|
|
28
|
+
optimizeTypeStructure(typeStructure);
|
|
29
|
+
const names = getNames(typeStructure, options.rootName);
|
|
30
|
+
return getInterfaceDescriptions(typeStructure, names).map((description) => getInterfaceStringFromDescription({
|
|
31
|
+
...description,
|
|
32
|
+
isRoot: options.rootName === description.name,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare enum TypeGroup {
|
|
2
|
+
Primitive = 0,
|
|
3
|
+
Array = 1,
|
|
4
|
+
Object = 2,
|
|
5
|
+
Date = 3
|
|
6
|
+
}
|
|
7
|
+
export interface TypeDescription {
|
|
8
|
+
id: string;
|
|
9
|
+
isUnion?: boolean;
|
|
10
|
+
typeObj?: {
|
|
11
|
+
[index: string]: string;
|
|
12
|
+
};
|
|
13
|
+
arrayOfTypes?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface TypeStructure {
|
|
16
|
+
rootTypeId: string;
|
|
17
|
+
types: TypeDescription[];
|
|
18
|
+
}
|
|
19
|
+
export interface NameEntry {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
export interface NameStructure {
|
|
24
|
+
rootName: string;
|
|
25
|
+
names: NameEntry[];
|
|
26
|
+
}
|
|
27
|
+
export interface InterfaceDescription {
|
|
28
|
+
name: string;
|
|
29
|
+
typeMap: object;
|
|
30
|
+
}
|
|
31
|
+
export interface Options {
|
|
32
|
+
rootName?: string;
|
|
33
|
+
/** To generate using type alias instead of interface */
|
|
34
|
+
useTypeAlias?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface KeyMetaData {
|
|
37
|
+
keyValue: string;
|
|
38
|
+
isOptional: boolean;
|
|
39
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export var TypeGroup;
|
|
2
|
+
(function (TypeGroup) {
|
|
3
|
+
TypeGroup[TypeGroup["Primitive"] = 0] = "Primitive";
|
|
4
|
+
TypeGroup[TypeGroup["Array"] = 1] = "Array";
|
|
5
|
+
TypeGroup[TypeGroup["Object"] = 2] = "Object";
|
|
6
|
+
TypeGroup[TypeGroup["Date"] = 3] = "Date";
|
|
7
|
+
})(TypeGroup || (TypeGroup = {}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { KeyMetaData, TypeDescription, TypeGroup } from './model.js';
|
|
2
|
+
export declare function isHash(str: string): boolean;
|
|
3
|
+
export declare function onlyUnique(value: any, index: any, self: any): boolean;
|
|
4
|
+
export declare function isArray(x: any): boolean;
|
|
5
|
+
export declare function isNonArrayUnion(typeName: string): boolean;
|
|
6
|
+
export declare function isObject(x: any): boolean;
|
|
7
|
+
export declare function isDate(x: any): x is Date;
|
|
8
|
+
export declare function parseKeyMetaData(key: string): KeyMetaData;
|
|
9
|
+
export declare function getTypeDescriptionGroup(desc: TypeDescription): TypeGroup;
|
|
10
|
+
export declare function findTypeById(id: string, types: TypeDescription[]): TypeDescription;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TypeGroup } from './model.js';
|
|
2
|
+
export function isHash(str) {
|
|
3
|
+
return str.length === 40;
|
|
4
|
+
}
|
|
5
|
+
export function onlyUnique(value, index, self) {
|
|
6
|
+
return self.indexOf(value) === index;
|
|
7
|
+
}
|
|
8
|
+
export function isArray(x) {
|
|
9
|
+
return Object.prototype.toString.call(x) === "[object Array]";
|
|
10
|
+
}
|
|
11
|
+
export function isNonArrayUnion(typeName) {
|
|
12
|
+
const arrayUnionRegex = /^\(.*\)\[\]$/;
|
|
13
|
+
return typeName.includes(" | ") && !arrayUnionRegex.test(typeName);
|
|
14
|
+
}
|
|
15
|
+
export function isObject(x) {
|
|
16
|
+
return Object.prototype.toString.call(x) === "[object Object]" && x !== null;
|
|
17
|
+
}
|
|
18
|
+
export function isDate(x) {
|
|
19
|
+
return x instanceof Date;
|
|
20
|
+
}
|
|
21
|
+
export function parseKeyMetaData(key) {
|
|
22
|
+
const isOptional = key.endsWith("--?");
|
|
23
|
+
if (isOptional) {
|
|
24
|
+
return {
|
|
25
|
+
isOptional,
|
|
26
|
+
keyValue: key.slice(0, -3)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return {
|
|
31
|
+
isOptional,
|
|
32
|
+
keyValue: key
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function getTypeDescriptionGroup(desc) {
|
|
37
|
+
if (desc === undefined) {
|
|
38
|
+
return TypeGroup.Primitive;
|
|
39
|
+
}
|
|
40
|
+
else if (desc.arrayOfTypes !== undefined) {
|
|
41
|
+
return TypeGroup.Array;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return TypeGroup.Object;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function findTypeById(id, types) {
|
|
48
|
+
return types.find(_ => _.id === id);
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@contentstorage/core",
|
|
3
3
|
"author": "Kaido Hussar <kaidohus@gmail.com>",
|
|
4
4
|
"homepage": "https://contentstorage.app",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.32",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Fetch content from contentstorage and generate TypeScript types",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"axios": "^1.7.2",
|
|
29
29
|
"chalk": "^4.1.2",
|
|
30
|
-
"
|
|
30
|
+
"es7-shim": "^6.0.0",
|
|
31
|
+
"pluralize": "^3.1.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@eslint/js": "^9.26.0",
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Helper function to define your application configuration.
|
|
3
|
-
* Provides autocompletion and type-checking for contentstorage.config.js files.
|
|
4
|
-
*/
|
|
5
|
-
export function defineConfig(config) {
|
|
6
|
-
// You can add basic runtime validation here if desired,
|
|
7
|
-
// e.g., check if contentUrl is a valid URL format,
|
|
8
|
-
// or if languageCodes is not empty.
|
|
9
|
-
if (!config.languageCodes || config.languageCodes.length === 0) {
|
|
10
|
-
console.warn('Warning: languageCodes array is empty or missing in the configuration.');
|
|
11
|
-
}
|
|
12
|
-
if (!config.contentDir) {
|
|
13
|
-
// This would typically be a hard error, but defineConfig is more for type safety at edit time.
|
|
14
|
-
// Runtime validation (see point 3) is better for hard errors.
|
|
15
|
-
console.warn('Warning: contentDir is missing in the configuration.');
|
|
16
|
-
}
|
|
17
|
-
// ... other checks
|
|
18
|
-
return config;
|
|
19
|
-
}
|