@major-tech/resource-client 0.1.1 → 0.1.3
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/README.md +28 -5
- package/bin/generate-clients.mjs +323 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ pnpm install @major-tech/resource-client
|
|
|
18
18
|
|
|
19
19
|
**PostgreSQL:**
|
|
20
20
|
```typescript
|
|
21
|
-
import { PostgresResourceClient } from '@major/resource-client';
|
|
21
|
+
import { PostgresResourceClient } from '@major-tech/resource-client';
|
|
22
22
|
const client = new PostgresResourceClient({ baseUrl, applicationId, resourceId, majorJwtToken? });
|
|
23
23
|
const res = await client.invoke('SELECT * FROM users WHERE id = $1', [123], 'fetch-user-by-id');
|
|
24
24
|
// res.ok ? res.result.rows / res.result.rowsAffected : res.error.message
|
|
@@ -26,7 +26,7 @@ const res = await client.invoke('SELECT * FROM users WHERE id = $1', [123], 'fet
|
|
|
26
26
|
|
|
27
27
|
**Custom API:**
|
|
28
28
|
```typescript
|
|
29
|
-
import { CustomApiResourceClient } from '@major/resource-client';
|
|
29
|
+
import { CustomApiResourceClient } from '@major-tech/resource-client';
|
|
30
30
|
const client = new CustomApiResourceClient({ baseUrl, applicationId, resourceId });
|
|
31
31
|
const res = await client.invoke('POST', '/v1/payments', 'create-payment', {
|
|
32
32
|
query: { currency: 'USD' }, headers: { 'X-Custom': 'value' },
|
|
@@ -37,7 +37,7 @@ const res = await client.invoke('POST', '/v1/payments', 'create-payment', {
|
|
|
37
37
|
|
|
38
38
|
**HubSpot:**
|
|
39
39
|
```typescript
|
|
40
|
-
import { HubSpotResourceClient } from '@major/resource-client';
|
|
40
|
+
import { HubSpotResourceClient } from '@major-tech/resource-client';
|
|
41
41
|
const client = new HubSpotResourceClient({ baseUrl, applicationId, resourceId });
|
|
42
42
|
const res = await client.invoke('GET', '/crm/v3/objects/contacts', 'fetch-contacts', { query: { limit: '10' } });
|
|
43
43
|
// res.ok && res.result.body.kind === 'json' ? res.result.body.value : res.error
|
|
@@ -45,7 +45,7 @@ const res = await client.invoke('GET', '/crm/v3/objects/contacts', 'fetch-contac
|
|
|
45
45
|
|
|
46
46
|
**S3:**
|
|
47
47
|
```typescript
|
|
48
|
-
import { S3ResourceClient } from '@major/resource-client';
|
|
48
|
+
import { S3ResourceClient } from '@major-tech/resource-client';
|
|
49
49
|
const client = new S3ResourceClient({ baseUrl, applicationId, resourceId });
|
|
50
50
|
const res = await client.invoke('ListObjectsV2', { Bucket: 'my-bucket', Prefix: 'uploads/' }, 'list-uploads');
|
|
51
51
|
// res.ok ? res.result.data : res.error
|
|
@@ -55,7 +55,7 @@ const url = await client.invoke('GeneratePresignedUrl', { Bucket: 'my-bucket', K
|
|
|
55
55
|
|
|
56
56
|
**Error Handling:**
|
|
57
57
|
```typescript
|
|
58
|
-
import { ResourceInvokeError } from '@major/resource-client';
|
|
58
|
+
import { ResourceInvokeError } from '@major-tech/resource-client';
|
|
59
59
|
try { await client.invoke(...); }
|
|
60
60
|
catch (e) { if (e instanceof ResourceInvokeError) { e.message, e.httpStatus, e.requestId } }
|
|
61
61
|
```
|
|
@@ -64,4 +64,27 @@ catch (e) { if (e instanceof ResourceInvokeError) { e.message, e.httpStatus, e.r
|
|
|
64
64
|
|
|
65
65
|
**Types:** All exported: `BaseClientConfig`, `DatabaseInvokeResponse`, `ApiInvokeResponse`, `StorageInvokeResponse`, `HttpMethod`, `QueryParams`, `BodyPayload`, `S3Command`, etc.
|
|
66
66
|
|
|
67
|
+
## 🛠️ CLI Tool - Generate Singleton Clients
|
|
68
|
+
|
|
69
|
+
The package includes a CLI tool to generate pre-configured singleton clients for your resources:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Add a resource
|
|
73
|
+
npx major-client add "resource-123" "orders-db" "database-postgresql" "Orders database" "app-456"
|
|
74
|
+
|
|
75
|
+
# List all resources
|
|
76
|
+
npx major-client list
|
|
77
|
+
|
|
78
|
+
# Remove a resource
|
|
79
|
+
npx major-client remove "orders-db"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This generates TypeScript files in `src/clients/` that you can import:
|
|
83
|
+
```typescript
|
|
84
|
+
import { ordersDbClient } from './clients';
|
|
85
|
+
const result = await ordersDbClient.invoke('SELECT * FROM orders', [], 'list-orders');
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Types:** `database-postgresql` | `api-custom` | `api-hubspot` | `storage-s3`
|
|
89
|
+
|
|
67
90
|
MIT License
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI tool to generate singleton resource clients
|
|
4
|
+
* Part of @major-tech/resource-client
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx @major-tech/resource-client add <resource_id> <name> <type> <description> <application_id>
|
|
8
|
+
* npx @major-tech/resource-client remove <name>
|
|
9
|
+
* npx @major-tech/resource-client list
|
|
10
|
+
*
|
|
11
|
+
* Types: database-postgresql | api-hubspot | api-custom | storage-s3
|
|
12
|
+
*
|
|
13
|
+
* Examples:
|
|
14
|
+
* npx @major-tech/resource-client add "abc-123" "orders-db" "database-postgresql" "Orders database" "app-123"
|
|
15
|
+
* npx @major-tech/resource-client add "xyz-789" "payment-api" "api-custom" "Payment API" "app-456"
|
|
16
|
+
* npx @major-tech/resource-client remove "orders-db"
|
|
17
|
+
* npx @major-tech/resource-client list
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import * as path from 'path';
|
|
22
|
+
import { fileURLToPath } from 'url';
|
|
23
|
+
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
+
const __dirname = path.dirname(__filename);
|
|
26
|
+
|
|
27
|
+
// User's project root (where the command is run from)
|
|
28
|
+
const projectRoot = process.cwd();
|
|
29
|
+
const resourcesConfigPath = path.join(projectRoot, 'resources.json');
|
|
30
|
+
const clientsDir = path.join(projectRoot, 'src', 'clients');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Client template
|
|
34
|
+
*/
|
|
35
|
+
function clientTemplate(data) {
|
|
36
|
+
return `import { ${data.clientClass} } from '@major-tech/resource-client';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* ${data.description}
|
|
40
|
+
*
|
|
41
|
+
* Type: ${data.type}
|
|
42
|
+
* Resource ID: ${data.resourceId}
|
|
43
|
+
* Application ID: ${data.applicationId}
|
|
44
|
+
*
|
|
45
|
+
* DO NOT EDIT - Auto-generated by @major-tech/resource-client
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
const BASE_URL = import.meta.env.MAJOR_API_BASE_URL || 'https://api.major.tech';
|
|
49
|
+
const MAJOR_JWT_TOKEN = import.meta.env.MAJOR_JWT_TOKEN;
|
|
50
|
+
|
|
51
|
+
class ${data.clientName}Singleton {
|
|
52
|
+
private static instance: ${data.clientClass} | null = null;
|
|
53
|
+
|
|
54
|
+
static getInstance(): ${data.clientClass} {
|
|
55
|
+
if (!${data.clientName}Singleton.instance) {
|
|
56
|
+
${data.clientName}Singleton.instance = new ${data.clientClass}({
|
|
57
|
+
baseUrl: BASE_URL,
|
|
58
|
+
majorJwtToken: MAJOR_JWT_TOKEN,
|
|
59
|
+
applicationId: '${data.applicationId}',
|
|
60
|
+
resourceId: '${data.resourceId}',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return ${data.clientName}Singleton.instance;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const ${data.clientName} = ${data.clientName}Singleton.getInstance();
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Index template
|
|
73
|
+
*/
|
|
74
|
+
function indexTemplate(data) {
|
|
75
|
+
return `/**
|
|
76
|
+
* Auto-generated client exports
|
|
77
|
+
* DO NOT EDIT - Generated by @major-tech/resource-client
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
${data.exports.join('\n')}
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Load resources from JSON config file
|
|
86
|
+
*/
|
|
87
|
+
function loadResources() {
|
|
88
|
+
if (!fs.existsSync(resourcesConfigPath)) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const content = fs.readFileSync(resourcesConfigPath, 'utf-8');
|
|
92
|
+
return JSON.parse(content);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Save resources to JSON config file
|
|
97
|
+
*/
|
|
98
|
+
function saveResources(resources) {
|
|
99
|
+
const dir = path.dirname(resourcesConfigPath);
|
|
100
|
+
if (!fs.existsSync(dir)) {
|
|
101
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
fs.writeFileSync(resourcesConfigPath, JSON.stringify(resources, null, 2), 'utf-8');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Convert a resource name to a valid camelCase identifier
|
|
108
|
+
* e.g., "yo mama" -> "yoMama", "orders-db" -> "ordersDb"
|
|
109
|
+
*/
|
|
110
|
+
function toCamelCase(str) {
|
|
111
|
+
return str
|
|
112
|
+
.toLowerCase()
|
|
113
|
+
.replace(/[^a-z0-9]+(.)/g, (_, char) => char.toUpperCase())
|
|
114
|
+
.replace(/^[^a-z]+/, ''); // Remove leading non-letters
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getClientClass(type) {
|
|
118
|
+
const typeMap = {
|
|
119
|
+
'database-postgresql': 'PostgresResourceClient',
|
|
120
|
+
'api-custom': 'CustomApiResourceClient',
|
|
121
|
+
'api-hubspot': 'HubSpotResourceClient',
|
|
122
|
+
'storage-s3': 'S3ResourceClient',
|
|
123
|
+
};
|
|
124
|
+
return typeMap[type] || 'PostgresResourceClient';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function generateClientFile(resource) {
|
|
128
|
+
const clientName = toCamelCase(resource.name) + 'Client';
|
|
129
|
+
const clientClass = getClientClass(resource.type);
|
|
130
|
+
|
|
131
|
+
return clientTemplate({
|
|
132
|
+
clientClass,
|
|
133
|
+
description: resource.description,
|
|
134
|
+
type: resource.type,
|
|
135
|
+
resourceId: resource.id,
|
|
136
|
+
applicationId: resource.applicationId,
|
|
137
|
+
clientName,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function generateIndexFile(resources) {
|
|
142
|
+
if (resources.length === 0) {
|
|
143
|
+
return `// No clients configured\n`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const exports = resources.map(resource => {
|
|
147
|
+
const clientName = toCamelCase(resource.name) + 'Client';
|
|
148
|
+
const fileName = toCamelCase(resource.name);
|
|
149
|
+
return `export { ${clientName} } from './${fileName}';`;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return indexTemplate({ exports });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function addResource(resourceId, name, type, description, applicationId) {
|
|
156
|
+
const validTypes = ['database-postgresql', 'api-hubspot', 'api-custom', 'storage-s3'];
|
|
157
|
+
if (!validTypes.includes(type)) {
|
|
158
|
+
console.error(`❌ Invalid type: ${type}`);
|
|
159
|
+
console.log(` Valid types: ${validTypes.join(', ')}`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!applicationId) {
|
|
164
|
+
console.error('❌ Application ID is required');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const resources = loadResources();
|
|
169
|
+
|
|
170
|
+
const existing = resources.find(r => r.name === name);
|
|
171
|
+
if (existing) {
|
|
172
|
+
console.error(`❌ Resource with name "${name}" already exists`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const newResource = {
|
|
177
|
+
id: resourceId,
|
|
178
|
+
name,
|
|
179
|
+
type,
|
|
180
|
+
description,
|
|
181
|
+
applicationId
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
resources.push(newResource);
|
|
185
|
+
saveResources(resources);
|
|
186
|
+
|
|
187
|
+
console.log(`✅ Added resource: ${name}`);
|
|
188
|
+
console.log(` Type: ${type}`);
|
|
189
|
+
console.log(` ID: ${resourceId}`);
|
|
190
|
+
|
|
191
|
+
regenerateClients(resources);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Remove a resource by name
|
|
196
|
+
*/
|
|
197
|
+
function removeResource(name) {
|
|
198
|
+
const resources = loadResources();
|
|
199
|
+
const index = resources.findIndex(r => r.name === name);
|
|
200
|
+
|
|
201
|
+
if (index === -1) {
|
|
202
|
+
console.error(`❌ Resource "${name}" not found`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const removed = resources.splice(index, 1)[0];
|
|
207
|
+
saveResources(resources);
|
|
208
|
+
|
|
209
|
+
console.log(`✅ Removed resource: ${removed.name}`);
|
|
210
|
+
console.log(` ID: ${removed.id}`);
|
|
211
|
+
|
|
212
|
+
regenerateClients(resources);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* List all resources
|
|
217
|
+
*/
|
|
218
|
+
function listResources() {
|
|
219
|
+
const resources = loadResources();
|
|
220
|
+
|
|
221
|
+
if (resources.length === 0) {
|
|
222
|
+
console.log('No resources configured');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(`Resources (${resources.length}):\n`);
|
|
227
|
+
resources.forEach((r, idx) => {
|
|
228
|
+
console.log(`${idx + 1}. ${r.name} (${r.type})`);
|
|
229
|
+
console.log(` ID: ${r.id}`);
|
|
230
|
+
console.log(` Client: ${toCamelCase(r.name)}Client`);
|
|
231
|
+
console.log('');
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Regenerate all client files
|
|
237
|
+
*/
|
|
238
|
+
function regenerateClients(resources) {
|
|
239
|
+
// Ensure clients directory exists
|
|
240
|
+
if (!fs.existsSync(clientsDir)) {
|
|
241
|
+
fs.mkdirSync(clientsDir, { recursive: true });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Clear existing client files (except index.ts temporarily)
|
|
245
|
+
const existingFiles = fs.readdirSync(clientsDir).filter(f => f !== 'index.ts');
|
|
246
|
+
existingFiles.forEach(file => {
|
|
247
|
+
fs.unlinkSync(path.join(clientsDir, file));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Generate individual client files
|
|
251
|
+
resources.forEach(resource => {
|
|
252
|
+
const fileName = toCamelCase(resource.name) + '.ts';
|
|
253
|
+
const filePath = path.join(clientsDir, fileName);
|
|
254
|
+
const code = generateClientFile(resource);
|
|
255
|
+
fs.writeFileSync(filePath, code, 'utf-8');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Generate index file
|
|
259
|
+
const indexPath = path.join(clientsDir, 'index.ts');
|
|
260
|
+
const indexCode = generateIndexFile(resources);
|
|
261
|
+
fs.writeFileSync(indexPath, indexCode, 'utf-8');
|
|
262
|
+
|
|
263
|
+
console.log(`✅ Generated ${resources.length} client(s) in ${clientsDir}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Main execution
|
|
268
|
+
*/
|
|
269
|
+
function main() {
|
|
270
|
+
const args = process.argv.slice(2);
|
|
271
|
+
const command = args[0];
|
|
272
|
+
|
|
273
|
+
if (!command) {
|
|
274
|
+
console.log('Usage:');
|
|
275
|
+
console.log(' npx @major-tech/resource-client add <resource_id> <name> <type> <description> <application_id>');
|
|
276
|
+
console.log(' npx @major-tech/resource-client remove <name>');
|
|
277
|
+
console.log(' npx @major-tech/resource-client list');
|
|
278
|
+
console.log('\nTypes: database-postgresql | api-hubspot | api-custom | storage-s3');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
switch (command) {
|
|
283
|
+
case 'add': {
|
|
284
|
+
const [, resourceId, name, type, description, applicationId] = args;
|
|
285
|
+
if (!resourceId || !name || !type || !description || !applicationId) {
|
|
286
|
+
console.error('❌ Missing arguments');
|
|
287
|
+
console.log('Usage: add <resource_id> <name> <type> <description> <application_id>');
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
addResource(resourceId, name, type, description, applicationId);
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
case 'remove': {
|
|
295
|
+
const [, name] = args;
|
|
296
|
+
if (!name) {
|
|
297
|
+
console.error('❌ Missing name');
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
removeResource(name);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
case 'list': {
|
|
305
|
+
listResources();
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
case 'regenerate': {
|
|
310
|
+
const resources = loadResources();
|
|
311
|
+
regenerateClients(resources);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
default: {
|
|
316
|
+
console.error(`❌ Unknown command: ${command}`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
main();
|
|
323
|
+
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@major-tech/resource-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "TypeScript client library for invoking Major resources (PostgreSQL, Custom APIs, HubSpot, S3)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=18.0.0"
|
|
9
9
|
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"major-client": "./bin/generate-clients.mjs"
|
|
12
|
+
},
|
|
10
13
|
"files": [
|
|
11
14
|
"dist",
|
|
15
|
+
"bin",
|
|
12
16
|
"README.md",
|
|
13
17
|
"LICENSE"
|
|
14
18
|
],
|