alemonjs 2.1.80 → 2.1.82

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/lib/app/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, MiddlewareTree, ProcessorEventAutoClearMap, ProcessorEventUserAutoClearMap, Response, ResponseMiddleware, ResponseRouter, ResponseTree, State, StateSubscribe, SubscribeList, bumpStoreVersion, core, getSubscribeList, logger } from './store.js';
1
+ export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, MiddlewareTree, ProcessorEventAutoClearMap, ProcessorEventUserAutoClearMap, Response, ResponseMiddleware, ResponseRouter, ResponseTree, State, StateSubscribe, SubscribeList, bumpStoreVersion, clearRuntimeAppKoaRouters, core, disposeAllRuntimeApps, disposeRuntimeApp, getRuntimeApp, getRuntimeAppKoaRouters, getSubscribeList, hasRuntimeAppCapability, listRuntimeAppKoaRouters, listRuntimeApps, logger, registerRuntimeApp, setRuntimeAppKoaRouters, toRuntimeAppSnapshot, updateRuntimeAppCapabilities, updateRuntimeAppStatus } from './store.js';
2
2
  export { Expose, clearAllExpose, disposeExpose, registerExpose } from './expose.js';
3
3
  export { loadModels, run } from './load_modules/load.js';
4
4
  export { loadChildren, loadChildrenFile } from './load_modules/loadChild.js';
@@ -1,21 +1,36 @@
1
1
  import { getConfig } from '../../core/config.js';
2
2
  import { loadChildren, loadChildrenFile } from './loadChild.js';
3
- import { join } from 'path';
3
+ import { join, dirname } from 'path';
4
4
  import { existsSync } from 'fs';
5
5
  import { ResultCode } from '../../core/variable.js';
6
+ import { registerRuntimeApp } from '../store.js';
6
7
 
7
8
  const loadApps = () => {
8
9
  const cfg = getConfig();
9
- const apps = Array.isArray(cfg.value?.apps) ? cfg.value.apps : Object.keys(cfg.value?.apps ?? {}).filter(Boolean);
10
+ const apps = Array.isArray(cfg.value?.apps)
11
+ ? cfg.value.apps.filter(Boolean)
12
+ : Object.entries(cfg.value?.apps ?? {})
13
+ .filter(([, enabled]) => Boolean(enabled))
14
+ .map(([name]) => name);
10
15
  const uniqueApps = Array.from(new Set(apps));
11
- uniqueApps.forEach(app => void loadChildrenFile(app));
16
+ uniqueApps.forEach(app => {
17
+ registerRuntimeApp({
18
+ name: app,
19
+ kind: 'plugin',
20
+ enabled: true,
21
+ status: 'discovered',
22
+ rootDir: '',
23
+ mainPath: ''
24
+ });
25
+ void loadChildrenFile(app);
26
+ });
12
27
  };
