@majkapp/plugin-kit 3.3.4 → 3.4.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/README.md CHANGED
@@ -605,6 +605,10 @@ const plugin = definePlugin('my-plugin', 'My Plugin', '1.0.0');
605
605
 
606
606
  ## Troubleshooting
607
607
 
608
+ ### Client Generation Warnings
609
+
610
+ After running `npm run build`, check `ui/src/generated/generation.log` for schema optimization warnings. The generator analyzes your function input schemas and provides actionable suggestions for improving hook performance (e.g., flattening nested structures, reducing large objects).
611
+
608
612
  ### "React app not built"
609
613
 
610
614
  ```
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EAAE,GACZ,OAAO,CAAC,gBAAgB,CAAC,CAmC3B"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAgBD,wBAAsB,cAAc,CAClC,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EAAE,GACZ,OAAO,CAAC,gBAAgB,CAAC,CA8B3B"}
@@ -39,7 +39,6 @@ const path = __importStar(require("path"));
39
39
  async function generateClient(metadata, outputPath, log) {
40
40
  const warnings = [];
41
41
  const files = [];
42
- // Helper to write files
43
42
  const writeFile = (filename, content) => {
44
43
  const filePath = path.join(outputPath, filename);
45
44
  fs.writeFileSync(filePath, content, 'utf-8');
@@ -47,16 +46,12 @@ async function generateClient(metadata, outputPath, log) {
47
46
  log.push(` ✓ Generated ${filename}`);
48
47
  };
49
48
  try {
50
- // 1. Generate TypeScript types
51
49
  const types = generateTypes(metadata, warnings, log);
52
50
  writeFile('types.ts', types);
53
- // 2. Generate client class
54
51
  const client = generateClientClass(metadata, warnings, log);
55
52
  writeFile('client.ts', client);
56
- // 3. Generate React hooks
57
53
  const hooks = generateReactHooks(metadata, warnings, log);
58
54
  writeFile('hooks.ts', hooks);
59
- // 4. Generate index file
60
55
  const index = generateIndex();
61
56
  writeFile('index.ts', index);
62
57
  }
@@ -75,20 +70,16 @@ function generateTypes(metadata, warnings, log) {
75
70
  */
76
71
 
77
72
  `);
78
- // Extract types from function schemas
79
73
  const processedTypes = new Set();
