@nocobase/server 2.1.0-alpha.4 → 2.1.0-alpha.45

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.
Files changed (63) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/lib/acl/available-action.js +1 -1
  4. package/lib/aes-encryptor.js +3 -2
  5. package/lib/app-supervisor/app-options-factory.d.ts +1 -0
  6. package/lib/app-supervisor/index.d.ts +13 -5
  7. package/lib/app-supervisor/index.js +48 -13
  8. package/lib/app-supervisor/main-only-adapter.d.ts +4 -1
  9. package/lib/app-supervisor/main-only-adapter.js +24 -12
  10. package/lib/app-supervisor/types.d.ts +18 -3
  11. package/lib/application.d.ts +1 -2
  12. package/lib/application.js +3 -24
  13. package/lib/audit-manager/index.d.ts +2 -0
  14. package/lib/audit-manager/index.js +5 -2
  15. package/lib/commands/ai.js +1 -6
  16. package/lib/commands/create-migration.js +1 -1
  17. package/lib/commands/install.js +0 -2
  18. package/lib/commands/pm.js +7 -0
  19. package/lib/commands/start.js +2 -5
  20. package/lib/commands/upgrade.js +0 -2
  21. package/lib/{ai/create-docs-index.d.ts → constants.d.ts} +1 -5
  22. package/lib/constants.js +36 -0
  23. package/lib/event-queue.js +1 -1
  24. package/lib/gateway/index.d.ts +15 -3
  25. package/lib/gateway/index.js +161 -20
  26. package/lib/gateway/static-file-security.d.ts +10 -0
  27. package/lib/gateway/static-file-security.js +69 -0
  28. package/lib/gateway/utils.d.ts +17 -0
  29. package/lib/gateway/utils.js +115 -0
  30. package/lib/helper.js +33 -1
  31. package/lib/index.d.ts +3 -1
  32. package/lib/index.js +5 -4
  33. package/lib/locale/locale.d.ts +24 -0
  34. package/lib/locale/locale.js +29 -5
  35. package/lib/main-data-source.js +12 -5
  36. package/lib/plugin-manager/deps.js +3 -2
  37. package/lib/plugin-manager/findPackageNames.js +4 -2
  38. package/lib/plugin-manager/options/resource.d.ts +12 -1
  39. package/lib/plugin-manager/options/resource.js +212 -53
  40. package/lib/plugin-manager/plugin-manager.d.ts +7 -2
  41. package/lib/plugin-manager/plugin-manager.js +65 -56
  42. package/lib/plugin-manager/utils.d.ts +9 -1
  43. package/lib/plugin-manager/utils.js +68 -10
  44. package/lib/plugin.js +46 -2
  45. package/lib/pub-sub-manager/handler-manager.d.ts +1 -0
  46. package/lib/pub-sub-manager/handler-manager.js +11 -0
  47. package/lib/pub-sub-manager/pub-sub-manager.js +2 -1
  48. package/lib/swagger/app.d.ts +102 -0
  49. package/lib/swagger/app.js +124 -0
  50. package/lib/swagger/base.d.ts +244 -0
  51. package/lib/swagger/base.js +292 -0
  52. package/lib/swagger/collections.d.ts +996 -0
  53. package/lib/swagger/collections.js +1264 -0
  54. package/lib/swagger/index.d.ts +1774 -0
  55. package/lib/swagger/index.js +70 -0
  56. package/lib/swagger/pm.d.ts +462 -0
  57. package/lib/swagger/pm.js +422 -0
  58. package/lib/sync-message-manager.js +8 -1
  59. package/lib/worker-mode.d.ts +19 -0
  60. package/lib/worker-mode.js +67 -0
  61. package/package.json +19 -19
  62. package/lib/ai/create-docs-index.js +0 -892
  63. package/lib/swagger/index.json +0 -1569
package/README.md CHANGED
@@ -1,17 +1,24 @@
1
1
  # NocoBase
2
2
 
3
3
  <video width="100%" controls>
4
- <source src="https://static-docs.nocobase.com/NocoBase0510.mp4" type="video/mp4">
4
+ <source src="https://github.com/user-attachments/assets/4d11a87b-00e2-48f3-9bf7-389d21072d13" type="video/mp4">
5
5
  </video>
6
6
 
7
+ <p align="center">
8
+ <a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
9
+ <a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability&#0045;first&#0044;&#0032;open&#0045;source&#0032;no&#0045;code&#0032;platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
10
+ </p>
7
11
 
8
12
  ## What is NocoBase
9
13
 