13
28
  const run = (input) => {
14
29
  if (!input) {
15
30
  return;
16
31
  }
17
32
  const mainPath = join(process.cwd(), input);
18
- if (!existsSync(input)) {
33
+ if (!existsSync(mainPath)) {
19
34
  logger.warn({
20
35
  code: ResultCode.Warn,
21
36
  message: '未找到主要入口文件',
@@ -23,6 +38,14 @@ const run = (input) => {
23
38
  });
24
39
  return;
25
40
  }
41
+ registerRuntimeApp({
42
+ name: 'main',
43
+ kind: 'main',
44
+ enabled: true,
45
+ status: 'discovered',
46
+ rootDir: join(process.cwd(), dirname(input)),
47
+ mainPath
48
+ });
26
49
  void loadChildren(mainPath, 'main');
27
50
  };
28
51
  function loadModels() {
@@ -1,7 +1,7 @@
1
1
  import { dirname, join } from 'path';
2
2
  import { existsSync } from 'fs';
3
3
  import { showErrorModule, getRecursiveDirFiles, createEventName } from '../../core/utils.js';
4
- import { ChildrenApp } from '../store.js';
4
+ import { registerRuntimeApp, updateRuntimeAppStatus, ChildrenApp, clearRuntimeAppKoaRouters, setRuntimeAppKoaRouters, updateRuntimeAppCapabilities } from '../store.js';
5
5
  import { registerExpose } from '../expose.js';
6
6
  import { ResultCode, fileSuffixMiddleware } from '../../core/variable.js';
7
7
  import { registerAppDir, scheduleCancelByApp, unregisterAppDir } from '../schedule-store.js';
@@ -10,6 +10,33 @@ import module$1 from 'module';
10
10
  const initRequire = () => { };
11
11
  initRequire.resolve = () => '';
12
12
  const require$1 = module$1?.createRequire?.(import.meta.url) ?? initRequire;
13
+ const resolvePackageRoot = (startDir) => {
14
+ let currentDir = startDir;
15
+ while (currentDir && currentDir !== dirname(currentDir)) {
16
+ if (existsSync(join(currentDir, 'package.json'))) {
17
+ return currentDir;
18
+ }
19
+ currentDir = dirname(currentDir);
20
+ }
21
+ return startDir;
22
+ };
23
+ const detectWebCapability = (startDir) => {
24
+ const packageRoot = resolvePackageRoot(startDir);
25
+ const packageJsonPath = join(packageRoot, 'package.json');
26
+ if (!existsSync(packageJsonPath)) {
27
+ return existsSync(join(packageRoot, 'index.html'));
28
+ }
29
+ try {
30
+ const pkg = require$1(packageJsonPath) ?? {};
31
+ const root = pkg?.alemonjs?.web?.root;
32
+ if (typeof root === 'string' && root.trim()) {
33
+ return existsSync(join(packageRoot, root));
34
+ }
35
+ }
36
+ catch {
37
+ }
38
+ return existsSync(join(packageRoot, 'index.html'));
39
+ };
13
40
  const loadChildren = async (mainPath, appName) => {
14
41
  if (!mainPath || typeof mainPath !== 'string') {
15
42
  logger.error({
@@ -21,6 +48,24 @@ const loadChildren = async (mainPath, appName) => {
21
48
  }
22
49
  const mainDir = dirname(mainPath);
23
50
  const App = new ChildrenApp(appName);
51
+ const kind = appName === 'main' ? 'main' : 'plugin';
52
+ const baseCapabilities = {
53
+ event: false,
54
+ httpApi: existsSync(join(mainDir, 'route', 'api')),
55
+ web: detectWebCapability(mainDir),
56
+ schedule: false,
57
+ expose: false
58
+ };
59
+ registerRuntimeApp({
60
+ name: appName,
61
+ kind,
62
+ enabled: true,
63
+ status: 'discovered',
64
+ rootDir: mainDir,
65
+ mainPath,
66
+ capabilities: baseCapabilities
67
+ });
68
+ updateRuntimeAppStatus(appName, 'loading');
24
69
  registerAppDir(appName, mainDir);
25
70
  try {
26
71
  const moduleApp = await import(`file://${mainPath}`);
@@ -46,6 +91,8 @@ const loadChildren = async (mainPath, appName) => {
46
91
  App.pushCycle(app);
47
92
  const unMounted = async (e) => {
48
93
  showErrorModule(e);
94
+ clearRuntimeAppKoaRouters(appName);
95
+ updateRuntimeAppStatus(appName, 'failed', e);
49
96
  scheduleCancelByApp(appName);
50
97
  unregisterAppDir(appName);
51
98
  App.un();
@@ -69,17 +116,28 @@ const loadChildren = async (mainPath, appName) => {
69
116
  }
70
117
  const registerMounted = async () => {
71
118
  const res = await app?.register();
119
+ const hasEventCapability = Boolean(res && (res?.response || res?.middleware || res?.responseRouter || res?.middlewareRouter));
120
+ const hasExposeCapability = Boolean(res?.expose);
121
+ const hasKoaRouterCapability = Boolean(res?.koaRouter);
72
122
  if (res && (res?.response || res?.middleware || res?.responseRouter || res?.middlewareRouter)) {
73
123
  App.register(res);
74
124
  }
125
+ setRuntimeAppKoaRouters(appName, res?.koaRouter);
75
126
  if (res?.expose) {
76
127
  registerExpose(appName, res.expose.getConfigs());
77
128
  }
129
+ updateRuntimeAppCapabilities(appName, {
130
+ ...baseCapabilities,
131
+ httpApi: baseCapabilities.httpApi || hasKoaRouterCapability,
132
+ event: hasEventCapability,
133
+ expose: hasExposeCapability
134
+ });
78
135
  App.on();
79
136
  try {
80
137
  if (app?.onMounted) {
81
138
  await app.onMounted({ response: [], responseMiddleware: {}, middleware: [] });
82
139
  }
140
+ updateRuntimeAppStatus(appName, 'ready');
83
141
  }
84
142
  catch (e) {
85
143
  void unMounted(e);
@@ -139,11 +197,16 @@ const loadChildren = async (mainPath, appName) => {
139
197
  mwData.push(middleware);
140
198
  }
141
199
  App.pushMiddleware(mwData);
200
+ updateRuntimeAppCapabilities(appName, {
201
+ ...baseCapabilities,
202
+ event: resData.length > 0 || Object.keys(resAndMwData).length > 0 || mwData.length > 0
203
+ });
142
204
  App.on();
143
205
  try {
144
206
  if (app?.onMounted) {
145
207
  await app.onMounted({ response: resData, responseMiddleware: resAndMwData, middleware: mwData });
146
208
  }
209
+ updateRuntimeAppStatus(appName, 'ready');
147
210
  }
148
211
  catch (e) {
149
212
  void unMounted(e);
@@ -163,6 +226,8 @@ const loadChildren = async (mainPath, appName) => {
163
226
  }
164
227
  catch (e) {
165
228
  showErrorModule(e);
229
+ clearRuntimeAppKoaRouters(appName);
230
+ updateRuntimeAppStatus(appName, 'failed', e);
166
231
  App.un();
167
232
  }
168
233
  };
@@ -178,6 +243,7 @@ const loadChildrenFile = (appName) => {
178
243
  try {
179
244
  const mainPath = require$1.resolve(appName);
180
245
  if (!existsSync(mainPath)) {
246
+ updateRuntimeAppStatus(appName, 'failed', new Error('The main file does not exist,' + mainPath));
181
247
  logger.error({
182
248
  code: ResultCode.FailParams,
183
249
  message: 'The main file does not exist,' + mainPath,
@@ -185,9 +251,18 @@ const loadChildrenFile = (appName) => {
185
251
  });
186
252
  return;
187
253
  }
254
+ registerRuntimeApp({
255
+ name: appName,
256
+ kind: 'plugin',
257
+ enabled: true,
258
+ status: 'discovered',
259
+ rootDir: dirname(mainPath),
260
+ mainPath
261
+ });
188
262
  void loadChildren(mainPath, appName);
189
263
  }
190
264
  catch (e) {
265
+ updateRuntimeAppStatus(appName, 'failed', e);
191
266
  showErrorModule(e);
192
267
  }
193
268
  };
@@ -1,5 +1,31 @@
1
1
  import { SinglyLinkedList } from './SinglyLinkedList';
2
2
  import { childrenCallbackRes, ChildrenCycle, EventCycleEnum, EventKeys, FileTreeNode, StoreMiddlewareItem, StoreResponseItem, SubscribeValue } from '../types';
3
+ import type KoaRouter from 'koa-router';
4
+ export type RuntimeAppStatus = 'discovered' | 'loading' | 'ready' | 'failed' | 'disposed';
5
+ export type RuntimeAppCapability = {
6
+ event: boolean;
7
+ httpApi: boolean;
8
+ web: boolean;
9
+ schedule: boolean;
10
+ expose: boolean;
11
+ };
12
+ export type RuntimeAppError = {
13
+ message: string;
14
+ stack?: string;
15
+ time: number;
16
+ };
17
+ export type RuntimeAppRecord = {
18
+ name: string;
19
+ kind: 'main' | 'plugin';
20
+ enabled: boolean;
21
+ status: RuntimeAppStatus;
22
+ rootDir: string;
23
+ mainPath: string;
24
+ error?: RuntimeAppError;
25
+ capabilities: RuntimeAppCapability;
26
+ createdAt: number;
27
+ updatedAt: number;
28
+ };
3
29
  export declare class Logger {
4
30
  #private;
5
31
  constructor();
@@ -14,8 +40,92 @@ export declare class Core {
14
40
  storeChildrenApp: {
15
41
  [key: string]: import("../types").StoreChildrenApp;
16
42
  };
43
+ runtimeApps?: {
44
+ [key: string]: RuntimeAppRecord;
45
+ };
46
+ runtimeAppKoaRouters?: {
47
+ [key: string]: KoaRouter[];
48
+ };
17
49
  };
18
50
  }
51
+ export declare const registerRuntimeApp: (record: Omit<RuntimeAppRecord, "createdAt" | "updatedAt" | "capabilities"> & {
52
+ capabilities?: Partial<RuntimeAppCapability>;
53
+ }) => RuntimeAppRecord;
54
+ export declare const updateRuntimeAppStatus: (name: string, status: RuntimeAppStatus, error?: unknown) => RuntimeAppRecord;
55
+ export declare const updateRuntimeAppCapabilities: (name: string, capabilities: Partial<RuntimeAppCapability>) => RuntimeAppRecord;
56
+ export declare const setRuntimeAppKoaRouters: (name: string, koaRouters?: KoaRouter | KoaRouter[]) => KoaRouter<any, {}>[];
57
+ export declare const getRuntimeAppKoaRouters: (name: string) => KoaRouter<any, {}>[];
58
+ export declare const clearRuntimeAppKoaRouters: (name: string) => void;
59
+ export declare const listRuntimeAppKoaRouters: () => {
60
+ name: string;
61
+ routers: KoaRouter<any, {}>[];
62
+ }[];
63
+ export declare const getRuntimeApp: (name: string) => RuntimeAppRecord;
64
+ export declare const toRuntimeAppSnapshot: (item: RuntimeAppRecord) => {
65
+ capabilities: {
66
+ event: boolean;
67
+ httpApi: boolean;
68
+ web: boolean;
69
+ schedule: boolean;
70
+ expose: boolean;
71
+ };
72
+ error: {
73
+ message: string;
74
+ time: number;
75
+ };
76
+ name: string;
77
+ kind: "main" | "plugin";
78
+ enabled: boolean;
79
+ status: RuntimeAppStatus;
80
+ rootDir: string;
81
+ mainPath: string;
82
+ createdAt: number;
83
+ updatedAt: number;
84
+ };
85
+ export declare const listRuntimeApps: () => {
86
+ capabilities: {
87
+ event: boolean;
88
+ httpApi: boolean;
89
+ web: boolean;
90
+ schedule: boolean;
91
+ expose: boolean;
92
+ };
93
+ error: {
94
+ message: string;
95
+ time: number;
96
+ };
97
+ name: string;
98
+ kind: "main" | "plugin";
99
+ enabled: boolean;
100
+ status: RuntimeAppStatus;
101
+ rootDir: string;
102
+ mainPath: string;
103
+ createdAt: number;
104
+ updatedAt: number;
105
+ }[];
106
+ export declare const disposeRuntimeApp: (name: string) => RuntimeAppRecord;
107
+ export declare const disposeAllRuntimeApps: () => {
108
+ capabilities: {
109
+ event: boolean;
110
+ httpApi: boolean;
111
+ web: boolean;
112
+ schedule: boolean;
113
+ expose: boolean;
114
+ };
115
+ error: {
116
+ message: string;
117
+ time: number;
118
+ };
119
+ name: string;
120
+ kind: "main" | "plugin";
121
+ enabled: boolean;
122
+ status: RuntimeAppStatus;
123
+ rootDir: string;
124
+ mainPath: string;
125
+ createdAt: number;
126
+ updatedAt: number;
127
+ }[];
128
+ export declare const hasRuntimeAppCapability: (name: string, capability: keyof RuntimeAppCapability) => boolean;
19
129
  export declare const bumpStoreVersion: () => void;
20
130
  export declare class Response {
21
131
  #private;
@@ -87,4 +197,10 @@ export declare const core: {
87
197
  storeChildrenApp: {
88
198
  [key: string]: import("../types").StoreChildrenApp;
89
199
  };
200
+ runtimeApps?: {
201
+ [key: string]: RuntimeAppRecord;
202
+ };
203
+ runtimeAppKoaRouters?: {
204
+ [key: string]: KoaRouter[];
205
+ };
90
206
  };
package/lib/app/store.js CHANGED
@@ -107,7 +107,9 @@ class Core {
107
107
  mount: new Map(),
108
108
  unmount: new Map()
109
109
  },
110
- storeChildrenApp: {}
110
+ storeChildrenApp: {},
111
+ runtimeApps: {},
112
+ runtimeAppKoaRouters: {}
111
113
  };
112
114
  }
113
115
  }
@@ -116,6 +118,200 @@ class Core {
116
118
  }
117
119
  }
118
120
  let _storeVersion = 0;
121
+ const createEmptyRuntimeCapabilities = () => ({
122
+ event: false,
123
+ httpApi: false,
124
+ web: false,
125
+ schedule: false,
126
+ expose: false
127
+ });
128
+ const logRuntimeAppStatus = (level, record) => {
129
+ if (!global.logger?.[level]) {
130
+ return;
131
+ }
132
+ global.logger[level]({
133
+ message: 'runtime app status',
134
+ data: {
135
+ app: record.name,
136
+ kind: record.kind,
137
+ status: record.status,
138
+ capabilities: record.capabilities,
139
+ error: record.error?.message ?? null
140
+ }
141
+ });
142
+ };
143
+ const normalizeRuntimeAppError = (error) => {
144
+ if (!error) {
145
+ return undefined;
146
+ }
147
+ if (error instanceof Error) {
148
+ return {
149
+ message: error.message,
150
+ stack: error.stack,
151
+ time: Date.now()
152
+ };
153
+ }
154
+ return {
155
+ message: typeof error === 'string' ? error : 'Unknown runtime app error',
156
+ time: Date.now()
157
+ };
158
+ };
159
+ const sameRuntimeAppCapabilities = (left, right) => {
160
+ return (left.event === right.event && left.httpApi === right.httpApi && left.web === right.web && left.schedule === right.schedule && left.expose === right.expose);
161
+ };
162
+ const sameRuntimeAppError = (left, right) => {
163
+ if (!left && !right) {
164
+ return true;
165
+ }
166
+ if (!left || !right) {
167
+ return false;
168
+ }
169
+ return left.message === right.message && left.stack === right.stack;
170
+ };
171
+ const getRuntimeAppStore = () => {
172
+ if (!global.alemonjsCore.runtimeApps) {
173
+ global.alemonjsCore.runtimeApps = {};
174
+ }
175
+ return global.alemonjsCore.runtimeApps;
176
+ };
177
+ const getRuntimeAppKoaRouterStore = () => {
178
+ if (!global.alemonjsCore.runtimeAppKoaRouters) {
179
+ global.alemonjsCore.runtimeAppKoaRouters = {};
180
+ }
181
+ return global.alemonjsCore.runtimeAppKoaRouters;
182
+ };
183
+ const registerRuntimeApp = (record) => {
184
+ const runtimeApps = getRuntimeAppStore();
185
+ const current = runtimeApps[record.name];
186
+ const now = Date.now();
187
+ const nextCapabilities = {
188
+ ...(current?.capabilities ?? createEmptyRuntimeCapabilities()),
189
+ ...(record.capabilities ?? {})
190
+ };
191
+ runtimeApps[record.name] = {
192
+ name: record.name,
193
+ kind: record.kind,
194
+ enabled: record.enabled,
195
+ status: record.status,
196
+ rootDir: record.rootDir,
197
+ mainPath: record.mainPath,
198
+ error: record.error,
199
+ capabilities: nextCapabilities,
200
+ createdAt: current?.createdAt ?? now,
201
+ updatedAt: now
202
+ };
203
+ if (!current || current.status !== record.status) {
204
+ logRuntimeAppStatus(record.status === 'failed' ? 'warn' : 'debug', runtimeApps[record.name]);
205
+ }
206
+ return runtimeApps[record.name];
207
+ };
208
+ const updateRuntimeAppStatus = (name, status, error) => {
209
+ const runtimeApps = getRuntimeAppStore();
210
+ const current = runtimeApps[name];
211
+ if (!current) {
212
+ return;
213
+ }
214
+ const normalizedError = normalizeRuntimeAppError(error);
215
+ if (current.status === status && sameRuntimeAppError(current.error, normalizedError)) {
216
+ return current;
217
+ }
218
+ current.status = status;
219
+ current.updatedAt = Date.now();
220
+ current.error = normalizedError;
221
+ const level = status === 'failed' ? 'warn' : status === 'disposed' ? 'info' : 'debug';
222
+ logRuntimeAppStatus(level, current);
223
+ return current;
224
+ };
225
+ const updateRuntimeAppCapabilities = (name, capabilities) => {
226
+ const runtimeApps = getRuntimeAppStore();
227
+ const current = runtimeApps[name];
228
+ if (!current) {
229
+ return;
230
+ }
231
+ const nextCapabilities = {
232
+ ...current.capabilities,
233
+ ...capabilities
234
+ };
235
+ if (sameRuntimeAppCapabilities(current.capabilities, nextCapabilities)) {
236
+ return current;
237
+ }
238
+ current.capabilities = nextCapabilities;
239
+ current.updatedAt = Date.now();
240
+ return current;
241
+ };
242
+ const setRuntimeAppKoaRouters = (name, koaRouters) => {
243
+ const koaRouterStore = getRuntimeAppKoaRouterStore();
244
+ if (!koaRouters) {
245
+ delete koaRouterStore[name];
246
+ return [];
247
+ }
248
+ const normalizedRouters = (Array.isArray(koaRouters) ? koaRouters : [koaRouters]).filter(Boolean);
249
+ koaRouterStore[name] = normalizedRouters;
250
+ return normalizedRouters;
251
+ };
252
+ const getRuntimeAppKoaRouters = (name) => {
253
+ return getRuntimeAppKoaRouterStore()[name] ?? [];
254
+ };
255
+ const clearRuntimeAppKoaRouters = (name) => {
256
+ const koaRouterStore = getRuntimeAppKoaRouterStore();
257
+ delete koaRouterStore[name];
258
+ };
259
+ const listRuntimeAppKoaRouters = () => {
260
+ return Object.entries(getRuntimeAppKoaRouterStore())
261
+ .sort(([left], [right]) => {
262
+ if (left === 'main' && right !== 'main') {
263
+ return -1;
264
+ }
265
+ if (left !== 'main' && right === 'main') {
266
+ return 1;
267
+ }
268
+ return left.localeCompare(right);
269
+ })
270
+ .map(([name, routers]) => ({
271
+ name,
272
+ routers: [...routers]
273
+ }));
274
+ };
275
+ const getRuntimeApp = (name) => {
276
+ return getRuntimeAppStore()[name];
277
+ };
278
+ const toRuntimeAppSnapshot = (item) => ({
279
+ ...item,
280
+ capabilities: { ...item.capabilities },
281
+ error: item.error
282
+ ? {
283
+ message: item.error.message,
284
+ time: item.error.time
285
+ }
286
+ : undefined
287
+ });
288
+ const listRuntimeApps = () => {
289
+ return Object.values(getRuntimeAppStore())
290
+ .sort((left, right) => {
291
+ if (left.name === 'main' && right.name !== 'main') {
292
+ return -1;
293
+ }
294
+ if (left.name !== 'main' && right.name === 'main') {
295
+ return 1;
296
+ }
297
+ return left.name.localeCompare(right.name);
298
+ })
299
+ .map(toRuntimeAppSnapshot);
300
+ };
301
+ const disposeRuntimeApp = (name) => {
302
+ clearRuntimeAppKoaRouters(name);
303
+ return updateRuntimeAppStatus(name, 'disposed');
304
+ };
305
+ const disposeAllRuntimeApps = () => {
306
+ const runtimeApps = listRuntimeApps();
307
+ runtimeApps.forEach(app => {
308
+ disposeRuntimeApp(app.name);
309
+ });
310
+ return runtimeApps;
311
+ };
312
+ const hasRuntimeAppCapability = (name, capability) => {
313
+ return Boolean(getRuntimeApp(name)?.capabilities?.[capability]);
314
+ };
119
315
  const bumpStoreVersion = () => {
120
316
  _storeVersion++;
121
317
  };
@@ -449,4 +645,4 @@ process?.on?.('exit', code => {
449
645
  logger.info?.(`[alemonjs][exit] 进程退出,code=${code}`);
450
646
  });
451
647
 
452
- export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, MiddlewareTree, ProcessorEventAutoClearMap, ProcessorEventUserAutoClearMap, Response, ResponseMiddleware, ResponseRouter, ResponseTree, State, StateSubscribe, SubscribeList, bumpStoreVersion, core, getSubscribeList, logger };
648
+ export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, MiddlewareTree, ProcessorEventAutoClearMap, ProcessorEventUserAutoClearMap, Response, ResponseMiddleware, ResponseRouter, ResponseTree, State, StateSubscribe, SubscribeList, bumpStoreVersion, clearRuntimeAppKoaRouters, core, disposeAllRuntimeApps, disposeRuntimeApp, getRuntimeApp, getRuntimeAppKoaRouters, getSubscribeList, hasRuntimeAppCapability, listRuntimeAppKoaRouters, listRuntimeApps, logger, registerRuntimeApp, setRuntimeAppKoaRouters, toRuntimeAppSnapshot, updateRuntimeAppCapabilities, updateRuntimeAppStatus };
package/lib/client.js CHANGED
@@ -16,7 +16,7 @@ import '@koa/cors';
16
16
  import './cbp/routers/router.js';
17
17
  import 'lodash';
18
18
  import 'fs/promises';
19
- import './app/store.js';
19
+ import { disposeAllRuntimeApps } from './app/store.js';
20
20
  import { loadModels } from './app/load_modules/load.js';
21
21
  import './app/load_modules/loadChild.js';
22
22
  import './app/define-children.js';
@@ -28,7 +28,7 @@ import './app/event-processor.js';
28
28
  import './app/event-response.js';
29
29
  import './app/hook-event-context.js';
30
30
  import './app/message-format-old.js';
31
- import 'cron';
31
+ import { scheduleCancelByApp, unregisterAppDir } from './app/schedule-store.js';
32
32
  import './app/event-utils.js';
33
33
  import './app/message-api.js';
34
34
  import './process/platform.js';
@@ -36,6 +36,18 @@ import './process/module.js';
36
36
  import { createServer } from './server/main.js';
37
37
 
38
38
  global.__client_loaded = true;
39
+ let runtimeDisposed = false;
40
+ const disposeRuntime = () => {
41
+ if (runtimeDisposed) {
42
+ return;
43
+ }
44
+ runtimeDisposed = true;
45
+ const apps = disposeAllRuntimeApps();
46
+ apps.forEach(app => {
47
+ scheduleCancelByApp(app.name);
48
+ unregisterAppDir(app.name);
49
+ });
50
+ };
39
51
  const mainServer = () => {
40
52
  const port = process.env.serverPort;
41
53
  if (!port) {
@@ -72,10 +84,12 @@ const mainProcess = () => {
72
84
  ['SIGINT', 'SIGTERM', 'SIGQUIT', 'disconnect'].forEach(sig => {
73
85
  process?.on?.(sig, () => {
74
86
  logger.info?.(`[alemonjs][${sig}] 收到信号,正在关闭...`);
87
+ disposeRuntime();
75
88
  setImmediate(() => process.exit(0));
76
89
  });
77
90
  });
78
91
  process?.on?.('exit', code => {
92
+ disposeRuntime();
79
93
  logger.info?.(`[alemonjs][exit] 进程退出,code=${code}`);
80
94
  });
81
95
  process.on('message', msg => {
@@ -86,6 +100,7 @@ const mainProcess = () => {
86
100
  mainServer();
87
101
  }
88
102
  else if (data?.type === 'stop') {
103
+ disposeRuntime();
89
104
  process.exit(0);
90
105
  }
91
106
  }
package/lib/global.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { DefineChildrenFunc, OnResponseReversalFunc, OnMiddlewareReversalFunc, OnSelectsFunc, OnDataFormatFunc, OnResponseReversalFuncBack, OnGroupFunc, OnMiddlewareReversalFuncBack, DefineResponseFunc, defineMiddlewareFunc, StoreChildrenApp, StateSubscribeMap, SubscribeKeysMap, LoggerUtils, ResponseState, StartOptions } from './types';
2
2
  import WebSocket, { Server } from 'ws';
3
3
  import { IncomingMessage } from 'http';
4
+ import type KoaRouter from 'koa-router';
5
+ import type { RuntimeAppRecord } from './app/store.js';
4
6
  declare global {
5
7
  var __config: any;
6
8
  var __options: StartOptions;
@@ -12,6 +14,12 @@ declare global {
12
14
  storeChildrenApp: {
13
15
  [key: string]: StoreChildrenApp;
14
16
  };
17
+ runtimeApps?: {
18
+ [key: string]: RuntimeAppRecord;
19
+ };
20
+ runtimeAppKoaRouters?: {
21
+ [key: string]: KoaRouter[];
22
+ };
15
23
  };
16
24
  var chatbotServer: Server<typeof WebSocket, typeof IncomingMessage>;
17
25
  var chatbotPlatform: WebSocket;
package/lib/index.js CHANGED
@@ -5,7 +5,7 @@ export { createEventName, createHash, createResult, createUserHashKey, fastHash,
5
5
  export { cbpClient } from './cbp/connects/client.js';
6
6
  export { cbpPlatform } from './cbp/connects/platform.js';
7
7
  export { cbpServer } from './cbp/server/main.js';
8
- export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, MiddlewareTree, ProcessorEventAutoClearMap, ProcessorEventUserAutoClearMap, Response, ResponseMiddleware, ResponseRouter, ResponseTree, State, StateSubscribe, SubscribeList, bumpStoreVersion, core, getSubscribeList, logger } from './app/store.js';
8
+ export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, MiddlewareTree, ProcessorEventAutoClearMap, ProcessorEventUserAutoClearMap, Response, ResponseMiddleware, ResponseRouter, ResponseTree, State, StateSubscribe, SubscribeList, bumpStoreVersion, clearRuntimeAppKoaRouters, core, disposeAllRuntimeApps, disposeRuntimeApp, getRuntimeApp, getRuntimeAppKoaRouters, getSubscribeList, hasRuntimeAppCapability, listRuntimeAppKoaRouters, listRuntimeApps, logger, registerRuntimeApp, setRuntimeAppKoaRouters, toRuntimeAppSnapshot, updateRuntimeAppCapabilities, updateRuntimeAppStatus } from './app/store.js';
9
9
  export { Expose, clearAllExpose, disposeExpose, registerExpose } from './app/expose.js';
10
10
  export { loadModels, run } from './app/load_modules/load.js';
11
11
  export { loadChildren, loadChildrenFile } from './app/load_modules/loadChild.js';
package/lib/main.js CHANGED
@@ -58,8 +58,8 @@ const start = (options = {}) => {
58
58
  cbpServer(port, () => {
59
59
  const httpURL = `http://127.0.0.1:${port}`;
60
60
  const wsURL = `ws://127.0.0.1:${port}`;
61
- logger.info(`[CBP server started at ${httpURL}]`);
62
- logger.info(`[CBP server started at ${wsURL}]`);
61
+ logger.info(`[CBP-Server] ${httpURL}`);
62
+ logger.info(`[CBP-Server] ${wsURL}]`);
63
63
  startClient(options);
64
64
  startPlatform(options);
65
65
  });
@@ -67,7 +67,7 @@ const start = (options = {}) => {
67
67
  else {
68
68
  const sockPath = generateSocketPath();
69
69
  process.env.__ALEMON_DIRECT_SOCK = sockPath;
70
- logger.info('[Direct-IPC mode] 平台↔客户端直连通道,无主进程桥接');
70
+ logger.info('[Direct-IPC] 平台↔客户端直连');
71
71
  startClient(options);
72
72
  startPlatform(options);
73
73
  }
@@ -9,14 +9,113 @@ import module$1 from 'module';
9
9
  import { ResultCode } from '../../core/variable.js';
10
10
  import 'yaml';
11
11
  import '../../core/utils.js';
12
+ import { listRuntimeApps, getRuntimeApp, toRuntimeAppSnapshot, listRuntimeAppKoaRouters, hasRuntimeAppCapability, getRuntimeAppKoaRouters } from '../../app/store.js';
12
13
 
13
14
  const initRequire = () => { };
14
15
  initRequire.resolve = () => '';
15
16
  const require$1 = module$1?.createRequire?.(import.meta.url) ?? initRequire;
16
- const mainDirMap = new Map();
17
17
  const router = new KoaRouter({
18
18
  prefix: '/'
19
19
  });
20
+ const resolvePackageRoot = (startDir) => {
21
+ let currentDir = startDir;
22
+ while (currentDir && currentDir !== path__default.dirname(currentDir)) {
23
+ if (existsSync(path__default.join(currentDir, 'package.json'))) {
24
+ return currentDir;
25
+ }
26
+ currentDir = path__default.dirname(currentDir);
27
+ }
28
+ return startDir;
29
+ };
30
+ const readWebRootConfig = (packageRoot) => {
31
+ const packageJsonPath = path__default.join(packageRoot, 'package.json');
32
+ if (!existsSync(packageJsonPath)) {
33
+ return '';
34
+ }
35
+ const pkg = require$1(packageJsonPath) ?? {};
36
+ return pkg?.alemonjs?.web?.root ?? '';
37
+ };
38
+ const denyRuntimeAppAccess = (ctx, appName, capability) => {
39
+ const runtimeApp = getRuntimeApp(appName);
40
+ if (!runtimeApp || !runtimeApp.enabled) {
41
+ ctx.status = 404;
42
+ ctx.body = {
43
+ code: 404,
44
+ message: '应用未注册或未启用',
45
+ data: null
46
+ };
47
+ return null;
48
+ }
49
+ if (runtimeApp.status === 'discovered' || runtimeApp.status === 'loading') {
50
+ ctx.status = 503;
51
+ ctx.body = {
52
+ code: 503,
53
+ message: '应用正在初始化',
54
+ data: {
55
+ app: appName,
56
+ status: runtimeApp.status
57
+ }
58
+ };
59
+ return null;
60
+ }
61
+ if (runtimeApp.status === 'failed') {
62
+ ctx.status = 500;
63
+ ctx.body = {
64
+ code: 500,
65
+ message: '应用生命周期失败',
66
+ data: {
67
+ app: appName,
68
+ status: runtimeApp.status,
69
+ lifecycle: 'failed'
70
+ }
71
+ };
72
+ return null;
73
+ }
74
+ if (runtimeApp.status === 'disposed') {
75
+ ctx.status = 410;
76
+ ctx.body = {
77
+ code: 410,
78
+ message: '应用已卸载或已结束服务',
79
+ data: {
80
+ app: appName,
81
+ status: runtimeApp.status,
82
+ lifecycle: 'disposed'
83
+ }
84
+ };
85
+ return null;
86
+ }
87
+ if (runtimeApp.status !== 'ready' || !hasRuntimeAppCapability(appName, capability)) {
88
+ ctx.status = 404;
89
+ ctx.body = {
90
+ code: 404,
91
+ message: '应用未提供对应服务能力',
92
+ data: null
93
+ };
94
+ return null;
95
+ }
96
+ return runtimeApp;
97
+ };
98
+ const dispatchRegisteredKoaRouters = async (ctx) => {
99
+ const registeredRouters = listRuntimeAppKoaRouters();
100
+ for (const item of registeredRouters) {
101
+ const runtimeApp = getRuntimeApp(item.name);
102
+ if (!runtimeApp || !runtimeApp.enabled || runtimeApp.status !== 'ready' || !hasRuntimeAppCapability(item.name, 'httpApi')) {
103
+ continue;
104
+ }
105
+ const routers = getRuntimeAppKoaRouters(item.name);
106
+ for (const koaRouter of routers) {
107
+ const beforeMatched = Array.isArray(ctx.matched) ? ctx.matched.length : 0;
108
+ await koaRouter.routes()(ctx, async () => { });
109
+ const afterMatched = Array.isArray(ctx.matched) ? ctx.matched.length : 0;
110
+ if (afterMatched <= beforeMatched) {
111
+ continue;
112
+ }
113
+ await koaRouter.allowedMethods()(ctx, async () => { });
114
+ return true;
115
+ }
116
+ }
117
+ return false;
118
+ };
20
119
  router.get('/', ctx => {
21
120
  ctx.status = 200;
22
121
  ctx.set('Content-Type', 'text/html; charset=utf-8');
@@ -30,6 +129,47 @@ router.get('api/online', ctx => {
30
129
  data: null
31
130
  };
32
131
  });
132
+ router.get('api/runtime/apps', ctx => {
133
+ const status = String(ctx.query?.status ?? '').trim();
134
+ const data = listRuntimeApps().filter(item => {
135
+ if (!status) {
136
+ return true;
137
+ }
138
+ return item.status === status;
139
+ });
140
+ ctx.status = 200;
141
+ ctx.body = {
142
+ code: 200,
143
+ message: 'runtime apps',
144
+ data
145
+ };
146
+ });
147
+ router.get('api/runtime/apps/:app', ctx => {
148
+ const appName = ctx.params.app;
149
+ const runtimeApp = getRuntimeApp(appName);
150
+ if (!runtimeApp) {
151
+ ctx.status = 404;
152
+ ctx.body = {
153
+ code: 404,
154
+ message: '应用未注册',
155
+ data: null
156
+ };
157
+ return;
158
+ }
159
+ ctx.status = 200;
160
+ ctx.body = {
161
+ code: 200,
162
+ message: 'runtime app',
163
+ data: toRuntimeAppSnapshot(runtimeApp)
164
+ };
165
+ });
166
+ router.use(async (ctx, next) => {
167
+ const handled = await dispatchRegisteredKoaRouters(ctx);
168
+ if (handled) {
169
+ return;
170
+ }
171
+ await next();
172
+ });
33
173
  router.all('app/{*path}', async (ctx) => {
34
174
  if (!process.env.input) {
35
175
  ctx.status = 400;
@@ -43,6 +183,10 @@ router.all('app/{*path}', async (ctx) => {
43
183
  const rootPath = process.cwd();
44
184
  const apiPath = '/app/api';
45
185
  if (ctx.path.startsWith(apiPath)) {
186
+ const runtimeApp = denyRuntimeAppAccess(ctx, 'main', 'httpApi');
187
+ if (!runtimeApp) {
188
+ return;
189
+ }
46
190
  const mainPath = join(rootPath, process.env.input);
47
191
  if (!existsSync(mainPath)) {
48
192
  ctx.status = 400;
@@ -111,9 +255,13 @@ router.all('app/{*path}', async (ctx) => {
111
255
  }
112
256
  let root = '';
113
257
  const resourcePath = formatPath(ctx.params?.path);
258
+ const runtimeApp = denyRuntimeAppAccess(ctx, 'main', 'web');
259
+ if (!runtimeApp) {
260
+ return;
261
+ }
262
+ const packageRoot = resolvePackageRoot(runtimeApp.rootDir);
114
263
  try {
115
- const pkg = require$1(path__default.join(rootPath, 'package.json')) ?? {};
116
- root = pkg.alemonjs?.web?.root ?? '';
264
+ root = readWebRootConfig(packageRoot);
117
265
  }
118
266
  catch (err) {
119
267
  ctx.status = 500;
@@ -124,7 +272,7 @@ router.all('app/{*path}', async (ctx) => {
124
272
  };
125
273
  return;
126
274
  }
127
- const webRoot = root ? path__default.join(rootPath, root) : rootPath;
275
+ const webRoot = root ? path__default.join(packageRoot, root) : packageRoot;
128
276
  const fullPath = safePath(webRoot, resourcePath);
129
277
  if (!fullPath) {
130
278
  ctx.status = 403;
@@ -177,22 +325,12 @@ router.all('apps/:app/{*path}', async (ctx) => {
177
325
  }
178
326
  const apiPath = `/apps/${appName}/api`;
179
327
  if (ctx.path.startsWith(apiPath)) {
328
+ const runtimeApp = denyRuntimeAppAccess(ctx, appName, 'httpApi');
329
+ if (!runtimeApp) {
330
+ return;
331
+ }
180
332
  try {
181
- if (!mainDirMap.has(appName)) {
182
- const mainPath = require$1.resolve(appName);
183
- if (!existsSync(mainPath)) {
184
- ctx.status = 400;
185
- ctx.body = {
186
- code: 400,
187
- message: '未找到主要入口文件',
188
- data: null
189
- };
190
- return;
191
- }
192
- const mainDir = dirname(mainPath);
193
- mainDirMap.set(appName, mainDir);
194
- }
195
- const routeBase = join(mainDirMap.get(appName), 'route');
333
+ const routeBase = join(runtimeApp.rootDir, 'route');
196
334
  const dir = safePath(routeBase, ctx.path?.replace(apiPath, '/api') || '');
197
335
  if (!dir) {
198
336
  ctx.status = 403;
@@ -250,12 +388,15 @@ router.all('apps/:app/{*path}', async (ctx) => {
250
388
  ctx.status = 405;
251
389
  return;
252
390
  }
253
- const rootPath = path__default.join(process.cwd(), 'node_modules', appName);
391
+ const runtimeApp = denyRuntimeAppAccess(ctx, appName, 'web');
392
+ if (!runtimeApp) {
393
+ return;
394
+ }
395
+ const packageRoot = resolvePackageRoot(runtimeApp.rootDir);
254
396
  const resourcePath = formatPath(ctx.params?.path);
255
397
  let root = '';
256
398
  try {
257
- const pkg = require$1(`${appName}/package`) ?? {};
258
- root = pkg?.alemonjs?.web?.root ?? '';
399
+ root = readWebRootConfig(packageRoot);
259
400
  }
260
401
  catch (err) {
261
402
  ctx.status = 500;
@@ -266,7 +407,7 @@ router.all('apps/:app/{*path}', async (ctx) => {
266
407
  };
267
408
  return;
268
409
  }
269
- const webRoot = root ? path__default.join(rootPath, root) : rootPath;
410
+ const webRoot = root ? path__default.join(packageRoot, root) : packageRoot;
270
411
  const fullPath = safePath(webRoot, resourcePath);
271
412
  if (!fullPath) {
272
413
  ctx.status = 403;
@@ -3,6 +3,7 @@ import { ClientAPI } from '../client';
3
3
  import { EventKeys, Events } from './map';
4
4
  import { DataEnums } from '../message';
5
5
  import { Expose } from '../../app/expose';
6
+ import type KoaRouter from 'koa-router';
6
7
  export type Current<T extends EventKeys> = (event: Events[T], next: Next) => Promise<boolean | void | undefined> | boolean | void | undefined;
7
8
  export type OnResponseValue<C, T extends EventKeys> = {
8
9
  current: C;
@@ -56,6 +57,7 @@ export type childrenCallbackRes = {
56
57
  middleware?: ReturnType<defineMiddlewareFunc>;
57
58
  responseRouter?: ReturnType<DefineRouterFunc>;
58
59
  middlewareRouter?: ReturnType<DefineRouterFunc>;
60
+ koaRouter?: KoaRouter | KoaRouter[];
59
61
  expose?: Expose;
60
62
  } | undefined;
61
63
  export type childrenCallback = ChildrenCycle & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alemonjs",
3
- "version": "2.1.80",
3
+ "version": "2.1.82",
4
4
  "description": "bot script",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",