@lark-apaas/nestjs-capability 0.1.3 → 0.1.4-alpha.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 CHANGED
@@ -178,7 +178,7 @@ interface PluginInstance {
178
178
  aggregate?(actionName: string, chunks: unknown[]): unknown;
179
179
  }
180
180
  interface PluginPackage {
181
- create(): PluginInstance;
181
+ create(config?: unknown): PluginInstance;
182
182
  }
183
183
 
184
184
  /**
@@ -258,6 +258,14 @@ declare class TemplateEngineService {
258
258
  private getDefaultValueFromSchema;
259
259
  }
260
260
 
261
+ interface PluginManifest {
262
+ name: string;
263
+ form?: {
264
+ refType?: 'config' | 'action';
265
+ schema?: unknown;
266
+ };
267
+ [key: string]: unknown;
268
+ }
261
269
  declare class PluginNotFoundError extends Error {
262
270
  readonly pluginKey: string;
263
271
  constructor(pluginKey: string);
@@ -270,11 +278,30 @@ declare class PluginLoaderService {
270
278
  private readonly pluginInstances;
271
279
  /** 记录每个插件的加载版本(时间戳),用于 ESM 缓存绕过 */
272
280
  private readonly pluginVersions;
273
- loadPlugin(pluginKey: string): Promise<PluginInstance>;
281
+ /** 缓存插件的 manifest.json */
282
+ private readonly manifestCache;
283
+ /**
284
+ * 从入口路径向上查找包根目录(包含 package.json 的目录)
285
+ */
286
+ private findPackageRoot;
287
+ /**
288
+ * 读取并缓存 manifest
289
+ */
290
+ getManifest(pluginKey: string): PluginManifest | null;
291
+ /**
292
+ * 检查插件是否需要 config(form.refType === 'config')
293
+ */
294
+ isConfigRequired(pluginKey: string): boolean;
295
+ loadPlugin(pluginKey: string, config?: unknown): Promise<PluginInstance>;
296
+ /**
297
+ * 创建插件实例(内部方法)
298
+ */
299
+ private createPluginInstance;
274
300
  isPluginInstalled(pluginKey: string): boolean;
275
301
  /**
276
302
  * 清除插件缓存
277
303
  * - 清除应用层 pluginInstances 缓存
304
+ * - 清除 manifest 缓存
278
305
  * - 清除 Node.js CJS 模块缓存(require.cache)
279
306
  * - 更新版本号,下次 import 时绕过 ESM 缓存
280
307
  * @param pluginKey - 插件标识,不传则清除所有
@@ -394,6 +421,11 @@ declare class CapabilityService implements OnModuleInit, OnModuleDestroy {
394
421
  */
395
422
  loadWithConfig(config: CapabilityConfig): CapabilityExecutor;
396
423
  private createExecutor;
424
+ /**
425
+ * 加载插件实例并解析参数
426
+ * 根据 manifest 的 form.refType 决定 formValue 的消费方式
427
+ */
428
+ private loadPluginAndResolveParams;
397
429
  /**
398
430
  * 检查 action 是否为流式
399
431
  */