10
- NocoBase is a scalability-first, open-source no-code development platform.
11
- Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform!
14
+ NocoBase is the most extensible AI-powered no-code platform.
15
+ Total control. Infinite extensibility. AI collaboration.
16
+ Enable your team to adapt quickly and cut costs dramatically.
17
+ No years of development. No millions wasted.
18
+ Deploy NocoBase in minutes — and take control of everything.
12
19
 
13
20
  Homepage:
14
- https://www.nocobase.com/
21
+ https://www.nocobase.com/
15
22
 
16
23
  Online Demo:
17
24
  https://demo.nocobase.com/new
@@ -19,12 +26,74 @@ https://demo.nocobase.com/new
19
26
  Documents:
20
27
  https://docs.nocobase.com/
21
28
 
22
- Commericial license & plugins:
23
- https://www.nocobase.com/en/commercial
29
+ Forum:
30
+ https://forum.nocobase.com/
24
31
 
25
- License agreement:
26
- https://www.nocobase.com/en/agreement
32
+ Use Cases:
33
+ https://www.nocobase.com/en/blog/tags/customer-stories
27
34
 
35
+ ## Release Notes
28
36
 
29
- ## Contact Us:
30
- hello@nocobase.com
37
+ Our [blog](https://www.nocobase.com/en/blog/timeline) is regularly updated with release notes and provides a weekly summary.
38
+
39
+ ## Distinctive features
40
+
41
+ ### 1. Data model-driven, not form/table–driven
42
+
43
+ Instead of being constrained by forms or tables, NocoBase adopts a data model–driven approach, separating data structure from user interface to unlock unlimited possibilities.
44
+
45
+ - UI and data structure are fully decoupled
46
+ - Multiple blocks and actions can be created for the same table or record in any quantity or form
47
+ - Supports the main database, external databases, and third-party APIs as data sources
48
+
49
+ ![model](https://static-docs.nocobase.com/model.png)
50
+
51
+ ### 2. AI employees, integrated into your business systems
52
+ Unlike standalone AI demos, NocoBase allows you to embed AI capabilities seamlessly into your interfaces, workflows, and data context, making AI truly useful in real business scenarios.
53
+
54
+ - Define AI employees for roles such as translator, analyst, researcher, or assistant
55
+ - Seamless AI–human collaboration in interfaces and workflows
56
+ - Ensure AI usage is secure, transparent, and customizable for your business needs
57
+
58
+ ![AI-employee](https://static-docs.nocobase.com/ai-employee-home.png)
59
+
60
+ ### 3. What you see is what you get, incredibly easy to use
61
+
62
+ While enabling the development of complex business systems, NocoBase keeps the experience simple and intuitive.
63
+
64
+ - One-click switch between usage mode and configuration mode
65
+ - Pages serve as a canvas to arrange blocks and actions, similar to Notion
66
+ - Configuration mode is designed for ordinary users, not just programmers
67
+
68
+ ![wysiwyg](https://static-docs.nocobase.com/wysiwyg.gif)
69
+
70
+ ### 4. Everything is a plugin, designed for extension
71
+ Adding more no-code features will never cover every business case. NocoBase is built for extension through its plugin-based microkernel architecture.
72
+
73
+ - All functionalities are plugins, similar to WordPress
74
+ - Plugins are ready to use upon installation
75
+ - Pages, blocks, actions, APIs, and data sources can all be extended through custom plugins
76
+
77
+ ![plugins](https://static-docs.nocobase.com/plugins.png)
78
+
79
+ ## Installation
80
+
81
+ NocoBase supports three installation methods:
82
+
83
+ - <a target="_blank" href="https://docs.nocobase.com/welcome/getting-started/installation/docker-compose">Installing With Docker (👍Recommended)</a>
84
+
85
+ Suitable for no-code scenarios, no code to write. When upgrading, just download the latest image and reboot.
86
+
87
+ - <a target="_blank" href="https://docs.nocobase.com/welcome/getting-started/installation/create-nocobase-app">Installing from create-nocobase-app CLI</a>
88
+
89
+ The business code of the project is completely independent and supports low-code development.
90
+
91
+ - <a target="_blank" href="https://docs.nocobase.com/welcome/getting-started/installation/git-clone">Installing from Git source code</a>
92
+
93
+ If you want to experience the latest unreleased version, or want to participate in the contribution, you need to make changes and debug on the source code, it is recommended to choose this installation method, which requires a high level of development skills, and if the code has been updated, you can git pull the latest code.
94
+
95
+ ## How NocoBase works
96
+
97
+ <video width="100%" controls>
98
+ <source src="https://github.com/user-attachments/assets/8d183b44-9bb5-4792-b08f-bc08fe8dfaaf" type="video/mp4">
99
+ </video>
@@ -50,7 +50,7 @@ const availableActions = {
50
50
  view: {
51
51
  displayName: '{{t("View")}}',
52
52
  type: "old-data",
53
- aliases: ["get", "list"],
53
+ aliases: ["get", "list", "query"],
54
54
  allowConfigureFields: true
55
55
  },
56
56
  update: {
@@ -44,6 +44,7 @@ module.exports = __toCommonJS(aes_encryptor_exports);
44
44
  var import_crypto = __toESM(require("crypto"));
45
45
  var import_fs_extra = __toESM(require("fs-extra"));
46
46
  var import_path = __toESM(require("path"));
47
+ var import_utils = require("@nocobase/utils");
47
48
  const _AesEncryptor = class _AesEncryptor {
48
49
  key;
49
50
  constructor(key) {
@@ -99,12 +100,12 @@ const _AesEncryptor = class _AesEncryptor {
99
100
  }
100
101
  }
101
102
  static async getKeyPath(appName) {
102
- const appKeyPath = import_path.default.resolve(process.cwd(), "storage", "apps", appName, "aes_key.dat");
103
+ const appKeyPath = (0, import_utils.storagePathJoin)("apps", appName, "aes_key.dat");
103
104
  const appKeyExists = await import_fs_extra.default.exists(appKeyPath);
104
105
  if (appKeyExists) {
105
106
  return appKeyPath;
106
107
  }
107
- const envKeyPath = import_path.default.resolve(process.cwd(), "storage", "environment-variables", appName, "aes_key.dat");
108
+ const envKeyPath = (0, import_utils.storagePathJoin)("environment-variables", appName, "aes_key.dat");
108
109
  const envKeyExists = await import_fs_extra.default.exists(envKeyPath);
109
110
  if (envKeyExists) {
110
111
  return envKeyPath;
@@ -16,6 +16,7 @@ export declare const appOptionsFactory: (appName: string, mainApp: Application,
16
16
  migrator?: any;
17
17
  usingBigIntForId?: boolean;
18
18
  underscored?: boolean;
19
+ rawTimezone?: string;
19
20
  logger?: import("@nocobase/logger/lib/logger").Logger | import("@nocobase/logger/lib/logger").LoggerOptions;
20
21
  customHooks?: any;
21
22
  instanceId?: string;
@@ -13,7 +13,7 @@ import { IncomingMessage, ServerResponse } from 'http';
13
13
  import { AsyncEmitter } from '@nocobase/utils';
14
14
  import { EventEmitter } from 'events';
15
15
  import Application, { ApplicationOptions } from '../application';
16
- import type { AppDiscoveryAdapter, AppProcessAdapter, AppStatus, EnvironmentInfo, GetAppOptions, ProcessCommand, AppDbCreator, AppOptionsFactory, AppDbCreatorOptions, AppCommandAdapter, AppModel, BootstrapLock } from './types';
16
+ import type { AppDiscoveryAdapter, AppProcessAdapter, AppStatus, EnvironmentInfo, GetAppOptions, ProcessCommand, AppDbCreator, AppOptionsFactory, AppDbCreatorOptions, AppCommandAdapter, AppModel, BootstrapLock, AppCondition, GetAppsByConditionOptions } from './types';
17
17
  import { Predicate } from './condition-registry';
18
18
  import { PubSubManagerPublishOptions } from '../pub-sub-manager';
19
19
  import { Transaction, Transactionable } from '@nocobase/database';
@@ -48,6 +48,7 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
48
48
  private processAdapterName;
49
49
  private commandAdapterName;
50
50
  private appDbCreator;
51
+ private appConditions;
51
52
  appOptionsFactory: AppOptionsFactory;
52
53
  private environmentHeartbeatInterval;
53
54
  private environmentHeartbeatTimer;
@@ -107,11 +108,15 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
107
108
  getAppLastSeenAt(appName: string): Promise<number>;
108
109
  addAppModel(appModel: AppModel): Promise<void>;
109
110
  getAppModel(appName: string): Promise<AppModel>;
111
+ registerAppCondition(name: string, condition: AppCondition): void;
112
+ unregisterAppCondition(name: string): void;
113
+ getAppCondition(name: string): AppCondition;
114
+ getAppConditions(): [string, AppCondition][];
115
+ getAppsByCondition(conditionName: string, options?: GetAppsByConditionOptions): Promise<string[]>;
110
116
  removeAppModel(appName: string): Promise<void>;
111
117
  getAppNameByCName(cname: string): Promise<string>;
112
- addAutoStartApps(environmentName: string, appNames: string[]): Promise<void>;
113
- getAutoStartApps(): Promise<string[]>;
114
- removeAutoStartApps(environmentName: string, appNames: string[]): Promise<void>;
118
+ addAppsToCondition(conditionName: string, environmentName: string, appNames: string[]): Promise<void>;
119
+ removeAppsFromCondition(conditionName: string, environmentName: string, appNames: string[]): Promise<void>;
115
120
  addApp(app: Application): Application<import("../application").DefaultState, import("../application").DefaultContext>;
116
121
  getApp(appName: string, options?: GetAppOptions): Promise<Application<import("../application").DefaultState, import("../application").DefaultContext>>;
117
122
  hasApp(appName: string): boolean;
@@ -134,6 +139,9 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
134
139
  upgradeApp(appName: string, context?: {
135
140
  requestId: string;
136
141
  }): Promise<void>;
142
+ dispatchAppEvent(appName: string, event: string, payload?: any, context?: {
143
+ requestId: string;
144
+ }): Promise<void>;
137
145
  /**
138
146
  * @deprecated
139
147
  * use {#getApps} instead
@@ -157,5 +165,5 @@ export declare class AppSupervisor extends EventEmitter implements AsyncEmitter
157
165
  on(eventName: string | symbol, listener: (...args: any[]) => void): this;
158
166
  private bindAppEvents;
159
167
  }
160
- export type { AppDiscoveryAdapter, AppProcessAdapter, AppCommandAdapter, AppStatus, ProcessCommand, EnvironmentInfo, GetAppOptions, AppDbCreator, AppOptionsFactory, AppModel, AppModelOptions, BootstrapLock, } from './types';
168
+ export type { AppDiscoveryAdapter, AppProcessAdapter, AppCommandAdapter, AppStatus, ProcessCommand, EnvironmentInfo, GetAppOptions, AppDbCreator, AppOptionsFactory, AppModel, AppModelOptions, AppCondition, GetAppsByConditionOptions, BootstrapLock, } from './types';
161
169
  export { MainOnlyAdapter } from './main-only-adapter';
@@ -47,6 +47,7 @@ var import_lodash = __toESM(require("lodash"));
47
47
  var import_utils = require("@nocobase/utils");
48
48
  var import_events = require("events");
49
49
  var import_application = __toESM(require("../application"));
50
+ var import_worker_mode = require("../worker-mode");
50
51
  var import_main_only_adapter = require("./main-only-adapter");
51
52
  var import_handler = require("../errors/handler");
52
53
  var import_condition_registry = require("./condition-registry");
@@ -68,6 +69,7 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
68
69
  processAdapterName;
69
70
  commandAdapterName;
70
71
  appDbCreator = new import_condition_registry.ConditionalRegistry();
72
+ appConditions = /* @__PURE__ */ new Map();
71
73
  appOptionsFactory = import_app_options_factory.appOptionsFactory;
72
74
  environmentHeartbeatInterval = 2 * 60 * 1e3;
73
75
  environmentHeartbeatTimer = null;
@@ -288,6 +290,10 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
288
290
  return this.discoveryAdapter.getAppStatus(appName, defaultStatus);
289
291
  }
290
292
  async setAppStatus(appName, status, options = {}) {
293
+ if ((0, import_worker_mode.isTransient)()) {
294
+ this.logger.debug("App running as worker, status will not be set", { appName, status });
295
+ return;
296
+ }
291
297
  this.logger.debug("Setting app status", { appName, status });
292
298
  return this.discoveryAdapter.setAppStatus(appName, status, options);
293
299
  }
@@ -329,7 +335,7 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
329
335
  };
330
336
  }
331
337
  const app = new import_application.default(options);
332
- if (hook !== false) {
338
+ if (hook ?? !(0, import_worker_mode.isTransient)()) {
333
339
  app.on("afterStart", async () => {
334
340
  await this.sendSyncMessage(mainApp, {
335
341
  type: "app:started",
@@ -356,6 +362,9 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
356
362
  this.registerCommandHandler(app);
357
363
  app.on("afterStart", async (app2) => {
358
364
  var _a, _b;
365
+ if ((0, import_worker_mode.isTransient)()) {
366
+ return;
367
+ }
359
368
  await app2.syncMessageManager.subscribe(
360
369
  "app_supervisor:sync",
361
370
  async (message) => {
@@ -390,6 +399,9 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
390
399
  }
391
400
  });
392
401
  app.on("afterDestroy", async (app2) => {
402
+ if ((0, import_worker_mode.isTransient)()) {
403
+ return;
404
+ }
393
405
  await this.unregisterEnvironment();
394
406
  });
395
407
  return app;
@@ -409,6 +421,28 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
409
421
  async getAppModel(appName) {
410
422
  return this.discoveryAdapter.getAppModel(appName);
411
423
  }
424
+ registerAppCondition(name, condition) {
425
+ this.appConditions.set(name, condition);
426
+ }
427
+ unregisterAppCondition(name) {
428
+ this.appConditions.delete(name);
429
+ }
430
+ getAppCondition(name) {
431
+ return this.appConditions.get(name);
432
+ }
433
+ getAppConditions() {
434
+ return Array.from(this.appConditions.entries());
435
+ }
436
+ async getAppsByCondition(conditionName, options = {}) {
437
+ const condition = this.getAppCondition(conditionName);
438
+ if (!condition || typeof this.discoveryAdapter.getAppsByCondition !== "function") {
439
+ return [];
440
+ }
441
+ return this.discoveryAdapter.getAppsByCondition(conditionName, condition, {
442
+ ...options,
443
+ environmentName: options.allEnvironments ? options.environmentName : options.environmentName ?? this.environmentName
444
+ });
445
+ }
412
446
  async removeAppModel(appName) {
413
447
  if (typeof this.discoveryAdapter.removeAppModel !== "function") {
414
448
  return;
@@ -421,23 +455,17 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
421
455
  }
422
456
  return this.discoveryAdapter.getAppNameByCName(cname);
423
457
  }
424
- async addAutoStartApps(environmentName, appNames) {
425
- if (typeof this.discoveryAdapter.addAutoStartApps !== "function") {
458
+ async addAppsToCondition(conditionName, environmentName, appNames) {
459
+ if (typeof this.discoveryAdapter.addAppsToCondition !== "function") {
426
460
  return;
427
461
  }
428
- return this.discoveryAdapter.addAutoStartApps(environmentName, appNames);
429
- }
430
- async getAutoStartApps() {
431
- if (typeof this.discoveryAdapter.getAutoStartApps === "function") {
432
- return this.discoveryAdapter.getAutoStartApps(this.environmentName);
433
- }
434
- return [];
462
+ return this.discoveryAdapter.addAppsToCondition(conditionName, environmentName, appNames);
435
463
  }
436
- async removeAutoStartApps(environmentName, appNames) {
437
- if (typeof this.discoveryAdapter.addAutoStartApps !== "function") {
464
+ async removeAppsFromCondition(conditionName, environmentName, appNames) {
465
+ if (typeof this.discoveryAdapter.removeAppsFromCondition !== "function") {
438
466
  return;
439
467
  }
440
- return this.discoveryAdapter.removeAutoStartApps(environmentName, appNames);
468
+ return this.discoveryAdapter.removeAppsFromCondition(conditionName, environmentName, appNames);
441
469
  }
442
470
  addApp(app) {
443
471
  this.processAdapter.addApp(app);
@@ -466,6 +494,10 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
466
494
  async upgradeApp(appName, context) {
467
495
  await this.processAdapter.upgradeApp(appName, context);
468
496
  }
497
+ async dispatchAppEvent(appName, event, payload, context) {
498
+ var _a, _b;
499
+ return (_b = (_a = this.processAdapter).dispatchAppEvent) == null ? void 0 : _b.call(_a, appName, event, payload, context);
500
+ }
469
501
  /**
470
502
  * @deprecated
471
503
  * use {#getApps} instead
@@ -586,6 +618,9 @@ const _AppSupervisor = class _AppSupervisor extends import_events.EventEmitter {
586
618
  return super.on(eventName, listener);
587
619
  }
588
620
  bindAppEvents(app) {
621
+ if ((0, import_worker_mode.isTransient)()) {
622
+ return;
623
+ }
589
624
  app.on("afterDestroy", async () => {
590
625
  delete this.apps[app.name];
591
626
  delete this.appStatus[app.name];
@@ -13,7 +13,7 @@ import type { AppSupervisor } from './index';
13
13
  export declare class MainOnlyAdapter implements AppDiscoveryAdapter, AppProcessAdapter {
14
14
  protected readonly supervisor: AppSupervisor;
15
15
  readonly name: string;
16
- app: Application;
16
+ apps: Record<string, Application>;
17
17
  status: AppStatus;
18
18
  appErrors: Record<string, Error>;
19
19
  constructor(supervisor: AppSupervisor);
@@ -26,6 +26,9 @@ export declare class MainOnlyAdapter implements AppDiscoveryAdapter, AppProcessA
26
26
  stopApp(appName: string): Promise<void>;
27
27
  removeApp(appName: string): Promise<void>;
28
28
  upgradeApp(appName: string): Promise<void>;
29
+ dispatchAppEvent(appName: string, event: string, payload?: any, _context?: {
30
+ requestId: string;
31
+ }): Promise<void>;
29
32
  removeAllApps(): Promise<void>;
30
33
  setAppStatus(appName: string, status: AppStatus, options?: {}): void;
31
34
  getAppStatus(appName: string, defaultStatus?: AppStatus): AppStatus;
@@ -36,7 +36,7 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
36
36
  this.name = "main-only";
37
37
  }
38
38
  name;
39
- app;
39
+ apps = {};
40
40
  status;
41
41
  appErrors = {};
42
42
  async getApp(appName, options = {}) {
@@ -47,10 +47,10 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
47
47
  if (!options.withOutBootStrap) {
48
48
  await this.bootstrapApp(appName);
49
49
  }
50
- return this.app;
50
+ return this.apps[appName];
51
51
  }
52
52
  async bootstrapApp(appName) {
53
- if (appName !== "main" || !this.app) {
53
+ if (appName !== "main" || !this.apps[appName]) {
54
54
  this.setAppStatus(appName, "not_found");
55
55
  return;
56
56
  }
@@ -66,23 +66,23 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
66
66
  this.supervisor.logger.warn(`only main app is supported`, { method: "addApp" });
67
67
  return;
68
68
  }
69
- if (this.app) {
69
+ if (this.apps[app.name]) {
70
70
  throw new Error(`app ${app.name} already exists`);
71
71
  }
72
- this.app = app;
72
+ this.apps[app.name] = app;
73
73
  if (!this.status || this.status === "not_found") {
74
74
  this.setAppStatus(app.name, "preparing");
75
75
  }
76
76
  return app;
77
77
  }
78
78
  getApps() {
79
- return [this.app];
79
+ return Object.values(this.apps);
80
80
  }
81
81
  hasApp(appName) {
82
82
  if (appName !== "main") {
83
83
  return false;
84
84
  }
85
- return !!this.app;
85
+ return !!this.apps[appName];
86
86
  }
87
87
  async startApp(appName) {
88
88
  if (appName !== "main") {
@@ -93,31 +93,43 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
93
93
  await (app == null ? void 0 : app.runCommand("start", "--quickstart"));
94
94
  }
95
95
  async stopApp(appName) {
96
+ var _a;
96
97
  if (appName !== "main") {
97
98
  this.supervisor.logger.warn(`only main app is supported`, { method: "stopApp" });
98
99
  return;
99
100
  }
100
- await this.app.runCommand("stop");
101
+ if (!this.apps[appName]) {
102
+ return;
103
+ }
104
+ await ((_a = this.apps[appName]) == null ? void 0 : _a.runCommand("stop"));
101
105
  }
102
106
  async removeApp(appName) {
103
107
  if (appName !== "main") {
104
108
  this.supervisor.logger.warn(`only main app is supported`, { method: "removeApp" });
105
109
  return;
106
110
  }
107
- if (!this.app) {
111
+ if (!this.apps[appName]) {
108
112
  return;
109
113
  }
110
- await this.app.runCommand("destroy");
114
+ await this.apps[appName].runCommand("destroy");
115
+ this.apps[appName] = null;
111
116
  }
112
117
  async upgradeApp(appName) {
113
118
  if (appName !== "main") {
114
119
  this.supervisor.logger.warn(`only main app is supported`, { method: "upgrade" });
115
120
  return;
116
121
  }
117
- if (!this.app) {
122
+ if (!this.apps[appName]) {
123
+ return;
124
+ }
125
+ await this.apps[appName].runCommand("upgrade");
126
+ }
127
+ async dispatchAppEvent(appName, event, payload, _context) {
128
+ const app = await this.getApp(appName, { withOutBootStrap: true });
129
+ if (!app) {
118
130
  return;
119
131
  }
120
- await this.app.runCommand("upgrade");
132
+ await app.emitAsync(event, payload);
121
133
  }
122
134
  async removeAllApps() {
123
135
  return this.removeApp("main");
@@ -57,11 +57,23 @@ export type AppModelOptions = {
57
57
  };
58
58
  export type AppModel = {
59
59
  name: string;
60
+ title?: string;
61
+ icon?: string;
60
62
  cname?: string;
63
+ sort?: number;
64
+ pinned?: boolean;
61
65
  environment?: string;
62
66
  environments?: string[];
63
67
  options: AppModelOptions;
64
68
  };
69
+ export type AppCondition = {
70
+ filter?: Record<string, any>;
71
+ match?: (appModel: AppModel) => boolean;
72
+ };
73
+ export type GetAppsByConditionOptions = {
74
+ environmentName?: string;
75
+ allEnvironments?: boolean;
76
+ };
65
77
  export type ProcessCommand = {
66
78
  requestId: string;
67
79
  appName: string;
@@ -102,9 +114,9 @@ export interface AppDiscoveryAdapter {
102
114
  clearAppStatus?(appName: string): void | Promise<void>;
103
115
  loadAppModels?(mainApp: Application): Promise<void>;
104
116
  getAppsStatuses?(appNames?: string[]): Promise<AppStatusesResult> | AppStatusesResult;
105
- addAutoStartApps?(environmentName: string, appName: string[]): Promise<void>;
106
- getAutoStartApps?(environmentName: string): Promise<string[]>;
107
- removeAutoStartApps?(environmentName: string, appNames: string[]): Promise<void>;
117
+ getAppsByCondition?(conditionName: string, condition: AppCondition, options?: GetAppsByConditionOptions): Promise<string[]>;
118
+ addAppsToCondition?(conditionName: string, environmentName: string, appNames: string[]): Promise<void>;
119
+ removeAppsFromCondition?(conditionName: string, environmentName: string, appNames: string[]): Promise<void>;
108
120
  addAppModel?(appModel: AppModel): Promise<void>;
109
121
  getAppModel?(appName: string): Promise<AppModel>;
110
122
  removeAppModel?(appName: string): Promise<void>;
@@ -149,6 +161,9 @@ export interface AppProcessAdapter {
149
161
  upgradeApp?(appName: string, context?: {
150
162
  requestId: string;
151
163
  }): Promise<void>;
164
+ dispatchAppEvent?(appName: string, event: string, payload?: any, context?: {
165
+ requestId: string;
166
+ }): Promise<void>;
152
167
  removeAllApps?(): Promise<void>;
153
168
  setAppError?(appName: string, error: Error): void;
154
169
  hasAppError?(appName: string): boolean;
@@ -270,8 +270,7 @@ export declare class Application<StateT = DefaultState, ContextT = DefaultContex
270
270
  protected _aesEncryptor: AesEncryptor;
271
271
  get aesEncryptor(): AesEncryptor;
272
272
  /**
273
- * Check if the application is serving as a specific worker.
274
- * @experimental
273
+ * @deprecated use {@link serving} from './worker-mode' instead.
275
274
  */
276
275
  serving(key?: string): boolean;
277
276
  /**
@@ -87,6 +87,7 @@ var import_redis_connection_manager = require("./redis-connection-manager");
87
87
  var import_service_container = require("./service-container");
88
88
  var import_snowflake_id_field = require("./snowflake-id-field");
89
89
  var import_worker_id_allocator = require("./worker-id-allocator");
90
+ var import_worker_mode = require("./worker-mode");
90
91
  const _Application = class _Application extends import_koa.default {
91
92
  constructor(options) {
92
93
  super();
@@ -282,32 +283,10 @@ const _Application = class _Application extends import_koa.default {
282
283
  return this._aesEncryptor;
283
284
  }
284
285
  /**
285
- * Check if the application is serving as a specific worker.
286
- * @experimental
286
+ * @deprecated use {@link serving} from './worker-mode' instead.
287
287
  */
288
288
  serving(key) {
289
- const { WORKER_MODE = "" } = process.env;
290
- if (!WORKER_MODE) {
291
- return true;
292
- }
293
- if (WORKER_MODE === "-") {
294
- return false;
295
- }
296
- const topics = WORKER_MODE.trim().split(",");
297
- if (key) {
298
- if (WORKER_MODE === "*") {
299
- return true;
300
- }
301
- if (topics.includes(key)) {
302
- return true;
303
- }
304
- return false;
305
- } else {
306
- if (topics.includes("!")) {
307
- return true;
308
- }
309
- return false;
310
- }
289
+ return (0, import_worker_mode.serving)(key);
311
290
  }
312
291
  /**
313
292
  * @internal
@@ -12,6 +12,7 @@ export interface AuditLog {
12
12
  dataSource: string;
13
13
  resource: string;
14
14
  action: string;
15
+ requestSource?: string;
15
16
  sourceCollection?: string;
16
17
  sourceRecordUK?: string;
17
18
  targetCollection?: string;
@@ -102,6 +103,7 @@ export declare class AuditManager {
102
103
  'x-authenticator': any;
103
104
  'x-locale': any;
104
105
  'x-timezone': any;
106
+ 'x-request-source': any;
105
107
  };
106
108
  };
107
109
  response: {
@@ -41,6 +41,7 @@ __export(audit_manager_exports, {
41
41
  });
42
42
  module.exports = __toCommonJS(audit_manager_exports);
43
43
  var import_stream = __toESM(require("stream"));
44
+ const REQUEST_SOURCE_HEADER = "x-request-source";
44
45
  function isStream(obj) {
45
46
  return obj instanceof import_stream.default.Readable || obj instanceof import_stream.default.Writable || obj instanceof import_stream.default.Duplex || obj instanceof import_stream.default.Transform;
46
47
  }
@@ -201,7 +202,7 @@ const _AuditManager = class _AuditManager {
201
202
  return null;
202
203
  }
203
204
  async getDefaultMetaData(ctx) {
204
- var _a, _b, _c;
205
+ var _a, _b, _c, _d;
205
206
  let body = null;
206
207
  if (ctx.body) {
207
208
  if (!Buffer.isBuffer(ctx.body) && !isStream(ctx.body)) {
@@ -217,7 +218,8 @@ const _AuditManager = class _AuditManager {
217
218
  headers: {
218
219
  "x-authenticator": (_a = ctx.request) == null ? void 0 : _a.headers["x-authenticator"],
219
220
  "x-locale": (_b = ctx.request) == null ? void 0 : _b.headers["x-locale"],
220
- "x-timezone": (_c = ctx.request) == null ? void 0 : _c.headers["x-timezone"]
221
+ "x-timezone": (_c = ctx.request) == null ? void 0 : _c.headers["x-timezone"],
222
+ "x-request-source": (_d = ctx.request) == null ? void 0 : _d.headers[REQUEST_SOURCE_HEADER]
221
223
  }
222
224
  },
223
225
  response: {
@@ -241,6 +243,7 @@ const _AuditManager = class _AuditManager {
241
243
  dataSource: ctx.request.header["x-data-source"] || "main",
242
244
  resource: resourceName,
243
245
  action: ctx.action.actionName,
246
+ requestSource: ctx.request.header[REQUEST_SOURCE_HEADER],
244
247
  userId: (_b = (_a = ctx.state) == null ? void 0 : _a.currentUser) == null ? void 0 : _b.id,
245
248
  roleName: (_c = ctx.state) == null ? void 0 : _c.currentRole,
246
249
  ip: ips.length > 0 ? ips[0] : ctx.request.ip,
@@ -30,11 +30,6 @@ __export(ai_exports, {
30
30
  default: () => ai_default
31
31
  });
32
32
  module.exports = __toCommonJS(ai_exports);
33
- var import_create_docs_index = require("../ai/create-docs-index");
34
33
  var ai_default = /* @__PURE__ */ __name((app) => {
35
- const ai = app.command("ai");
36
- ai.command("create-docs-index").option("--pkg [pkg]", "Generate docs index for the specified plugin package (comma separated).").action(async (...cliArgs) => {
37
- const [opts] = cliArgs;
38
- await (0, import_create_docs_index.createDocsIndex)(app, opts);
39
- });
34
+ app.command("ai");
40
35
  }, "default");
@@ -76,6 +76,6 @@ export default class extends Migration {
76
76
  `;
77
77
  await import_fs.default.promises.mkdir((0, import_path.dirname)(filename), { recursive: true });
78
78
  await import_fs.default.promises.writeFile(filename, data, "utf8");
79
- app.log.info(`migration file in ${filename}`);
79
+ console.log(`migration file in ${filename}`);
80
80
  });
81
81
  }, "default");
@@ -30,14 +30,12 @@ __export(install_exports, {
30
30
  default: () => install_default
31
31
  });
32
32
  module.exports = __toCommonJS(install_exports);
33
- var import_create_docs_index = require("../ai/create-docs-index");
34
33
  /* istanbul ignore file -- @preserve */
35
34
  var install_default = /* @__PURE__ */ __name((app) => {
36
35
  app.command("install").ipc().auth().option("-f, --force").option("-c, --clean").option("--lang <lang>").action(async (options) => {
37
36
  if (options.lang) {
38
37
  process.env.INIT_APP_LANG = options.lang;
39
38
  }
40
- await (0, import_create_docs_index.createDocsIndex)(app);
41
39
  await app.install(options);
42
40
  const reinstall = options.clean || options.force;
43
41
  app.log.info(`app ${reinstall ? "reinstalled" : "installed"} successfully [v${app.getVersion()}]`);