80
74
  metadata.functions.forEach((func) => {
81
75
  try {
82
- // Generate input type
83
76
  const inputTypeName = `${capitalize(func.name)}Input`;
84
77
  if (!processedTypes.has(inputTypeName)) {
85
78
  lines.push(`export interface ${inputTypeName} ${schemaToTypeScript(func.input, 0)}\n`);
86
79
  processedTypes.add(inputTypeName);
87
80
  }
88
- // Generate output type
89
81
  const outputTypeName = `${capitalize(func.name)}Output`;
90
82
  if (!processedTypes.has(outputTypeName)) {
91
- // Handle array types specially - use type alias instead of interface
92
83
  if (func.output.type === 'array') {
93
84
  lines.push(`export type ${outputTypeName} = ${schemaToTypeScript(func.output, 0)};\n`);
94
85
  }
@@ -102,7 +93,6 @@ function generateTypes(metadata, warnings, log) {
102
93
  warnings.push(`Failed to generate types for ${func.name}: ${error.message}`);
103
94
  }
104
95
  });
105
- // Standard response types
106
96
  lines.push(`
107
97
  export interface SuccessResponse<T> {
108
98
  success: true;
@@ -134,13 +124,11 @@ ${metadata.functions.map((f) => ` ${capitalize(f.name)}Input,
134
124
 
135
125
  export class ${capitalize(toCamelCase(metadata.id))}Client {
136
126
  private getApiUrl(functionName: string): string {
137
- // Extract plugin path from current URL: /plugins/{org}/{id}/ui -> /plugins/{org}/{id}
138
127
  const path = window.location.pathname;
139
128
  const match = path.match(/(\\/plugins\\/[^\\/]+\\/[^\\/]+)\\//);
140
129
  if (match) {
141
130
  return \`\${match[1]}/api/\${functionName}\`;
142
131
  }
143
- // Fallback to relative path
144
132
  return \`../api/\${functionName}\`;
145
133
  }
146
134
 
@@ -159,14 +147,10 @@ export class ${capitalize(toCamelCase(metadata.id))}Client {
159
147
 
160
148
  const result = await response.json();
161
149
 
162
- // The transport wraps responses in { success: boolean, data?: T, error?: string }
163
- // Check for errors first
164
150
  if (result.success === false) {
165
151
  throw new Error(result.error || \`Function \${functionName} failed\`);
166
152
  }
167
153
 
168
- // Return the unwrapped data
169
- // The TypeScript output types represent the data inside the wrapper
170
154
  return result.data;
171
155
  } catch (error: any) {
172
156
  console.error(\`Error calling \${functionName}:\`, error);
@@ -174,7 +158,6 @@ export class ${capitalize(toCamelCase(metadata.id))}Client {
174
158
  }
175
159
  }
176
160
  `);
177
- // Generate methods for each function
178
161
  metadata.functions.forEach((func) => {
179
162
  try {
180
163
  const methodName = toCamelCase(func.name);
@@ -196,7 +179,6 @@ export class ${capitalize(toCamelCase(metadata.id))}Client {
196
179
  const className = `${capitalize(toCamelCase(metadata.id))}Client`;
197
180
  lines.push(`}
198
181
 
199
- // Default singleton instance
200
182
  export const client = new ${className}();
201
183
  `);
202
184
  return lines.join('');
@@ -207,6 +189,14 @@ function generateReactHooks(metadata, warnings, log) {
207
189
  * Auto-generated React hooks for ${metadata.name}
208
190
  * Generated at: ${new Date().toISOString()}
209
191
  * Plugin version: ${metadata.version}
192
+ *
193
+ * Hook Dependency Strategy:
194
+ * - Query hooks use schema-driven primitive dependency extraction
195
+ * - Primitive fields (string, number, boolean) are tracked individually via Object.is()
196
+ * - Complex fields (objects, arrays) are serialized only when necessary
197
+ * - This enables inline object literals without memoization while maintaining performance
198
+ *
199
+ * See generation.log for dependency analysis and schema optimization warnings.
210
200
  */
211
201
 
212
202
  import { useState, useEffect, useCallback } from 'react';
@@ -230,21 +220,34 @@ export interface UseMutationResult<TInput, TOutput> {
230
220
  loading: boolean;
231
221
  }
232
222
  `);
233
- // Generate hooks for each function
234
223
  metadata.functions.forEach((func) => {
235
224
  try {
236
225
  const hookName = `use${capitalize(func.name)}`;
237
226
  const inputType = `${capitalize(func.name)}Input`;
238
227
  const outputType = `${capitalize(func.name)}Output`;
239
- // Determine if it's a query or mutation based on name/tags
240
228
  const isQuery = func.name.startsWith('get') ||
241
229
  func.name.startsWith('list') ||
242
230
  func.name.includes('health') ||
243
231
  func.name.includes('status');
244
232
  if (isQuery) {
233
+ const analysis = analyzeSchemaDependencies(func.input, 'input', func.name);
234
+ logDependencyAnalysis(func.name, analysis, log);
235
+ analysis.warnings.forEach(w => {
236
+ const prefix = w.severity === 'error' ? '❌' : w.severity === 'warning' ? '⚠️' : 'ℹ️';
237
+ const msg = `${prefix} ${func.name}${w.field ? `.${w.field}` : ''}: ${w.message}${w.suggestion ? ` → ${w.suggestion}` : ''}`;
238
+ log.push(` ${msg}`);
239
+ warnings.push(msg);
240
+ });
241
+ const depExpressions = analysis.expressions.length > 0
242
+ ? [...analysis.expressions, 'options?.enabled']
243
+ : ['JSON.stringify(input)', 'options?.enabled'];
244
+ const depsString = depExpressions.join(', ');
245
+ const depComment = buildDependencyComment(analysis, func.input);
245
246
  lines.push(`
246
247
  /**
247
248
  * React hook for ${func.description}
249
+ *
250
+ * Dependencies (${analysis.primitiveCount} primitive, ${analysis.serializedCount} serialized):${depComment}
248
251
  */
249
252
  export function ${hookName}(
250
253
  input: ${inputType} = {},
@@ -268,7 +271,7 @@ export function ${hookName}(
268
271
  } finally {
269
272
  setLoading(false);
270
273
  }
271
- }, [JSON.stringify(input), options?.enabled]);
274
+ }, [${depsString}]);
272
275
 
273
276
  useEffect(() => {
274
277
  fetch();
@@ -331,12 +334,210 @@ export * from './client';
331
334
  export * from './hooks';
332
335
  `;
333
336
  }
334
- // Helper functions
337
+ function analyzeSchemaDependencies(schema, accessor, funcName, depth = 0, maxDepth = 1) {
338
+ const expressions = [];
339
+ const warnings = [];
340
+ let primitiveCount = 0;
341
+ let serializedCount = 0;
342
+ if (!schema || !schema.properties) {
343
+ if (schema && schema.type === 'object' && schema.additionalProperties) {
344
+ warnings.push({
345
+ severity: 'warning',
346
+ message: 'Schema uses additionalProperties - using full object serialization',
347
+ suggestion: 'Define explicit properties for better performance and type safety'
348
+ });
349
+ serializedCount++;
350
+ }
351
+ return { expressions, warnings, primitiveCount, serializedCount };
352
+ }
353
+ const props = schema.properties || {};
354
+ const propCount = Object.keys(props).length;
355
+ if (propCount === 0) {
356
+ warnings.push({
357
+ severity: 'info',
358
+ message: 'Input has no properties - empty object input'
359
+ });
360
+ return { expressions, warnings, primitiveCount, serializedCount };
361
+ }
362
+ if (propCount > 15) {
363
+ warnings.push({
364
+ severity: 'warning',
365
+ message: `Input has ${propCount} properties - consider splitting into smaller API calls`,
366
+ suggestion: 'Large input objects can cause performance issues and are harder to maintain'
367
+ });
368
+ }
369
+ for (const [key, prop] of Object.entries(props)) {
370
+ const path = `${accessor}?.${key}`;
371
+ const propAnalysis = analyzeProperty(prop, path, key, depth, maxDepth);
372
+ expressions.push(...propAnalysis.expressions);
373
+ warnings.push(...propAnalysis.warnings);
374
+ primitiveCount += propAnalysis.primitiveCount;
375
+ serializedCount += propAnalysis.serializedCount;
376
+ }
377
+ return { expressions, warnings, primitiveCount, serializedCount };
378
+ }
379
+ function analyzeProperty(prop, path, fieldName, depth, maxDepth) {
380
+ const expressions = [];
381
+ const warnings = [];
382
+ let primitiveCount = 0;
383
+ let serializedCount = 0;
384
+ if (isPrimitiveSchema(prop)) {
385
+ expressions.push(path);
386
+ primitiveCount++;
387
+ }
388
+ else if (prop.type === 'array') {
389
+ if (!prop.items) {
390
+ warnings.push({
391
+ severity: 'warning',
392
+ field: fieldName,
393
+ message: 'Array field has no items schema - using serialization',
394
+ suggestion: 'Define items schema for better type safety'
395
+ });
396
+ expressions.push(`JSON.stringify(${path})`);
397
+ serializedCount++;
398
+ }
399
+ else if (isPrimitiveSchema(prop.items)) {
400
+ expressions.push(`JSON.stringify(${path})`);
401
+ serializedCount++;
402
+ }
403
+ else {
404
+ warnings.push({
405
+ severity: 'info',
406
+ field: fieldName,
407
+ message: 'Array of complex objects requires serialization',
408
+ suggestion: 'Consider using IDs instead of full objects for better performance'
409
+ });
410
+ expressions.push(`JSON.stringify(${path})`);
411
+ serializedCount++;
412
+ }
413
+ }
414
+ else if (prop.type === 'object' || prop.properties) {
415
+ if (depth < maxDepth && isShallowObjectSchema(prop)) {
416
+ const nestedProps = prop.properties || {};
417
+ const nestedCount = Object.keys(nestedProps).length;
418
+ if (nestedCount > 10) {
419
+ warnings.push({
420
+ severity: 'warning',
421
+ field: fieldName,
422
+ message: `Nested object has ${nestedCount} properties - using serialization`,
423
+ suggestion: 'Consider flattening the structure or reducing nested properties'
424
+ });
425
+ expressions.push(`JSON.stringify(${path})`);
426
+ serializedCount++;
427
+ }
428
+ else {
429
+ for (const [nestedKey, nestedProp] of Object.entries(nestedProps)) {
430
+ const nestedPath = `${path}?.${nestedKey}`;
431
+ if (isPrimitiveSchema(nestedProp)) {
432
+ expressions.push(nestedPath);
433
+ primitiveCount++;
434
+ }
435
+ else {
436
+ expressions.push(`JSON.stringify(${nestedPath})`);
437
+ serializedCount++;
438
+ }
439
+ }
440
+ }
441
+ }
442
+ else {
443
+ if (depth >= maxDepth) {
444
+ warnings.push({
445
+ severity: 'info',
446
+ field: fieldName,
447
+ message: `Nested beyond depth ${maxDepth} - using serialization`,
448
+ suggestion: 'Consider flattening deeply nested structures'
449
+ });
450
+ }
451
+ expressions.push(`JSON.stringify(${path})`);
452
+ serializedCount++;
453
+ }
454
+ }
455
+ else if (prop.anyOf || prop.oneOf) {
456
+ warnings.push({
457
+ severity: 'info',
458
+ field: fieldName,
459
+ message: 'Union type field requires serialization',
460
+ suggestion: 'Union types cannot be optimized - this is expected'
461
+ });
462
+ expressions.push(`JSON.stringify(${path})`);
463
+ serializedCount++;
464
+ }
465
+ else {
466
+ warnings.push({
467
+ severity: 'warning',
468
+ field: fieldName,
469
+ message: `Unknown schema type - using serialization`,
470
+ suggestion: 'Check schema definition'
471
+ });
472
+ expressions.push(`JSON.stringify(${path})`);
473
+ serializedCount++;
474
+ }
475
+ return { expressions, warnings, primitiveCount, serializedCount };
476
+ }
477
+ function isPrimitiveSchema(schema) {
478
+ if (!schema)
479
+ return false;
480
+ const primitiveTypes = new Set(['string', 'number', 'integer', 'boolean', 'null']);
481
+ if (typeof schema.type === 'string') {
482
+ return primitiveTypes.has(schema.type);
483
+ }
484
+ if (Array.isArray(schema.type)) {
485
+ return schema.type.every((t) => t === 'null' || primitiveTypes.has(t));
486
+ }
487
+ return false;
488
+ }
489
+ function isShallowObjectSchema(schema) {
490
+ if (!schema)
491
+ return false;
492
+ if (schema.type !== 'object' && !schema.properties)
493
+ return false;
494
+ if (schema.anyOf || schema.oneOf || schema.allOf)
495
+ return false;
496
+ if (schema.additionalProperties === true)
497
+ return false;
498
+ return true;
499
+ }
500
+ function buildDependencyComment(analysis, schema) {
501
+ if (analysis.expressions.length === 0) {
502
+ return '\n * - Using full object serialization (no schema properties defined)';
503
+ }
504
+ const props = schema?.properties || {};
505
+ const lines = [];
506
+ for (const expr of analysis.expressions) {
507
+ if (expr === 'options?.enabled')
508
+ continue;
509
+ if (expr.includes('JSON.stringify')) {
510
+ const match = expr.match(/input\?\.(\w+)/);
511
+ const field = match ? match[1] : 'unknown';
512
+ const prop = props[field];
513
+ const type = prop?.type || 'unknown';
514
+ lines.push(`\n * - ${expr} (${type} - requires serialization)`);
515
+ }
516
+ else {
517
+ const match = expr.match(/input\?\.(\w+)/);
518
+ const field = match ? match[1] : 'unknown';
519
+ const prop = props[field];
520
+ const type = prop?.type || 'primitive';
521
+ lines.push(`\n * - ${expr} (${type} - O(1) comparison)`);
522
+ }
523
+ }
524
+ return lines.join('');
525
+ }
526
+ function logDependencyAnalysis(funcName, analysis, log) {
527
+ log.push(` 📊 ${funcName} dependency analysis:`);
528
+ log.push(` ✓ ${analysis.primitiveCount} primitive dependencies (fast)`);
529
+ if (analysis.serializedCount > 0) {
530
+ log.push(` ⚡ ${analysis.serializedCount} serialized dependencies`);
531
+ }
532
+ if (analysis.warnings.length === 0) {
533
+ log.push(` ✓ No schema issues detected`);
534
+ }
535
+ }
335
536
  function schemaToTypeScript(schema, indent) {
336
537
  if (!schema)
337
538
  return 'any';
338
539
  const spaces = ' '.repeat(indent);
339
- if (schema.type === 'object') {
540
+ if (schema.type === 'object' || schema.properties) {
340
541
  const props = schema.properties || {};
341
542
  const required = schema.required || [];
342
543
  const lines = ['{'];
@@ -370,7 +571,6 @@ function schemaToTypeScript(schema, indent) {
370
571
  if (schema.type === 'null') {
371
572
  return 'null';
372
573
  }
373
- // Handle union types
374
574
  if (Array.isArray(schema.type)) {
375
575
  return schema.type.map((t) => {
376
576
  if (t === 'null')
@@ -384,7 +584,6 @@ function schemaToTypeScript(schema, indent) {
384
584
  return 'any';
385
585
  }).join(' | ');
386
586
  }
387
- // Handle anyOf/oneOf
388
587
  if (schema.anyOf || schema.oneOf) {
389
588
  const schemas = schema.anyOf || schema.oneOf;
390
589
  return schemas.map((s) => schemaToTypeScript(s, indent)).join(' | ');
@@ -134,6 +134,12 @@ export interface FluentBuilder<Id extends string> {
134
134
  * .build();
135
135
  */
136
136
  pluginRoot(dir: string): this;
137
+ /**
138
+ * Disable automatic plugin service generation.
139
+ * By default, plugin-kit creates a service exposing all plugin functions.
140
+ * Use this to manage services manually.
141
+ */
142
+ noPluginService(): this;
137
143
  /** Build the plugin */
138
144
  build(): InProcessPlugin;
139
145
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-kit.d.ts","sourceRoot":"","sources":["../src/plugin-kit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin-kit.d.ts","sourceRoot":"","sources":["../src/plugin-kit.ts"],"names":[],"mappings":"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"}
@@ -665,6 +665,7 @@ function definePlugin(id, name, version) {
665
665
  let _onReady = null;
666
666
  let _healthCheck = null;
667
667
  let _pluginRoot = undefined;
668
+ let _autoPluginService = true; // Auto-generate plugin service by default
668
669
  const builder = {
669
670
  topbar(route, opts) {
670
671
  _topbars.push({
@@ -1099,6 +1100,11 @@ function definePlugin(id, name, version) {
1099
1100
  _pluginRoot = dir;
1100
1101
  return this;
1101
1102
  },
1103
+ noPluginService() {
1104
+ _autoPluginService = false;
1105
+ log(` 🚫 Disabled auto-generated plugin service`);
1106
+ return this;
1107
+ },
1102
1108
  build() {
1103
1109
  // ========== Load Package Info ==========
1104
1110
  let packageName;
@@ -1347,6 +1353,46 @@ function definePlugin(id, name, version) {
1347
1353
  }
1348
1354
  _capabilities.push(functionCap);
1349
1355
  }
1356
+ // ========== Auto-Generate Plugin Service ==========
1357
+ // Automatically create a service that exposes all plugin functions
1358
+ // unless explicitly disabled with .noPluginService()
1359
+ if (_autoPluginService && _functionRegistry.functions.size > 0) {
1360
+ const functionMetadata = [];
1361
+ // Build function metadata for all registered functions
1362
+ for (const [funcName, func] of _functionRegistry.functions.entries()) {
1363
+ const parameters = convertSchemaToParameters(func.input);
1364
+ const returns = convertSchemaToReturns(func.output);
1365
+ functionMetadata.push({
1366
+ name: funcName,
1367
+ description: func.description,
1368
+ parameters,
1369
+ returns,
1370
+ tags: [id], // Single tag: plugin ID
1371
+ deprecated: func.deprecated
1372
+ // NO examples - intentionally omitted
1373
+ });
1374
+ }
1375
+ // Use packageName if available, fallback to plugin id
1376
+ // This ensures service names like "@majk/task-manager:all" instead of "task-manager-sample:all"
1377
+ const serviceBaseName = packageName || id;
1378
+ const serviceTypeBaseName = packageName ? packageName : `@${id}`;
1379
+ // Create the auto-generated service capability
1380
+ const pluginServiceCapability = {
1381
+ type: 'service',
1382
+ serviceName: `${serviceBaseName}:all`,
1383
+ serviceType: `${serviceTypeBaseName}:all`,
1384
+ discoverable: true,
1385
+ metadata: {
1386
+ name: `${name} Functions`,
1387
+ description: `All functions from the ${name} plugin`,
1388
+ version: version,
1389
+ category: 'plugin'
1390
+ },
1391
+ functions: functionMetadata
1392
+ };
1393
+ _capabilities.push(pluginServiceCapability);
1394
+ log(` 🔄 Auto-generated service: ${serviceBaseName}:all (${functionMetadata.length} functions)`);
1395
+ }
1350
1396
  // ========== Build Plugin Class ==========
1351
1397
  // MAJK expects a class/constructor, not an instance
1352
1398
  // Return a class that instantiates BuiltPlugin
package/docs/HOOKS.md CHANGED
@@ -524,6 +524,11 @@ Generated files (DO NOT EDIT manually):
524
524
  - `ui/src/generated/client.ts` - RPC client
525
525
  - `ui/src/generated/types.ts` - TypeScript types
526
526
  - `ui/src/generated/index.ts` - Barrel export
527
+ - `ui/src/generated/generation.log` - Build log with warnings
528
+
529
+ ### Hook Performance Optimization
530
+
531
+ Generated hooks use **schema-driven dependency extraction** for optimal performance. Primitive fields (string, number, boolean) are tracked individually via `Object.is()` instead of expensive serialization. Check `generation.log` for dependency analysis and warnings about schema improvements.
527
532
 
528
533
  ## Next Steps
529
534
 
package/docs/INDEX.md CHANGED
@@ -421,7 +421,7 @@ export class MockStorageService implements StorageService {
421
421
 
422
422
  ## React UI with Generated Hooks
423
423
 
424
- The plugin-kit auto-generates React hooks from your functions.
424
+ The plugin-kit auto-generates **performance-optimized** React hooks from your functions. Dependencies are extracted from your input schemas for O(1) comparison instead of serialization. Review `ui/src/generated/generation.log` for optimization analysis and schema warnings.
425
425
 
426
426
  ```typescript
427
427
  // ui/src/DashboardPage.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majkapp/plugin-kit",
3
- "version": "3.3.4",
3
+ "version": "3.4.0",
4
4
  "description": "Pure plugin definition library for MAJK - outputs plugin definitions, not HTTP servers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,59 +0,0 @@
1
- import type { Transport, TransportMetadata, FunctionRegistry, PluginContext, RequestLike, ResponseLike } from './types';
2
- /**
3
- * HTTP Transport configuration
4
- */
5
- export interface HttpTransportConfig {
6
- basePath?: string;
7
- cors?: boolean;
8
- validation?: {
9
- request?: boolean;
10
- response?: boolean;
11
- };
12
- }
13
- /**
14
- * HTTP Transport implementation
15
- */
16
- export declare class HttpTransport implements Transport {
17
- name: string;
18
- private registry?;
19
- private context?;
20
- private config;
21
- constructor(config?: HttpTransportConfig);
22
- initialize(registry: FunctionRegistry, context: PluginContext): Promise<void>;
23
- start(): Promise<void>;
24
- stop(): Promise<void>;
25
- getMetadata(): TransportMetadata;
26
- /**
27
- * Handle a function call via HTTP
28
- */
29
- handleFunctionCall(functionName: string, req: RequestLike, res: ResponseLike, ctx: any): Promise<void>;
30
- /**
31
- * Get discovery information
32
- */
33
- getDiscovery(): any;
34
- }
35
- /**
36
- * WebSocket Transport (placeholder for future implementation)
37
- */
38
- export declare class WebSocketTransport implements Transport {
39
- name: string;
40
- private config;
41
- constructor(config?: any);
42
- initialize(registry: FunctionRegistry, context: PluginContext): Promise<void>;
43
- start(): Promise<void>;
44
- stop(): Promise<void>;
45
- getMetadata(): TransportMetadata;
46
- }
47
- /**
48
- * MCP Transport (placeholder for future implementation)
49
- */
50
- export declare class MCPTransport implements Transport {
51
- name: string;
52
- private config;
53
- constructor(config?: any);
54
- initialize(registry: FunctionRegistry, context: PluginContext): Promise<void>;
55
- start(): Promise<void>;
56
- stop(): Promise<void>;
57
- getMetadata(): TransportMetadata;
58
- }
59
- //# sourceMappingURL=transports.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"transports.d.ts","sourceRoot":"","sources":["../src/transports.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE;QACX,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,aAAc,YAAW,SAAS;IAC7C,IAAI,SAAU;IACd,OAAO,CAAC,QAAQ,CAAC,CAAmB;IACpC,OAAO,CAAC,OAAO,CAAC,CAAgB;IAChC,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,GAAE,mBAAwB;IAWtC,UAAU,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAM7E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,WAAW,IAAI,iBAAiB;IAUhC;;OAEG;IACG,kBAAkB,CACtB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,GAAG,GACP,OAAO,CAAC,IAAI,CAAC;IAgDhB;;OAEG;IACH,YAAY,IAAI,GAAG;CAkCpB;AAED;;GAEG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAClD,IAAI,SAAe;IACnB,OAAO,CAAC,MAAM,CAAM;gBAER,MAAM,GAAE,GAAQ;IAItB,UAAU,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,WAAW,IAAI,iBAAiB;CAMjC;AAED;;GAEG;AACH,qBAAa,YAAa,YAAW,SAAS;IAC5C,IAAI,SAAS;IACb,OAAO,CAAC,MAAM,CAAM;gBAER,MAAM,GAAE,GAAQ;IAItB,UAAU,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,WAAW,IAAI,iBAAiB;CAMjC"}
@@ -1,171 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MCPTransport = exports.WebSocketTransport = exports.HttpTransport = void 0;
4
- /**
5
- * HTTP Transport implementation
6
- */
7
- class HttpTransport {
8
- constructor(config = {}) {
9
- this.name = 'http';
10
- this.config = {
11
- basePath: config.basePath || '/api',
12
- cors: config.cors !== false,
13
- validation: {
14
- request: config.validation?.request !== false,
15
- response: config.validation?.response !== false
16
- }
17
- };
18
- }
19
- async initialize(registry, context) {
20
- this.registry = registry;
21
- this.context = context;
22
- context.logger.info(`HTTP Transport initialized with base path ${this.config.basePath}`);
23
- }
24
- async start() {
25
- // HTTP server is managed by the BuiltPlugin
26
- // This transport just provides the route handlers
27
- }
28
- async stop() {
29
- // Cleanup if needed
30
- }
31
- getMetadata() {
32
- // Use the baseUrl from context if available, otherwise construct from context.http
33
- const baseUrl = this.context?.http?.baseUrl || '';
34
- return {
35
- type: 'http',
36
- endpoint: `${baseUrl}${this.config.basePath}`,
37
- discovery: `${baseUrl}${this.config.basePath}/discovery`
38
- };
39
- }
40
- /**
41
- * Handle a function call via HTTP
42
- */
43
- async handleFunctionCall(functionName, req, res, ctx) {
44
- if (!this.registry) {
45
- res.status(500);
46
- res.json({ success: false, error: 'Transport not initialized' });
47
- return;
48
- }
49
- const func = this.registry.functions.get(functionName);
50
- if (!func) {
51
- res.status(404);
52
- res.json({ success: false, error: `Function "${functionName}" not found` });
53
- return;
54
- }
55
- try {
56
- // Get input from request body
57
- const input = req.body || {};
58
- // Validate input if configured
59
- if (this.config.validation?.request) {
60
- // TODO: Add actual JSON Schema validation here
61
- // For now, just check basic structure
62
- if (func.input.type === 'object' && typeof input !== 'object') {
63
- res.status(400);
64
- res.json({
65
- success: false,
66
- error: 'Invalid input',
67
- details: ['Input must be an object']
68
- });
69
- return;
70
- }
71
- }
72
- // Call the function handler
73
- const output = await func.handler(input, this.context);
74
- // Return success response
75
- res.json({ success: true, data: output });
76
- }
77
- catch (error) {
78
- this.context?.logger.error(`Function "${functionName}" error: ${error.message}`);
79
- res.status(500);
80
- res.json({
81
- success: false,
82
- error: error.message || 'Internal server error'
83
- });
84
- }
85
- }
86
- /**
87
- * Get discovery information
88
- */
89
- getDiscovery() {
90
- if (!this.registry) {
91
- return { error: 'Transport not initialized' };
92
- }
93
- const functions = [];
94
- for (const [name, func] of this.registry.functions) {
95
- functions.push({
96
- name,
97
- description: func.description,
98
- input: func.input,
99
- output: func.output,
100
- tags: func.tags,
101
- deprecated: func.deprecated
102
- });
103
- }
104
- const subscriptions = [];
105
- for (const [name, sub] of this.registry.subscriptions) {
106
- subscriptions.push({
107
- name,
108
- description: sub.description,
109
- input: sub.input,
110
- output: sub.output
111
- });
112
- }
113
- return {
114
- plugin: this.registry.plugin,
115
- functions,
116
- subscriptions,
117
- transports: [this.getMetadata()]
118
- };
119
- }
120
- }
121
- exports.HttpTransport = HttpTransport;
122
- /**
123
- * WebSocket Transport (placeholder for future implementation)
124
- */
125
- class WebSocketTransport {
126
- constructor(config = {}) {
127
- this.name = 'websocket';
128
- this.config = config;
129
- }
130
- async initialize(registry, context) {
131
- context.logger.info('WebSocket Transport initialized (not yet implemented)');
132
- }
133
- async start() {
134
- // TODO: Implement WebSocket server
135
- }
136
- async stop() {
137
- // TODO: Stop WebSocket server
138
- }
139
- getMetadata() {
140
- return {
141
- type: 'websocket',
142
- endpoint: `ws://localhost:${this.config.port || 3001}${this.config.path || '/ws'}`
143
- };
144
- }
145
- }
146
- exports.WebSocketTransport = WebSocketTransport;
147
- /**
148
- * MCP Transport (placeholder for future implementation)
149
- */
150
- class MCPTransport {
151
- constructor(config = {}) {
152
- this.name = 'mcp';
153
- this.config = config;
154
- }
155
- async initialize(registry, context) {
156
- context.logger.info('MCP Transport initialized (not yet implemented)');
157
- }
158
- async start() {
159
- // TODO: Register functions as MCP tools
160
- }
161
- async stop() {
162
- // TODO: Cleanup MCP registration
163
- }
164
- getMetadata() {
165
- return {
166
- type: 'mcp',
167
- name: this.config.name || 'plugin-mcp-server'
168
- };
169
- }
170
- }
171
- exports.MCPTransport = MCPTransport;