@@ -535,4 +567,4 @@ interface MigrationResponse {
535
567
  */
536
568
  declare function migrationAdaptor(promise: Promise<unknown>): Promise<MigrationResponse>;
537
569
 
538
- export { ActionNotFoundError, type ActionSchema, type CapabilityConfig, type CapabilityExecutor, type CapabilityListItem, CapabilityModule, type CapabilityModuleOptions, CapabilityNotFoundError, CapabilityService, DebugController, type DebugExecuteResponseData, type DebugInfo, type ErrorCode, ErrorCodes, type ErrorResponse, type ExecuteResponseData, type FailureOutput, type JSONSchema, type ListResponseData, type MigrationResponse, type PluginActionContext, type PluginInstance, PluginLoadError, PluginLoaderService, PluginNotFoundError, type PluginPackage, type StreamContentResponse, type StreamDoneMetadata, type StreamError, type StreamErrorResponse, type StreamEvent, type StreamResponse, type SuccessResponse, TemplateEngineService, type UserContext, WebhookController, migrationAdaptor };
570
+ export { ActionNotFoundError, type ActionSchema, type CapabilityConfig, type CapabilityExecutor, type CapabilityListItem, CapabilityModule, type CapabilityModuleOptions, CapabilityNotFoundError, CapabilityService, DebugController, type DebugExecuteResponseData, type DebugInfo, type ErrorCode, ErrorCodes, type ErrorResponse, type ExecuteResponseData, type FailureOutput, type JSONSchema, type ListResponseData, type MigrationResponse, type PluginActionContext, type PluginInstance, PluginLoadError, PluginLoaderService, type PluginManifest, PluginNotFoundError, type PluginPackage, type StreamContentResponse, type StreamDoneMetadata, type StreamError, type StreamErrorResponse, type StreamEvent, type StreamResponse, type SuccessResponse, TemplateEngineService, type UserContext, WebhookController, migrationAdaptor };
package/dist/index.js CHANGED
@@ -65,13 +65,13 @@ var TemplateEngineService = class {
65
65
  resolveString(template, input, paramsSchema) {
66
66
  const wholeMatch = template.match(this.WHOLE_STRING_EXPR_REGEX);
67
67
  if (wholeMatch) {
68
- const path2 = wholeMatch[1];
69
- const value = this.getValueByPath(input, path2);
68
+ const path3 = wholeMatch[1];
69
+ const value = this.getValueByPath(input, path3);
70
70
  if (value !== void 0) {
71
71
  return value;
72
72
  }
73
73
  if (paramsSchema) {
74
- const defaultValue = this.getDefaultValueForPath(path2, paramsSchema);
74
+ const defaultValue = this.getDefaultValueForPath(path3, paramsSchema);
75
75
  if (defaultValue !== void 0) {
76
76
  return defaultValue;
77
77
  }
@@ -83,8 +83,8 @@ var TemplateEngineService = class {
83
83
  return template;
84
84
  }
85
85
  this.EXPR_REGEX.lastIndex = 0;
86
- const result = template.replace(this.EXPR_REGEX, (match, path2) => {
87
- const value = this.getValueByPath(input, path2);
86
+ const result = template.replace(this.EXPR_REGEX, (match, path3) => {
87
+ const value = this.getValueByPath(input, path3);
88
88
  if (value === void 0) {
89
89
  return match;
90
90
  }
@@ -99,8 +99,8 @@ var TemplateEngineService = class {
99
99
  }
100
100
  return result;
101
101
  }
102
- getValueByPath(obj, path2) {
103
- const keys = path2.split(".");
102
+ getValueByPath(obj, path3) {
103
+ const keys = path3.split(".");
104
104
  let current = obj;
105
105
  for (const key of keys) {
106
106
  if (current === null || current === void 0) {
@@ -116,8 +116,8 @@ var TemplateEngineService = class {
116
116
  * @param schema - JSON Schema
117
117
  * @returns 默认值,如果无法确定则返回 undefined
118
118
  */
119
- getDefaultValueForPath(path2, schema) {
120
- const fieldSchema = this.getSchemaForPath(path2, schema);
119
+ getDefaultValueForPath(path3, schema) {
120
+ const fieldSchema = this.getSchemaForPath(path3, schema);
121
121
  if (!fieldSchema) {
122
122
  return void 0;
123
123
  }
@@ -129,8 +129,8 @@ var TemplateEngineService = class {
129
129
  * @param schema - 根 JSON Schema
130
130
  * @returns 路径对应的 schema,如果不存在则返回 undefined
131
131
  */
132
- getSchemaForPath(path2, schema) {
133
- const keys = path2.split(".");
132
+ getSchemaForPath(path3, schema) {
133
+ const keys = path3.split(".");
134
134
  let currentSchema = schema;
135
135
  for (const key of keys) {
136
136
  if (!currentSchema?.properties) {
@@ -179,6 +179,8 @@ TemplateEngineService = _ts_decorate([
179
179
  import { Injectable as Injectable2, Logger } from "@nestjs/common";
180
180
  import { createRequire } from "module";
181
181
  import { pathToFileURL } from "url";
182
+ import * as fs from "fs";
183
+ import * as path from "path";
182
184
  function _ts_decorate2(decorators, target, key, desc) {
183
185
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
184
186
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -215,13 +217,75 @@ var PluginLoaderService = class _PluginLoaderService {
215
217
  pluginInstances = /* @__PURE__ */ new Map();
216
218
  /** 记录每个插件的加载版本(时间戳),用于 ESM 缓存绕过 */
217
219
  pluginVersions = /* @__PURE__ */ new Map();
218
- async loadPlugin(pluginKey) {
220
+ /** 缓存插件的 manifest.json */
221
+ manifestCache = /* @__PURE__ */ new Map();
222
+ /**
223
+ * 从入口路径向上查找包根目录(包含 package.json 的目录)
224
+ */
225
+ findPackageRoot(entryPath) {
226
+ let dir = path.dirname(entryPath);
227
+ while (dir !== path.dirname(dir)) {
228
+ if (fs.existsSync(path.join(dir, "package.json"))) {
229
+ return dir;
230
+ }
231
+ dir = path.dirname(dir);
232
+ }
233
+ return null;
234
+ }
235
+ /**
236
+ * 读取并缓存 manifest
237
+ */
238
+ getManifest(pluginKey) {
239
+ if (this.manifestCache.has(pluginKey)) {
240
+ return this.manifestCache.get(pluginKey) ?? null;
241
+ }
242
+ try {
243
+ const entryPath = require2.resolve(pluginKey);
244
+ const packageRoot = this.findPackageRoot(entryPath);
245
+ if (!packageRoot) {
246
+ this.manifestCache.set(pluginKey, null);
247
+ return null;
248
+ }
249
+ const manifestPath = path.join(packageRoot, "manifest.json");
250
+ if (!fs.existsSync(manifestPath)) {
251
+ this.manifestCache.set(pluginKey, null);
252
+ return null;
253
+ }
254
+ const content = fs.readFileSync(manifestPath, "utf-8");
255
+ const manifest = JSON.parse(content);
256
+ this.manifestCache.set(pluginKey, manifest);
257
+ return manifest;
258
+ } catch {
259
+ this.manifestCache.set(pluginKey, null);
260
+ return null;
261
+ }
262
+ }
263
+ /**
264
+ * 检查插件是否需要 config(form.refType === 'config')
265
+ */
266
+ isConfigRequired(pluginKey) {
267
+ const manifest = this.getManifest(pluginKey);
268
+ return manifest?.form?.refType === "config";
269
+ }
270
+ async loadPlugin(pluginKey, config) {
271
+ const isConfigRequired = this.isConfigRequired(pluginKey);
272
+ if (isConfigRequired) {
273
+ return this.createPluginInstance(pluginKey, config);
274
+ }
219
275
  const cached = this.pluginInstances.get(pluginKey);
220
276
  if (cached) {
221
277
  this.logger.debug(`Using cached plugin instance: ${pluginKey}`);
222
278
  return cached;
223
279
  }
224
- this.logger.log(`Loading plugin: ${pluginKey}`);
280
+ const instance = await this.createPluginInstance(pluginKey);
281
+ this.pluginInstances.set(pluginKey, instance);
282
+ return instance;
283
+ }
284
+ /**
285
+ * 创建插件实例(内部方法)
286
+ */
287
+ async createPluginInstance(pluginKey, config) {
288
+ this.logger.log(`Loading plugin: ${pluginKey}${config !== void 0 ? " (with config)" : ""}`);
225
289
  try {
226
290
  const resolvedPath = require2.resolve(pluginKey);
227
291
  const version = this.pluginVersions.get(pluginKey) ?? 0;
@@ -232,8 +296,7 @@ var PluginLoaderService = class _PluginLoaderService {
232
296
  if (typeof pluginPackage.create !== "function") {
233
297
  throw new PluginLoadError(pluginKey, "Plugin does not export create() function");
234
298
  }
235
- const instance = pluginPackage.create();
236
- this.pluginInstances.set(pluginKey, instance);
299
+ const instance = pluginPackage.create(config);
237
300
  this.logger.log(`Plugin loaded successfully: ${pluginKey}`);
238
301
  return instance;
239
302
  } catch (error) {
@@ -254,6 +317,7 @@ var PluginLoaderService = class _PluginLoaderService {
254
317
  /**
255
318
  * 清除插件缓存
256
319
  * - 清除应用层 pluginInstances 缓存
320
+ * - 清除 manifest 缓存
257
321
  * - 清除 Node.js CJS 模块缓存(require.cache)
258
322
  * - 更新版本号,下次 import 时绕过 ESM 缓存
259
323
  * @param pluginKey - 插件标识,不传则清除所有
@@ -261,6 +325,7 @@ var PluginLoaderService = class _PluginLoaderService {
261
325
  clearCache(pluginKey) {
262
326
  if (pluginKey) {
263
327
  this.pluginInstances.delete(pluginKey);
328
+ this.manifestCache.delete(pluginKey);
264
329
  this.clearNodeModuleCache(pluginKey);
265
330
  this.pluginVersions.set(pluginKey, Date.now());
266
331
  this.logger.log(`Cleared cache for plugin: ${pluginKey}`);
@@ -270,6 +335,7 @@ var PluginLoaderService = class _PluginLoaderService {
270
335
  this.pluginVersions.set(key, Date.now());
271
336
  }
272
337
  this.pluginInstances.clear();
338
+ this.manifestCache.clear();
273
339
  this.logger.log("Cleared all plugin caches");
274
340
  }
275
341
  }
@@ -315,8 +381,8 @@ PluginLoaderService = _ts_decorate2([
315
381
  // src/services/capability.service.ts
316
382
  import { Injectable as Injectable3, Logger as Logger2, Inject } from "@nestjs/common";
317
383
  import { RequestContextService, PLATFORM_HTTP_CLIENT } from "@lark-apaas/nestjs-common";
318
- import * as fs from "fs";
319
- import * as path from "path";
384
+ import * as fs2 from "fs";
385
+ import * as path2 from "path";
320
386
  import * as chokidar from "chokidar";
321
387
 
322
388
  // src/utils/log-utils.ts
@@ -439,7 +505,7 @@ var CapabilityService = class _CapabilityService {
439
505
  this.pluginLoaderService = pluginLoaderService;
440
506
  this.templateEngineService = templateEngineService;
441
507
  const isDev = process.env.NODE_ENV === "development";
442
- this.capabilitiesDir = path.join(process.cwd(), isDev ? "server/capabilities" : "capabilities");
508
+ this.capabilitiesDir = path2.join(process.cwd(), isDev ? "server/capabilities" : "capabilities");
443
509
  }
444
510
  /**
445
511
  * 设置模块配置
@@ -470,11 +536,11 @@ var CapabilityService = class _CapabilityService {
470
536
  if (this.fileWatcher) {
471
537
  return;
472
538
  }
473
- if (!fs.existsSync(this.capabilitiesDir)) {
539
+ if (!fs2.existsSync(this.capabilitiesDir)) {
474
540
  this.logger.warn(`Cannot start watching: directory not found: ${this.capabilitiesDir}`);
475
541
  return;
476
542
  }
477
- const pattern = path.join(this.capabilitiesDir, "*.json");
543
+ const pattern = path2.join(this.capabilitiesDir, "*.json");
478
544
  const debounce = this.options.watchDebounce ?? 300;
479
545
  this.logger.log(`Starting file watcher: ${pattern}`);
480
546
  this.fileWatcher = chokidar.watch(pattern, {
@@ -539,7 +605,7 @@ var CapabilityService = class _CapabilityService {
539
605
  * 从文件加载单个能力配置
540
606
  */
541
607
  loadCapabilityFromFile(filePath) {
542
- const content = fs.readFileSync(filePath, "utf-8");
608
+ const content = fs2.readFileSync(filePath, "utf-8");
543
609
  const config = JSON.parse(content);
544
610
  if (!config.id) {
545
611
  this.logger.warn(`Skipping capability without id: ${filePath}`);
@@ -577,14 +643,14 @@ var CapabilityService = class _CapabilityService {
577
643
  // ==================== 原有方法 ====================
578
644
  async loadCapabilities() {
579
645
  this.logger.log(`Loading capabilities from ${this.capabilitiesDir}`);
580
- if (!fs.existsSync(this.capabilitiesDir)) {
646
+ if (!fs2.existsSync(this.capabilitiesDir)) {
581
647
  this.logger.warn(`Capabilities directory not found: ${this.capabilitiesDir}`);
582
648
  return;
583
649
  }
584
- const files = fs.readdirSync(this.capabilitiesDir).filter((f) => f.endsWith(".json"));
650
+ const files = fs2.readdirSync(this.capabilitiesDir).filter((f) => f.endsWith(".json"));
585
651
  for (const file of files) {
586
652
  try {
587
- const filePath = path.join(this.capabilitiesDir, file);
653
+ const filePath = path2.join(this.capabilitiesDir, file);
588
654
  this.loadCapabilityFromFile(filePath);
589
655
  } catch (error) {
590
656
  this.logger.error(`Failed to load capability from ${file}:`, error);
@@ -629,10 +695,30 @@ var CapabilityService = class _CapabilityService {
629
695
  };
630
696
  }
631
697
  /**
698
+ * 加载插件实例并解析参数
699
+ * 根据 manifest 的 form.refType 决定 formValue 的消费方式
700
+ */
701
+ async loadPluginAndResolveParams(config, input) {
702
+ const isConfigRequired = this.pluginLoaderService.isConfigRequired(config.pluginKey);
703
+ if (isConfigRequired) {
704
+ const pluginInstance2 = await this.pluginLoaderService.loadPlugin(config.pluginKey, config.formValue);
705
+ return {
706
+ pluginInstance: pluginInstance2,
707
+ resolvedParams: input
708
+ };
709
+ }
710
+ const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
711
+ const resolvedParams = config.formValue ? this.templateEngineService.resolve(config.formValue, input, config.paramsSchema) : input;
712
+ return {
713
+ pluginInstance,
714
+ resolvedParams
715
+ };
716
+ }
717
+ /**
632
718
  * 检查 action 是否为流式
633
719
  */
634
720
  async checkIsStream(config, actionName) {
635
- const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
721
+ const { pluginInstance } = await this.loadPluginAndResolveParams(config, {});
636
722
  if (!pluginInstance.hasAction(actionName)) {
637
723
  throw new ActionNotFoundError(config.pluginKey, actionName);
638
724
  }
@@ -651,11 +737,10 @@ var CapabilityService = class _CapabilityService {
651
737
  action: actionName
652
738
  };
653
739
  try {
654
- const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
740
+ const { pluginInstance, resolvedParams } = await this.loadPluginAndResolveParams(config, input);
655
741
  if (!pluginInstance.hasAction(actionName)) {
656
742
  throw new ActionNotFoundError(config.pluginKey, actionName);
657
743
  }
658
- const resolvedParams = config.formValue ? this.templateEngineService.resolve(config.formValue, input, config.paramsSchema) : input;
659
744
  const context = this.buildActionContext(contextOverride);
660
745
  const isStream = pluginInstance.isStreamAction?.(actionName) ?? false;
661
746
  this.logger.log("Executing capability (call)", {
@@ -702,11 +787,10 @@ var CapabilityService = class _CapabilityService {
702
787
  };
703
788
  const chunks = [];
704
789
  try {
705
- const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
790
+ const { pluginInstance, resolvedParams } = await this.loadPluginAndResolveParams(config, input);
706
791
  if (!pluginInstance.hasAction(actionName)) {
707
792
  throw new ActionNotFoundError(config.pluginKey, actionName);
708
793
  }
709
- const resolvedParams = config.formValue ? this.templateEngineService.resolve(config.formValue, input, config.paramsSchema) : input;
710
794
  const context = this.buildActionContext(contextOverride);
711
795
  const isStream = pluginInstance.isStreamAction?.(actionName) ?? false;
712
796
  this.logger.log("Executing capability (stream)", {
@@ -753,11 +837,10 @@ var CapabilityService = class _CapabilityService {
753
837
  };
754
838
  const chunks = [];
755
839
  try {
756
- const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
840
+ const { pluginInstance, resolvedParams } = await this.loadPluginAndResolveParams(config, input);
757
841
  if (!pluginInstance.hasAction(actionName)) {
758
842
  throw new ActionNotFoundError(config.pluginKey, actionName);
759
843
  }
760
- const resolvedParams = config.formValue ? this.templateEngineService.resolve(config.formValue, input, config.paramsSchema) : input;
761
844
  const context = this.buildActionContext(contextOverride);
762
845
  const isStream = pluginInstance.isStreamAction?.(actionName) ?? false;
763
846
  this.logger.log("Executing capability (streamWithEvents)", {
@@ -933,11 +1016,12 @@ var DebugController = class _DebugController {
933
1016
  * 获取 action 名称
934
1017
  * 优先使用传入的 action,否则使用插件第一个 action
935
1018
  */
936
- async getActionName(pluginKey, bodyAction) {
1019
+ async getActionName(pluginKey, formValue, bodyAction) {
937
1020
  if (bodyAction) {
938
1021
  return bodyAction;
939
1022
  }
940
- const pluginInstance = await this.pluginLoaderService.loadPlugin(pluginKey);
1023
+ const isConfigRequired = this.pluginLoaderService.isConfigRequired(pluginKey);
1024
+ const pluginInstance = isConfigRequired ? await this.pluginLoaderService.loadPlugin(pluginKey, formValue) : await this.pluginLoaderService.loadPlugin(pluginKey);
941
1025
  const actions = pluginInstance.listActions();
942
1026
  if (actions.length === 0) {
943
1027
  throw new Error(`Plugin ${pluginKey} has no actions`);
@@ -949,7 +1033,7 @@ var DebugController = class _DebugController {
949
1033
  const params = body.params ?? {};
950
1034
  try {
951
1035
  const config = this.getCapabilityConfig(capabilityId, body.capability);
952
- const action = await this.getActionName(config.pluginKey, body.action);
1036
+ const action = await this.getActionName(config.pluginKey, config.formValue, body.action);
953
1037
  const resolvedParams = this.templateEngineService.resolve(config.formValue, params, config.paramsSchema);
954
1038
  const result = await this.capabilityService.loadWithConfig(config).call(action, params, {
955
1039
  isDebug: true
@@ -1006,7 +1090,7 @@ var DebugController = class _DebugController {
1006
1090
  res.setHeader("Connection", "keep-alive");
1007
1091
  try {
1008
1092
  const config = this.getCapabilityConfig(capabilityId, body.capability);
1009
- const action = await this.getActionName(config.pluginKey, body.action);
1093
+ const action = await this.getActionName(config.pluginKey, config.formValue, body.action);
1010
1094
  const loggerContext = {
1011
1095
  capability_id: config.id,
1012
1096
  plugin_key: config.pluginKey,