bro-framework 2.2.1 → 2.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.
package/src/sdk.js CHANGED
@@ -1,160 +1,169 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { parseRouteFile, scanDir } from './router.js';
4
-
5
- export async function generateSDK() {
6
- const routesDir = path.join(process.cwd(), 'routes');
7
- if (!fs.existsSync(routesDir)) {
8
- throw new Error(`Routes directory not found at ${routesDir}`);
9
- }
10
-
11
- const files = scanDir(routesDir);
12
- const endpoints = [];
13
-
14
- for (const file of files) {
15
- const routeInfo = parseRouteFile(file, routesDir);
16
- if (routeInfo) {
17
- endpoints.push(routeInfo);
18
- }
19
- }
20
-
21
- const code = `// Auto-generated by bro.js
22
- const CONFIG = {
23
- baseURL: 'http://localhost:5000',
24
- tokenKey: 'bro_token'
25
- };
26
-
27
- async function request(method, path, data) {
28
- const headers = {};
29
-
30
- if (typeof localStorage !== 'undefined') {
31
- const token = localStorage.getItem(CONFIG.tokenKey);
32
- if (token) {
33
- headers['Authorization'] = \`Bearer \${token}\`;
34
- }
35
- }
36
-
37
- const options = {
38
- method: method.toUpperCase(),
39
- headers
40
- };
41
-
42
- if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
43
- headers['Content-Type'] = 'application/json';
44
- options.body = JSON.stringify(data);
45
- } else if (data && ['GET', 'DELETE'].includes(method.toUpperCase())) {
46
- const params = new URLSearchParams(data);
47
- const qs = params.toString();
48
- if (qs) {
49
- path += '?' + qs;
50
- }
51
- }
52
-
53
- const url = CONFIG.baseURL + path;
54
- const response = await fetch(url, options);
55
-
56
- if (!response.ok) {
57
- let errMessage = response.statusText;
58
- try {
59
- const errData = await response.json();
60
- errMessage = errData.error || errData.message || errMessage;
61
- } catch (e) {}
62
- throw new Error(\`HTTP \${response.status}: \${errMessage}\`);
63
- }
64
-
65
- return response.json();
66
- }
67
-
68
- export const api = {
69
- ${generateApiObject(endpoints)}
70
- };
71
-
72
- export function setBaseURL(url) {
73
- CONFIG.baseURL = url;
74
- }
75
-
76
- export function setTokenKey(key) {
77
- CONFIG.tokenKey = key;
78
- }
79
- `;
80
-
81
- fs.writeFileSync(path.join(process.cwd(), 'bro-sdk.js'), code, 'utf-8');
82
- }
83
-
84
- function generateApiObject(endpoints) {
85
- const tree = {};
86
-
87
- for (const { routePath, method } of endpoints) {
88
- const parts = routePath.split('/').filter(Boolean);
89
-
90
- let current = tree;
91
- let pathAcc = '';
92
-
93
- for (let i = 0; i < parts.length; i++) {
94
- const part = parts[i];
95
- const isParam = part.startsWith(':');
96
- const name = isParam ? part.slice(1) : part;
97
- pathAcc += '/' + part;
98
-
99
- if (!current[name]) {
100
- current[name] = { _isParam: isParam, _methods: {}, _children: {}, _path: pathAcc };
101
- } else {
102
- if (current[name]._isParam !== isParam) {
103
- throw new Error(`SDK Collision: Route segment "${name}" conflicts between static and dynamic parameters at path "${pathAcc}"`);
104
- }
105
- }
106
-
107
- if (i === parts.length - 1) {
108
- current[name]._methods[method.toLowerCase()] = pathAcc;
109
- }
110
-
111
- current = current[name]._children;
112
- }
113
-
114
- if (parts.length === 0) {
115
- if (!tree['root']) tree['root'] = { _isParam: false, _methods: {}, _children: {}, _path: '/' };
116
- tree['root']._methods[method.toLowerCase()] = '/';
117
- }
118
- }
119
-
120
- function renderTree(node, indent = ' ') {
121
- let result = '';
122
-
123
- const isValidIdentifier = (key) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
124
-
125
- for (const [key, val] of Object.entries(node)) {
126
- const formattedKey = isValidIdentifier(key) ? key : `"${key}"`;
127
-
128
- if (val._isParam) {
129
- result += `${indent}${formattedKey}: (${key}) => ({\n`;
130
-
131
- for (const [m, p] of Object.entries(val._methods)) {
132
- const templatedPath = p.replace(/:([a-zA-Z0-9_]+)/g, '${encodeURIComponent($1)}');
133
- result += `${indent} ${m}: (data) => request('${m}', \`${templatedPath}\`, data),\n`;
134
- }
135
-
136
- const childrenStr = renderTree(val._children, indent + ' ');
137
- if (childrenStr) {
138
- result += childrenStr;
139
- }
140
-
141
- result += `${indent}}),\n`;
142
- } else {
143
- result += `${indent}${formattedKey}: {\n`;
144
- for (const [m, p] of Object.entries(val._methods)) {
145
- result += `${indent} ${m}: (data) => request('${m}', '${p}', data),\n`;
146
- }
147
-
148
- const childrenStr = renderTree(val._children, indent + ' ');
149
- if (childrenStr) {
150
- result += childrenStr;
151
- }
152
-
153
- result += `${indent}},\n`;
154
- }
155
- }
156
- return result;
157
- }
158
-
159
- return renderTree(tree);
160
- }
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { parseRouteFile, scanDir } from './router.js';
4
+
5
+ export async function generateSDK() {
6
+ const routesDir = path.join(process.cwd(), 'routes');
7
+ if (!fs.existsSync(routesDir)) {
8
+ throw new Error(`Routes directory not found at ${routesDir}`);
9
+ }
10
+
11
+ const files = scanDir(routesDir);
12
+ const endpoints = [];
13
+
14
+ for (const file of files) {
15
+ const routeInfo = parseRouteFile(file, routesDir);
16
+ if (routeInfo) {
17
+ endpoints.push(routeInfo);
18
+ }
19
+ }
20
+
21
+ const code = `// Auto-generated by bro.js
22
+ const CONFIG = {
23
+ baseURL: 'http://localhost:5000',
24
+ tokenKey: 'bro_token',
25
+ locale: undefined
26
+ };
27
+
28
+ async function request(method, path, data) {
29
+ const headers = {};
30
+
31
+ if (CONFIG.locale) {
32
+ headers['Accept-Language'] = CONFIG.locale;
33
+ }
34
+
35
+ if (typeof localStorage !== 'undefined') {
36
+ const token = localStorage.getItem(CONFIG.tokenKey);
37
+ if (token) {
38
+ headers['Authorization'] = \`Bearer \${token}\`;
39
+ }
40
+ }
41
+
42
+ const options = {
43
+ method: method.toUpperCase(),
44
+ headers
45
+ };
46
+
47
+ if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
48
+ headers['Content-Type'] = 'application/json';
49
+ options.body = JSON.stringify(data);
50
+ } else if (data && ['GET', 'DELETE'].includes(method.toUpperCase())) {
51
+ const params = new URLSearchParams(data);
52
+ const qs = params.toString();
53
+ if (qs) {
54
+ path += '?' + qs;
55
+ }
56
+ }
57
+
58
+ const url = CONFIG.baseURL + path;
59
+ const response = await fetch(url, options);
60
+
61
+ if (!response.ok) {
62
+ let errMessage = response.statusText;
63
+ try {
64
+ const errData = await response.json();
65
+ errMessage = errData.error || errData.message || errMessage;
66
+ } catch (e) {}
67
+ throw new Error(\`HTTP \${response.status}: \${errMessage}\`);
68
+ }
69
+
70
+ return response.json();
71
+ }
72
+
73
+ export const api = {
74
+ ${generateApiObject(endpoints)}
75
+ };
76
+
77
+ export function setBaseURL(url) {
78
+ CONFIG.baseURL = url;
79
+ }
80
+
81
+ export function setTokenKey(key) {
82
+ CONFIG.tokenKey = key;
83
+ }
84
+
85
+ export function setLocale(locale) {
86
+ CONFIG.locale = locale;
87
+ }
88
+ `;
89
+
90
+ fs.writeFileSync(path.join(process.cwd(), 'bro-sdk.js'), code, 'utf-8');
91
+ }
92
+
93
+ function generateApiObject(endpoints) {
94
+ const tree = Object.create(null);
95
+
96
+ for (const { routePath, method } of endpoints) {
97
+ const parts = routePath.split('/').filter(Boolean);
98
+
99
+ let current = tree;
100
+ let pathAcc = '';
101
+
102
+ for (let i = 0; i < parts.length; i++) {
103
+ const part = parts[i];
104
+ const isParam = part.startsWith(':');
105
+ const name = isParam ? part.slice(1) : part;
106
+ pathAcc += '/' + part;
107
+
108
+ if (!current[name]) {
109
+ current[name] = Object.assign(Object.create(null), { _isParam: isParam, _methods: Object.create(null), _children: Object.create(null), _path: pathAcc });
110
+ } else {
111
+ if (current[name]._isParam !== isParam) {
112
+ throw new Error(`SDK Collision: Route segment "${name}" conflicts between static and dynamic parameters at path "${pathAcc}"`);
113
+ }
114
+ }
115
+
116
+ if (i === parts.length - 1) {
117
+ current[name]._methods[method.toLowerCase()] = pathAcc;
118
+ }
119
+
120
+ current = current[name]._children;
121
+ }
122
+
123
+ if (parts.length === 0) {
124
+ if (!tree['root']) tree['root'] = Object.assign(Object.create(null), { _isParam: false, _methods: Object.create(null), _children: Object.create(null), _path: '/' });
125
+ tree['root']._methods[method.toLowerCase()] = '/';
126
+ }
127
+ }
128
+
129
+ function renderTree(node, indent = ' ') {
130
+ let result = '';
131
+
132
+ const isValidIdentifier = (key) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
133
+
134
+ for (const [key, val] of Object.entries(node)) {
135
+ const formattedKey = isValidIdentifier(key) ? key : `"${key}"`;
136
+
137
+ if (val._isParam) {
138
+ result += `${indent}${formattedKey}: (${key}) => ({\n`;
139
+
140
+ for (const [m, p] of Object.entries(val._methods)) {
141
+ const templatedPath = p.replace(/:([a-zA-Z0-9_$]+)/g, '${encodeURIComponent($1)}');
142
+ result += `${indent} ${m}: (data) => request('${m}', \`${templatedPath}\`, data),\n`;
143
+ }
144
+
145
+ const childrenStr = renderTree(val._children, indent + ' ');
146
+ if (childrenStr) {
147
+ result += childrenStr;
148
+ }
149
+
150
+ result += `${indent}}),\n`;
151
+ } else {
152
+ result += `${indent}${formattedKey}: {\n`;
153
+ for (const [m, p] of Object.entries(val._methods)) {
154
+ result += `${indent} ${m}: (data) => request('${m}', '${p}', data),\n`;
155
+ }
156
+
157
+ const childrenStr = renderTree(val._children, indent + ' ');
158
+ if (childrenStr) {
159
+ result += childrenStr;
160
+ }
161
+
162
+ result += `${indent}},\n`;
163
+ }
164
+ }
165
+ return result;
166
+ }
167
+
168
+ return renderTree(tree);
169
+ }