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.
@@ -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
+ }