loginterface 1.0.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.
- package/dist/index.d.ts +11 -0
- package/dist/index.js +176 -0
- package/index.ts +214 -0
- package/package.json +22 -0
- package/tsconfig.json +13 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param interfaceName Interface name
|
|
4
|
+
* @returns Same stream
|
|
5
|
+
* @description RXJS operator that generates interfaces for passed data and log it into console when on localhost
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* this.http.get('/endpoint').pipe(map(res => res.data), generateInterface('Interface name'))
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare const logInterface: (interfaceName: string) => any;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { tap } from 'rxjs';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param interfaceName Interface name
|
|
5
|
+
* @returns Same stream
|
|
6
|
+
* @description RXJS operator that generates interfaces for passed data and log it into console when on localhost
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* this.http.get('/endpoint').pipe(map(res => res.data), generateInterface('Interface name'))
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export const logInterface = (interfaceName) => {
|
|
13
|
+
return tap((obj) => {
|
|
14
|
+
generateInterfaces(obj, interfaceName);
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param obj Object to create interface for
|
|
20
|
+
* @param rootName Interface name
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
23
|
+
const generateInterfaces = (obj, rootName = 'Generated') => {
|
|
24
|
+
const data = obj;
|
|
25
|
+
let interfaceData;
|
|
26
|
+
if (Array.isArray(data)) {
|
|
27
|
+
interfaceData = mergeFromFirst(data);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
interfaceData = data;
|
|
31
|
+
}
|
|
32
|
+
const interfaces = {};
|
|
33
|
+
const processed = new Set();
|
|
34
|
+
const capitalize = (str) => {
|
|
35
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
36
|
+
};
|
|
37
|
+
const resolveType = (value, parentName, propName) => {
|
|
38
|
+
if (value === null)
|
|
39
|
+
return 'any';
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
if (value.length === 0)
|
|
42
|
+
return 'any[]';
|
|
43
|
+
const firstItem = value.find((v) => v !== null);
|
|
44
|
+
if (!firstItem)
|
|
45
|
+
return 'any[]';
|
|
46
|
+
if (typeof firstItem === 'object' && !Array.isArray(firstItem)) {
|
|
47
|
+
const interfaceName = parentName + capitalize(propName);
|
|
48
|
+
generateInterface(firstItem, interfaceName);
|
|
49
|
+
return `${interfaceName}[]`;
|
|
50
|
+
}
|
|
51
|
+
return `${resolvePrimitive(firstItem)}[]`;
|
|
52
|
+
}
|
|
53
|
+
if (typeof value === 'object') {
|
|
54
|
+
const interfaceName = parentName + capitalize(propName);
|
|
55
|
+
generateInterface(value, interfaceName);
|
|
56
|
+
return interfaceName;
|
|
57
|
+
}
|
|
58
|
+
return resolvePrimitive(value);
|
|
59
|
+
};
|
|
60
|
+
const generateInterface = (obj, name) => {
|
|
61
|
+
if (processed.has(name))
|
|
62
|
+
return;
|
|
63
|
+
processed.add(name);
|
|
64
|
+
interfaces[name] = '';
|
|
65
|
+
const fields = Object.entries(obj)
|
|
66
|
+
.map(([key, value]) => {
|
|
67
|
+
const type = resolveType(value, name, key);
|
|
68
|
+
return ` ${key}: ${type};`;
|
|
69
|
+
})
|
|
70
|
+
.join('\n');
|
|
71
|
+
interfaces[name] = `interface ${name} {\n${fields}\n}`;
|
|
72
|
+
};
|
|
73
|
+
generateInterface(interfaceData, rootName);
|
|
74
|
+
let result = Object.values(interfaces).join('\n\n');
|
|
75
|
+
result = result.replace(`interface ${rootName} {`, `export interface ${rootName} {`);
|
|
76
|
+
colorizeInterface(result, rootName);
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
*
|
|
81
|
+
* @param code Console log output
|
|
82
|
+
* @param rootName Name of interface
|
|
83
|
+
* @description Colorize the console log to look good
|
|
84
|
+
*/
|
|
85
|
+
const colorizeInterface = (code, rootName) => {
|
|
86
|
+
const styles = [];
|
|
87
|
+
const purple = 'color: #C586C0; font-weight: bold;';
|
|
88
|
+
const blue = 'color: #569CD6; font-weight: bold;';
|
|
89
|
+
const green = 'color: #2cb69a; font-weight: bold;';
|
|
90
|
+
const darkGreen = 'color: #75ba55; font-weight: bold;';
|
|
91
|
+
const reset = '';
|
|
92
|
+
const primitives = [
|
|
93
|
+
'string',
|
|
94
|
+
'number',
|
|
95
|
+
'boolean',
|
|
96
|
+
'any',
|
|
97
|
+
'bigint',
|
|
98
|
+
'undefined',
|
|
99
|
+
'symbol',
|
|
100
|
+
];
|
|
101
|
+
//primitives first, then export/interface, then capitalized words
|
|
102
|
+
const regex = new RegExp(`\\b(${primitives.join('|')})\\b|\\b(export)\\b|\\b(interface)\\b|\\b([A-Za-z0-9_]+)\\b`, 'g');
|
|
103
|
+
let lastMatchedKeyword = '';
|
|
104
|
+
const formatted = code.replace(regex, (match, primitiveWord, exportWord, interfaceWord, nameWord) => {
|
|
105
|
+
if (primitiveWord) {
|
|
106
|
+
styles.push(darkGreen, reset);
|
|
107
|
+
lastMatchedKeyword = '';
|
|
108
|
+
return `%c${primitiveWord}%c`;
|
|
109
|
+
}
|
|
110
|
+
if (exportWord) {
|
|
111
|
+
styles.push(purple, reset);
|
|
112
|
+
lastMatchedKeyword = 'export';
|
|
113
|
+
return `%c${exportWord}%c`;
|
|
114
|
+
}
|
|
115
|
+
if (interfaceWord) {
|
|
116
|
+
styles.push(blue, reset);
|
|
117
|
+
lastMatchedKeyword = 'interface';
|
|
118
|
+
return `%c${interfaceWord}%c`;
|
|
119
|
+
}
|
|
120
|
+
if (nameWord) {
|
|
121
|
+
if (lastMatchedKeyword === 'interface') {
|
|
122
|
+
styles.push(green, reset);
|
|
123
|
+
lastMatchedKeyword = '';
|
|
124
|
+
return `%c${nameWord}%c`;
|
|
125
|
+
}
|
|
126
|
+
return nameWord;
|
|
127
|
+
}
|
|
128
|
+
return match;
|
|
129
|
+
});
|
|
130
|
+
if (location.href.includes('localhost')) {
|
|
131
|
+
//delay to clear angular logs first
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
console.clear();
|
|
134
|
+
console.groupCollapsed(`%cclick to view interface for %c${rootName}`, 'color: #0fbffe; font-size: 14px', 'color: #04d55b; font-size: 14px');
|
|
135
|
+
console.log(formatted, ...styles);
|
|
136
|
+
console.groupEnd();
|
|
137
|
+
}, 100);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const resolvePrimitive = (value) => {
|
|
141
|
+
switch (typeof value) {
|
|
142
|
+
case 'string':
|
|
143
|
+
return 'string';
|
|
144
|
+
case 'number':
|
|
145
|
+
return 'number';
|
|
146
|
+
case 'boolean':
|
|
147
|
+
return 'boolean';
|
|
148
|
+
case 'bigint':
|
|
149
|
+
return 'bigint';
|
|
150
|
+
case 'symbol':
|
|
151
|
+
return 'symbol';
|
|
152
|
+
case 'undefined':
|
|
153
|
+
return 'undefined';
|
|
154
|
+
default:
|
|
155
|
+
return 'any';
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
*
|
|
160
|
+
* @param items Array of items
|
|
161
|
+
* @returns Item of the array containing minimum null values
|
|
162
|
+
* @description Some array items have properties that are null, instead of setting that property to type 'any',
|
|
163
|
+
* we loop trough every item and try to find if that property is not null somewhere and we take that value
|
|
164
|
+
*/
|
|
165
|
+
const mergeFromFirst = (items) => {
|
|
166
|
+
if (!items.length)
|
|
167
|
+
return {};
|
|
168
|
+
return items.slice(1).reduce((acc, item) => {
|
|
169
|
+
Object.entries(item).forEach(([key, value]) => {
|
|
170
|
+
if (value != null) {
|
|
171
|
+
acc[key] = value;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return acc;
|
|
175
|
+
}, { ...items[0] });
|
|
176
|
+
};
|
package/index.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { tap } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param interfaceName Interface name
|
|
6
|
+
* @returns Same stream
|
|
7
|
+
* @description RXJS operator that generates interfaces for passed data and log it into console when on localhost
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* this.http.get('/endpoint').pipe(map(res => res.data), generateInterface('Interface name'))
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export const logInterface = (interfaceName: string) => {
|
|
14
|
+
return tap((obj: any) => {
|
|
15
|
+
generateInterfaces(obj, interfaceName);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param obj Object to create interface for
|
|
22
|
+
* @param rootName Interface name
|
|
23
|
+
* @returns
|
|
24
|
+
*/
|
|
25
|
+
const generateInterfaces = (obj: any, rootName = 'Generated') => {
|
|
26
|
+
const data = obj;
|
|
27
|
+
let interfaceData;
|
|
28
|
+
|
|
29
|
+
if (Array.isArray(data)) {
|
|
30
|
+
interfaceData = mergeFromFirst(data);
|
|
31
|
+
} else {
|
|
32
|
+
interfaceData = data;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const interfaces: any = {};
|
|
36
|
+
const processed = new Set();
|
|
37
|
+
|
|
38
|
+
const capitalize = (str: string) => {
|
|
39
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const resolveType = (value: any, parentName: string, propName: string) => {
|
|
43
|
+
if (value === null) return 'any';
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
if (value.length === 0) return 'any[]';
|
|
47
|
+
|
|
48
|
+
const firstItem = value.find((v) => v !== null);
|
|
49
|
+
|
|
50
|
+
if (!firstItem) return 'any[]';
|
|
51
|
+
|
|
52
|
+
if (typeof firstItem === 'object' && !Array.isArray(firstItem)) {
|
|
53
|
+
const interfaceName = parentName + capitalize(propName);
|
|
54
|
+
generateInterface(firstItem, interfaceName);
|
|
55
|
+
return `${interfaceName}[]`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return `${resolvePrimitive(firstItem)}[]`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof value === 'object') {
|
|
62
|
+
const interfaceName = parentName + capitalize(propName);
|
|
63
|
+
generateInterface(value, interfaceName);
|
|
64
|
+
return interfaceName;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return resolvePrimitive(value);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const generateInterface = (obj: any, name: any) => {
|
|
71
|
+
if (processed.has(name)) return;
|
|
72
|
+
processed.add(name);
|
|
73
|
+
|
|
74
|
+
interfaces[name] = '';
|
|
75
|
+
|
|
76
|
+
const fields = Object.entries(obj)
|
|
77
|
+
.map(([key, value]) => {
|
|
78
|
+
const type = resolveType(value, name, key);
|
|
79
|
+
return ` ${key}: ${type};`;
|
|
80
|
+
})
|
|
81
|
+
.join('\n');
|
|
82
|
+
|
|
83
|
+
interfaces[name] = `interface ${name} {\n${fields}\n}`;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
generateInterface(interfaceData, rootName);
|
|
87
|
+
|
|
88
|
+
let result = Object.values(interfaces).join('\n\n');
|
|
89
|
+
result = result.replace(
|
|
90
|
+
`interface ${rootName} {`,
|
|
91
|
+
`export interface ${rootName} {`,
|
|
92
|
+
);
|
|
93
|
+
colorizeInterface(result, rootName);
|
|
94
|
+
return result;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* @param code Console log output
|
|
100
|
+
* @param rootName Name of interface
|
|
101
|
+
* @description Colorize the console log to look good
|
|
102
|
+
*/
|
|
103
|
+
const colorizeInterface = (code: string, rootName: string) => {
|
|
104
|
+
const styles: string[] = [];
|
|
105
|
+
|
|
106
|
+
const purple = 'color: #C586C0; font-weight: bold;';
|
|
107
|
+
const blue = 'color: #569CD6; font-weight: bold;';
|
|
108
|
+
const green = 'color: #2cb69a; font-weight: bold;';
|
|
109
|
+
const darkGreen = 'color: #75ba55; font-weight: bold;';
|
|
110
|
+
const reset = '';
|
|
111
|
+
|
|
112
|
+
const primitives = [
|
|
113
|
+
'string',
|
|
114
|
+
'number',
|
|
115
|
+
'boolean',
|
|
116
|
+
'any',
|
|
117
|
+
'bigint',
|
|
118
|
+
'undefined',
|
|
119
|
+
'symbol',
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
//primitives first, then export/interface, then capitalized words
|
|
123
|
+
const regex = new RegExp(
|
|
124
|
+
`\\b(${primitives.join('|')})\\b|\\b(export)\\b|\\b(interface)\\b|\\b([A-Za-z0-9_]+)\\b`,
|
|
125
|
+
'g',
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
let lastMatchedKeyword = '';
|
|
129
|
+
|
|
130
|
+
const formatted = code.replace(
|
|
131
|
+
regex,
|
|
132
|
+
(match, primitiveWord, exportWord, interfaceWord, nameWord) => {
|
|
133
|
+
if (primitiveWord) {
|
|
134
|
+
styles.push(darkGreen, reset);
|
|
135
|
+
lastMatchedKeyword = '';
|
|
136
|
+
return `%c${primitiveWord}%c`;
|
|
137
|
+
}
|
|
138
|
+
if (exportWord) {
|
|
139
|
+
styles.push(purple, reset);
|
|
140
|
+
lastMatchedKeyword = 'export';
|
|
141
|
+
return `%c${exportWord}%c`;
|
|
142
|
+
}
|
|
143
|
+
if (interfaceWord) {
|
|
144
|
+
styles.push(blue, reset);
|
|
145
|
+
lastMatchedKeyword = 'interface';
|
|
146
|
+
return `%c${interfaceWord}%c`;
|
|
147
|
+
}
|
|
148
|
+
if (nameWord) {
|
|
149
|
+
if (lastMatchedKeyword === 'interface') {
|
|
150
|
+
styles.push(green, reset);
|
|
151
|
+
lastMatchedKeyword = '';
|
|
152
|
+
return `%c${nameWord}%c`;
|
|
153
|
+
}
|
|
154
|
+
return nameWord;
|
|
155
|
+
}
|
|
156
|
+
return match;
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
if (location.href.includes('localhost')) {
|
|
160
|
+
//delay to clear angular logs first
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
console.clear()
|
|
163
|
+
console.groupCollapsed(
|
|
164
|
+
`%cclick to view interface for %c${rootName}`,
|
|
165
|
+
'color: #0fbffe; font-size: 14px',
|
|
166
|
+
'color: #04d55b; font-size: 14px',
|
|
167
|
+
);
|
|
168
|
+
console.log(formatted, ...styles);
|
|
169
|
+
console.groupEnd();
|
|
170
|
+
}, 100);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const resolvePrimitive = (value: string) => {
|
|
175
|
+
switch (typeof value) {
|
|
176
|
+
case 'string':
|
|
177
|
+
return 'string';
|
|
178
|
+
case 'number':
|
|
179
|
+
return 'number';
|
|
180
|
+
case 'boolean':
|
|
181
|
+
return 'boolean';
|
|
182
|
+
case 'bigint':
|
|
183
|
+
return 'bigint';
|
|
184
|
+
case 'symbol':
|
|
185
|
+
return 'symbol';
|
|
186
|
+
case 'undefined':
|
|
187
|
+
return 'undefined';
|
|
188
|
+
default:
|
|
189
|
+
return 'any';
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
*
|
|
195
|
+
* @param items Array of items
|
|
196
|
+
* @returns Item of the array containing minimum null values
|
|
197
|
+
* @description Some array items have properties that are null, instead of setting that property to type 'any',
|
|
198
|
+
* we loop trough every item and try to find if that property is not null somewhere and we take that value
|
|
199
|
+
*/
|
|
200
|
+
const mergeFromFirst = (items: any) => {
|
|
201
|
+
if (!items.length) return {};
|
|
202
|
+
|
|
203
|
+
return items.slice(1).reduce(
|
|
204
|
+
(acc: any, item: any) => {
|
|
205
|
+
Object.entries(item).forEach(([key, value]) => {
|
|
206
|
+
if (value != null) {
|
|
207
|
+
acc[key] = value;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return acc;
|
|
211
|
+
},
|
|
212
|
+
{ ...items[0] },
|
|
213
|
+
);
|
|
214
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "loginterface",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"build": "tsc"
|
|
9
|
+
},
|
|
10
|
+
"author": "Milan Cakic",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"description": "",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"rxjs": "^7.0.0"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"rxjs": "^7.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "^5.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext", // or "CommonJS" if you target Node
|
|
4
|
+
"moduleResolution": "node", // <-- key
|
|
5
|
+
"esModuleInterop": true, // <-- key for default imports
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"target": "ES2019",
|
|
8
|
+
"lib": ["ES2019", "DOM"],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"outDir": "dist",
|
|
11
|
+
"declaration": true
|
|
12
|
+
}
|
|
13
|
+
}
|