alemonjs 2.1.81 → 2.1.83

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.
@@ -0,0 +1,2 @@
1
+ import type { EventErrorContext } from '../types';
2
+ export declare const dispatchEventError: (context: EventErrorContext) => Promise<boolean>;
@@ -0,0 +1,20 @@
1
+ import { getChildrenApp } from './store.js';
2
+ import { showErrorModule } from '../core/utils.js';
3
+
4
+ const dispatchEventError = async (context) => {
5
+ const app = getChildrenApp(context.appName);
6
+ const onEventError = app?.cycle?.onEventError;
7
+ if (!onEventError) {
8
+ return false;
9
+ }
10
+ try {
11
+ const result = await onEventError(context);
12
+ return result === 'continue';
13
+ }
14
+ catch (error) {
15
+ showErrorModule(error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'onEventError failed'));
16
+ return false;
17
+ }
18
+ };
19
+
20
+ export { dispatchEventError };
@@ -1 +1,5 @@
1
- export declare const createCallHandler: (valueEvent: any) => (currents: any, nextEvent: any) => void;
1
+ import type { EventErrorPhase } from '../types';
2
+ export declare const createCallHandler: (valueEvent: any) => (currents: any, nextEvent: any, meta?: {
3
+ appName?: string;
4
+ phase?: EventErrorPhase;
5
+ }) => void;
@@ -3,9 +3,10 @@ import 'path';
3
3
  import 'yaml';
4
4
  import { showErrorModule } from '../core/utils.js';
5
5
  import { withEventContext, finishCurrentTrace } from './hook-event-context.js';
6
+ import { dispatchEventError } from './event-error.js';
6
7
 
7
8
  const createCallHandler = valueEvent => {
8
- const callHandler = (currents, nextEvent) => {
9
+ const callHandler = (currents, nextEvent, meta) => {
9
10
  let index = 0;
10
11
  let isClose = false;
11
12
  let isNext = false;
@@ -24,7 +25,10 @@ const createCallHandler = valueEvent => {
24
25
  isNext = true;
25
26
  nextEvent(...cns);
26
27
  };
27
- const res = await withEventContext(valueEvent, nextFn, () => currents[index](valueEvent, nextFn));
28
+ const res = await withEventContext(valueEvent, nextFn, () => currents[index](valueEvent, nextFn), {
29
+ appName: meta?.appName,
30
+ phase: meta?.phase
31
+ });
28
32
  if (res !== true) {
29
33
  if (!isNext) {
30
34
  finishCurrentTrace('consumed');
@@ -35,7 +39,19 @@ const createCallHandler = valueEvent => {
35
39
  }
36
40
  catch (err) {
37
41
  finishCurrentTrace('error');
42
+ const shouldContinue = meta?.appName && meta?.phase
43
+ ? await dispatchEventError({
44
+ event: valueEvent,
45
+ error: err,
46
+ appName: meta.appName,
47
+ phase: meta.phase
48
+ })
49
+ : false;
38
50
  showErrorModule(err);
51
+ if (shouldContinue) {
52
+ nextEvent();
53
+ return;
54
+ }
39
55
  return;
40
56
  }
41
57
  ++index;
@@ -1,4 +1,7 @@
1
1
  import { Next, Events, EventKeys, FileTreeNode, StoreResponseItem } from '../types';
2
2
  export declare const clearModuleCache: (path?: string) => void;
3
3
  export declare const createNextStep: <T extends EventKeys>(valueEvent: Events[T], select: T, next: Next, files: StoreResponseItem[], callHandler: (currents: any, nextEvent: any) => void) => Next;
4
- export declare const createFileTreeStep: <T extends EventKeys>(valueEvent: Events[T], select: T, next: Next, root: FileTreeNode, callHandler: (currents: any, nextEvent: any) => void) => Next;
4
+ export declare const createFileTreeStep: <T extends EventKeys>(valueEvent: Events[T], select: T, next: Next, root: FileTreeNode, callHandler: (currents: any, nextEvent: any, meta?: {
5
+ appName?: string;
6
+ phase?: "middleware" | "response";
7
+ }) => void, phase: "middleware" | "response") => Next;
@@ -1,5 +1,6 @@
1
1
  import { getCachedRegExp, showErrorModule } from '../core/utils.js';
2
2
  import { EventMessageText } from '../core/variable.js';
3
+ import { dispatchEventError } from './event-error.js';
3
4
 
4
5
  const moduleCache = new Map();
5
6
  const shouldSkipFile = (file, select, valueEvent) => {
@@ -37,7 +38,7 @@ const loadModule = async (filePath) => {
37
38
  moduleCache.set(filePath, mod);
38
39
  return mod;
39
40
  };
40
- const callHandlerFile = async (valueEvent, select, file, nextStep, callback) => {
41
+ const callHandlerFile = async (valueEvent, select, file, nextStep, callback, phase) => {
41
42
  try {
42
43
  const app = await loadModule(file.path);
43
44
  if (!app?.default?.current || !app?.default?.select) {
@@ -60,8 +61,16 @@ const callHandlerFile = async (valueEvent, select, file, nextStep, callback) =>
60
61
  callback(app);
61
62
  }
62
63
  catch (err) {
64
+ const shouldContinue = await dispatchEventError({
65
+ event: valueEvent,
66
+ error: err,
67
+ appName: file.appName,
68
+ phase
69
+ });
63
70
  showErrorModule(err);
64
- nextStep();
71
+ if (shouldContinue) {
72
+ nextStep();
73
+ }
65
74
  }
66
75
  };
67
76
  const createNextStep = (valueEvent, select, next, files, callHandler) => {
@@ -94,14 +103,14 @@ const createNextStep = (valueEvent, select, next, files, callHandler) => {
94
103
  }, app => {
95
104
  const currentsItem = Array.isArray(app.default.current) ? app.default.current : [app.default.current];
96
105
  currents.push(...currentsItem);
97
- });
106
+ }, 'response');
98
107
  if (currents.length > 0) {
99
108
  callHandler(currents, nextStep);
100
109
  }
101
110
  };
102
111
  return nextStep;
103
112
  };
104
- const createFileTreeStep = (valueEvent, select, next, root, callHandler) => {
113
+ const createFileTreeStep = (valueEvent, select, next, root, callHandler, phase) => {
105
114
  const processNode = (node, done) => {
106
115
  if (node.middleware?.path) {
107
116
  void checkMiddleware(node, done);
@@ -123,7 +132,7 @@ const createFileTreeStep = (valueEvent, select, next, root, callHandler) => {
123
132
  matched = true;
124
133
  const items = Array.isArray(app.default.current) ? app.default.current : [app.default.current];
125
134
  mwCurrents.push(...items);
126
- });
135
+ }, 'middleware');
127
136
  if (matched) {
128
137
  if (mwCurrents.length === 0) {
129
138
  void processContent(node, done);
@@ -141,6 +150,9 @@ const createFileTreeStep = (valueEvent, select, next, root, callHandler) => {
141
150
  return;
142
151
  }
143
152
  void processContent(node, done);
153
+ }, {
154
+ appName: node.middleware?.appName,
155
+ phase
144
156
  });
145
157
  }
146
158
  };
@@ -174,8 +186,11 @@ const createFileTreeStep = (valueEvent, select, next, root, callHandler) => {
174
186
  else {
175
187
  processFiles(node, idx + 1, filesDone, treeDone);
176
188
  }
189
+ }, {
190
+ appName: file.appName,
191
+ phase
177
192
  });
178
- });
193
+ }, phase);
179
194
  };
180
195
  const processChildNodes = (node, done) => {
181
196
  const childKeys = Array.from(node.children.keys());
@@ -1,2 +1,5 @@
1
1
  import { Next, Events, EventKeys, ResponseRoute } from '../types';
2
- export declare const createRouteProcessChildren: <T extends EventKeys>(valueEvent: Events[T], select: T, nextCycle: Next, callHandler: (currents: any, nextEvent: any) => void) => (nodes: ResponseRoute[], _pending: any[], next: () => Promise<void> | void) => void;
2
+ export declare const createRouteProcessChildren: <T extends EventKeys>(valueEvent: Events[T], select: T, nextCycle: Next, callHandler: (currents: any, nextEvent: any, meta?: {
3
+ appName?: string;
4
+ phase?: "route";
5
+ }) => void, phase: "route") => (nodes: ResponseRoute[], _pending: any[], next: () => Promise<void> | void) => void;
@@ -3,6 +3,7 @@ import 'fs';
3
3
  import 'path';
4
4
  import 'yaml';
5
5
  import { getCachedRegExp, showErrorModule } from '../core/utils.js';
6
+ import { dispatchEventError } from './event-error.js';
6
7
 
7
8
  const AsyncFunction = (async () => { }).constructor;
8
9
  function isAsyncFunction(fn) {
@@ -11,7 +12,7 @@ function isAsyncFunction(fn) {
11
12
  function isFunction(value) {
12
13
  return isAsyncFunction(value) || typeof value === 'function' || value instanceof Function;
13
14
  }
14
- const createRouteProcessChildren = (valueEvent, select, nextCycle, callHandler) => {
15
+ const createRouteProcessChildren = (valueEvent, select, nextCycle, callHandler, phase) => {
15
16
  const handlerResultCache = new Map();
16
17
  const collectHandlers = (tail) => {
17
18
  const result = [];
@@ -126,11 +127,24 @@ const createRouteProcessChildren = (valueEvent, select, nextCycle, callHandler)
126
127
  return;
127
128
  }
128
129
  void nextNode();
130
+ }, {
131
+ appName: node.appName,
132
+ phase
129
133
  });
130
134
  }
131
135
  catch (err) {
136
+ const shouldContinue = typeof node.appName === 'string'
137
+ ? await dispatchEventError({
138
+ event: valueEvent,
139
+ error: err,
140
+ appName: node.appName,
141
+ phase
142
+ })
143
+ : false;
132
144
  showErrorModule(err);
133
- void nextNode();
145
+ if (shouldContinue) {
146
+ void nextNode();
147
+ }
134
148
  }
135
149
  };
136
150
  void nextNode();
@@ -8,10 +8,10 @@ const responseRouterSingleton = new ResponseRouter();
8
8
  const expendEvent = (valueEvent, select, next) => {
9
9
  const root = responseTreeSingleton.value;
10
10
  const callHandler = createCallHandler(valueEvent);
11
- const nextEvent = createFileTreeStep(valueEvent, select, next, root, callHandler);
11
+ const nextEvent = createFileTreeStep(valueEvent, select, next, root, callHandler, 'response');
12
12
  const routes = responseRouterSingleton.value;
13
13
  const callRouteHandler = createCallHandler(valueEvent);
14
- const processChildren = createRouteProcessChildren(valueEvent, select, nextEvent, callRouteHandler);
14
+ const processChildren = createRouteProcessChildren(valueEvent, select, nextEvent, callRouteHandler, 'route');
15
15
  void processChildren(routes, [], nextEvent);
16
16
  };
17
17
 
@@ -8,10 +8,10 @@ const middlewareRouterSingleton = new MiddlewareRouter();
8
8
  const expendMiddleware = (valueEvent, select, next) => {
9
9
  const root = middlewareTreeSingleton.value;
10
10
  const callHandler = createCallHandler(valueEvent);
11
- const nextMiddleware = createFileTreeStep(valueEvent, select, next, root, callHandler);
11
+ const nextMiddleware = createFileTreeStep(valueEvent, select, next, root, callHandler, 'middleware');
12
12
  const routes = middlewareRouterSingleton.value;
13
13
  const callRouteHandler = createCallHandler(valueEvent);
14
- const processChildren = createRouteProcessChildren(valueEvent, select, nextMiddleware, callRouteHandler);
14
+ const processChildren = createRouteProcessChildren(valueEvent, select, nextMiddleware, callRouteHandler, 'route');
15
15
  void processChildren(routes, [], nextMiddleware);
16
16
  };
17
17
 
@@ -5,6 +5,7 @@ import { showErrorModule } from '../core/utils.js';
5
5
  import { getSubscribeList } from './store.js';
6
6
  import { SubscribeStatus } from './config.js';
7
7
  import { withEventContext, finishCurrentTrace } from './hook-event-context.js';
8
+ import { dispatchEventError } from './event-error.js';
8
9
 
9
10
  const expendSubscribe = (valueEvent, select, next, choose) => {
10
11
  const subListValue = getSubscribeList(choose, select);
@@ -64,15 +65,29 @@ const expendSubscribe = (valueEvent, select, next, choose) => {
64
65
  }
65
66
  };
66
67
  try {
67
- const result = withEventContext(valueEvent, Continue, () => item.data.current(valueEvent, Continue));
68
+ const result = withEventContext(valueEvent, Continue, () => item.data.current(valueEvent, Continue), {
69
+ appName: item.data.appName,
70
+ phase: 'subscribe'
71
+ });
68
72
  if (result && typeof result.then === 'function') {
69
73
  void result.then(() => {
70
74
  if (!isContinue) {
71
75
  finishCurrentTrace('consumed');
72
76
  }
73
- }, error => {
77
+ }, async (error) => {
74
78
  finishCurrentTrace('error');
79
+ const shouldContinue = typeof item.data.appName === 'string'
80
+ ? await dispatchEventError({
81
+ event: valueEvent,
82
+ error,
83
+ appName: item.data.appName,
84
+ phase: 'subscribe'
85
+ })
86
+ : false;
75
87
  showErrorModule(error);
88
+ if (shouldContinue) {
89
+ nextObserver(true);
90
+ }
76
91
  });
77
92
  return;
78
93
  }
@@ -82,7 +97,20 @@ const expendSubscribe = (valueEvent, select, next, choose) => {
82
97
  }
83
98
  catch (error) {
84
99
  finishCurrentTrace('error');
85
- showErrorModule(error);
100
+ void (async () => {
101
+ const shouldContinue = typeof item.data.appName === 'string'
102
+ ? await dispatchEventError({
103
+ event: valueEvent,
104
+ error,
105
+ appName: item.data.appName,
106
+ phase: 'subscribe'
107
+ })
108
+ : false;
109
+ showErrorModule(error);
110
+ if (shouldContinue) {
111
+ nextObserver(true);
112
+ }
113
+ })();
86
114
  }
87
115
  };
88
116
  nextObserver();
@@ -4,6 +4,7 @@ import { expendCycle } from './event-processor-cycle.js';
4
4
  import { withProcessorTrace, finishCurrentTrace } from './hook-event-context.js';
5
5
  import { ProcessorEventAutoClearMap, ProcessorEventUserAutoClearMap } from './store.js';
6
6
  import { getCachedRegExp, matchIn, fastHash } from '../core/utils.js';
7
+ import { dispatchEventStart } from './lifecycle-callbacks.js';
7
8
 
8
9
  const filter = ({ Now, store, INTERVAL }, MessageId) => {
9
10
  if (store.has(MessageId)) {
@@ -59,8 +60,12 @@ const callback = () => {
59
60
  };
60
61
  setTimeout(callback, processorRepeatedClearTimeMin);
61
62
  const onProcessor = (name, event, data) => {
62
- withProcessorTrace(name, event, () => {
63
+ void withProcessorTrace(name, event, async () => {
63
64
  try {
65
+ await dispatchEventStart({
66
+ event,
67
+ name
68
+ });
64
69
  const value = getConfigValue();
65
70
  const disabledTextRegular = value?.disabled_text_regular;
66
71
  if (disabledTextRegular && event['MessageText']) {
@@ -1,10 +1,14 @@
1
1
  import { Result } from '../core';
2
- import { EventKeys, Events } from '../types';
3
- export type EventTraceReason = 'filtered' | 'completed' | 'consumed' | 'error';
4
- export declare const withEventContext: <T extends EventKeys, R>(event: Events[T], next: (...args: boolean[]) => void, runner: () => R) => R;
2
+ import { EventErrorPhase, EventKeys, Events, EventTraceReason } from '../types';
3
+ export declare const withEventContext: <T extends EventKeys, R>(event: Events[T], next: (...args: boolean[]) => void, runner: () => R, options?: {
4
+ appName?: string;
5
+ phase?: EventErrorPhase;
6
+ }) => R;
5
7
  export declare const withProcessorTrace: <T extends EventKeys, R>(select: T, event: Events[T], runner: () => R) => R;
6
8
  export declare const getCurrentEvent: <T extends EventKeys>() => Events[T] | undefined;
7
9
  export declare const getCurrentNext: () => ((...args: boolean[]) => void) | undefined;
10
+ export declare const getCurrentAppName: () => string | undefined;
11
+ export declare const getCurrentPhase: () => EventErrorPhase | undefined;
8
12
  export declare const finishCurrentTrace: (reason: EventTraceReason) => void;
9
13
  export declare const markEventSendAttempt: <T extends EventKeys>(event?: Events[T]) => void;
10
14
  export declare const markEventSendSuccess: <T extends EventKeys>(event?: Events[T]) => void;
@@ -2,6 +2,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { performance } from 'node:perf_hooks';
3
3
  import { getConfigValue } from '../core/config.js';
4
4
  import { ResultCode } from '../core/variable.js';
5
+ import { dispatchEventFinished } from './lifecycle-callbacks.js';
5
6
 
6
7
  const eventStore = new AsyncLocalStorage();
7
8
  const shouldShowLog = (event) => {
@@ -61,19 +62,31 @@ const createEventTrace = (select) => {
61
62
  message: 'event processor finished',
62
63
  data: createLogData(event, trace.select, reason, duration)
63
64
  });
64
- return;
65
65
  }
66
- logger.info(createLogText(event, trace.select, reason, duration));
66
+ else {
67
+ logger.info(createLogText(event, trace.select, reason, duration));
68
+ }
69
+ void dispatchEventFinished({
70
+ event,
71
+ name: trace.select,
72
+ reason,
73
+ duration,
74
+ hasSendAttempted: event._sendAttempted === true || event._has_send_attempt === true,
75
+ hasSendSucceeded: event._sendSucceeded === true || event._has_send_success === true,
76
+ lastSendError: event._lastSendError ?? event._last_send_error ?? null
77
+ });
67
78
  }
68
79
  };
69
80
  return trace;
70
81
  };
71
- const withEventContext = (event, next, runner) => {
82
+ const withEventContext = (event, next, runner, options) => {
72
83
  const current = eventStore.getStore();
73
84
  return eventStore.run({
74
85
  event,
75
86
  next,
76
- trace: current?.trace
87
+ trace: current?.trace,
88
+ appName: options?.appName ?? current?.appName,
89
+ phase: options?.phase ?? current?.phase
77
90
  }, runner);
78
91
  };
79
92
  const withProcessorTrace = (select, event, runner) => {
@@ -90,6 +103,12 @@ const getCurrentEvent = () => {
90
103
  const getCurrentNext = () => {
91
104
  return eventStore.getStore()?.next;
92
105
  };
106
+ const getCurrentAppName = () => {
107
+ return eventStore.getStore()?.appName;
108
+ };
109
+ const getCurrentPhase = () => {
110
+ return eventStore.getStore()?.phase;
111
+ };
93
112
  const finishCurrentTrace = (reason) => {
94
113
  eventStore.getStore()?.trace?.finish(reason);
95
114
  };
@@ -144,4 +163,4 @@ const recordEventSendResults = (results, event) => {
144
163
  }
145
164
  };
146
165
 
147
- export { finishCurrentTrace, getCurrentEvent, getCurrentNext, markEventSendAttempt, markEventSendFailure, markEventSendSuccess, recordEventSendResults, withEventContext, withProcessorTrace };
166
+ export { finishCurrentTrace, getCurrentAppName, getCurrentEvent, getCurrentNext, getCurrentPhase, markEventSendAttempt, markEventSendFailure, markEventSendSuccess, recordEventSendResults, withEventContext, withProcessorTrace };
@@ -1,12 +1,13 @@
1
1
  import { ResultCode } from '../../core/variable.js';
2
2
  import { SubscribeList } from '../store.js';
3
3
  import { SubscribeStatus } from '../config.js';
4
- import { getCurrentEvent } from '../hook-event-context.js';
4
+ import { getCurrentEvent, getCurrentAppName } from '../hook-event-context.js';
5
5
 
6
6
  function useSubscribe(eventOrSelects, maybeSelects) {
7
7
  const selects = (maybeSelects === undefined ? eventOrSelects : maybeSelects);
8
8
  const event = (maybeSelects === undefined ? undefined : eventOrSelects);
9
9
  const valueEvent = event ?? getCurrentEvent();
10
+ const appName = getCurrentAppName();
10
11
  if (typeof valueEvent !== 'object' || valueEvent === null) {
11
12
  logger.error({
12
13
  code: ResultCode.FailParams,
@@ -50,6 +51,7 @@ function useSubscribe(eventOrSelects, maybeSelects) {
50
51
  }
51
52
  }
52
53
  subList.value.append({
54
+ appName,
53
55
  choose,
54
56
  selects: curSelects,
55
57
  keys: values,
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, clearRuntimeAppKoaRouters, core, disposeAllRuntimeApps, disposeRuntimeApp, getRuntimeApp, getRuntimeAppKoaRouters, getSubscribeList, hasRuntimeAppCapability, listRuntimeAppKoaRouters, listRuntimeApps, logger, registerRuntimeApp, setRuntimeAppKoaRouters, toRuntimeAppSnapshot, updateRuntimeAppCapabilities, updateRuntimeAppStatus } 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, getChildrenApp, getRuntimeApp, getRuntimeAppKoaRouters, getSubscribeList, hasRuntimeAppCapability, listChildrenApps, 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';
@@ -35,7 +35,7 @@ export { useUser } from './hook-use/user.js';
35
35
  export { useObserver, useSubscribe } from './hook-use/subscribe.js';
36
36
  export { createEvent, useEvent } from './hook-use/event.js';
37
37
  export { clearInterval, clearTimeout, listSchedule, pauseSchedule, resumeSchedule, setCron, setInterval, setTimeout } from './api/schedule.js';
38
- export { finishCurrentTrace, getCurrentEvent, getCurrentNext, markEventSendAttempt, markEventSendFailure, markEventSendSuccess, recordEventSendResults, withEventContext, withProcessorTrace } from './hook-event-context.js';
38
+ export { finishCurrentTrace, getCurrentAppName, getCurrentEvent, getCurrentNext, getCurrentPhase, markEventSendAttempt, markEventSendFailure, markEventSendSuccess, recordEventSendResults, withEventContext, withProcessorTrace } from './hook-event-context.js';
39
39
  export { registerAppDir, scheduleCancel, scheduleCancelAll, scheduleCancelByApp, scheduleCron, scheduleInterval, scheduleList, schedulePause, scheduleResume, scheduleTimeout, unregisterAppDir } from './schedule-store.js';
40
40
  export { createEventValue, createSelects, onSelects, onState, unChildren, unState, useState } from './event-utils.js';
41
41
  export { MessageDirect, createDataFormat, format, getMessageIntent, sendToChannel, sendToUser } from './message-api.js';
@@ -0,0 +1,14 @@
1
+ import type { EventFinishedContext, EventStartContext, HttpErrorContext, RuntimeStatusChangeContext } from '../types';
2
+ export declare const dispatchEventStart: (context: EventStartContext) => Promise<void>;
3
+ export declare const dispatchEventFinished: (context: EventFinishedContext) => Promise<void>;
4
+ export declare const dispatchHttpError: (context: HttpErrorContext) => Promise<boolean>;
5
+ export declare const dispatchRuntimeStatusChange: (context: RuntimeStatusChangeContext) => Promise<void>;
6
+ export declare const dispatchAppReady: (appName: string, store: {
7
+ response: any[];
8
+ responseMiddleware: {
9
+ [key: string]: any;
10
+ };
11
+ middleware: any[];
12
+ }) => Promise<void>;
13
+ export declare const dispatchAppDispose: (appName: string, error?: unknown) => Promise<void>;
14
+ export declare const dispatchDisposeAllApps: (error?: unknown) => Promise<void>;
@@ -0,0 +1,99 @@
1
+ import { showErrorModule } from '../core/utils.js';
2
+
3
+ const swallowLifecycleError = (error) => {
4
+ showErrorModule(error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Unknown lifecycle error'));
5
+ };
6
+ const getChildrenApps = () => {
7
+ return Object.values(global.alemonjsCore?.storeChildrenApp ?? {});
8
+ };
9
+ const getChildrenApp = (name) => {
10
+ return global.alemonjsCore?.storeChildrenApp?.[name] ?? null;
11
+ };
12
+ const dispatchEventStart = async (context) => {
13
+ const apps = getChildrenApps();
14
+ for (const app of apps) {
15
+ try {
16
+ await app.cycle?.onEventStart?.(context);
17
+ }
18
+ catch (error) {
19
+ swallowLifecycleError(error);
20
+ }
21
+ }
22
+ };
23
+ const dispatchEventFinished = async (context) => {
24
+ const apps = getChildrenApps();
25
+ for (const app of apps) {
26
+ try {
27
+ await app.cycle?.onEventFinished?.(context);
28
+ }
29
+ catch (error) {
30
+ swallowLifecycleError(error);
31
+ }
32
+ }
33
+ };
34
+ const dispatchHttpError = async (context) => {
35
+ const app = getChildrenApp(context.appName);
36
+ const handler = app?.cycle?.onHttpError;
37
+ if (!handler) {
38
+ return false;
39
+ }
40
+ try {
41
+ const result = await handler(context);
42
+ return result === 'handled';
43
+ }
44
+ catch (error) {
45
+ swallowLifecycleError(error);
46
+ return false;
47
+ }
48
+ };
49
+ const dispatchRuntimeStatusChange = async (context) => {
50
+ const app = getChildrenApp(context.appName);
51
+ const handler = app?.cycle?.onRuntimeStatusChange;
52
+ if (!handler) {
53
+ return;
54
+ }
55
+ try {
56
+ await handler(context);
57
+ }
58
+ catch (error) {
59
+ swallowLifecycleError(error);
60
+ }
61
+ };
62
+ const dispatchAppReady = async (appName, store) => {
63
+ const app = getChildrenApp(appName);
64
+ const handler = app?.cycle?.onReady;
65
+ if (!handler) {
66
+ return;
67
+ }
68
+ try {
69
+ await handler(store);
70
+ }
71
+ catch (error) {
72
+ throw error;
73
+ }
74
+ };
75
+ const dispatchAppDispose = async (appName, error) => {
76
+ const app = getChildrenApp(appName);
77
+ if (!app?.cycle) {
78
+ return;
79
+ }
80
+ try {
81
+ if (app.cycle.onDispose) {
82
+ await app.cycle.onDispose(error);
83
+ }
84
+ if (app.cycle.unMounted) {
85
+ await app.cycle.unMounted(error);
86
+ }
87
+ }
88
+ catch (disposeError) {
89
+ swallowLifecycleError(disposeError);
90
+ }
91
+ };
92
+ const dispatchDisposeAllApps = async (error) => {
93
+ const apps = getChildrenApps();
94
+ for (const app of apps) {
95
+ await dispatchAppDispose(app.name, error);
96
+ }
97
+ };
98
+
99
+ export { dispatchAppDispose, dispatchAppReady, dispatchDisposeAllApps, dispatchEventFinished, dispatchEventStart, dispatchHttpError, dispatchRuntimeStatusChange };
@@ -6,6 +6,7 @@ import { registerExpose } from '../expose.js';
6
6
  import { ResultCode, fileSuffixMiddleware } from '../../core/variable.js';
7
7
  import { registerAppDir, scheduleCancelByApp, unregisterAppDir } from '../schedule-store.js';
8
8
  import module$1 from 'module';
9
+ import { dispatchRuntimeStatusChange, dispatchAppDispose, dispatchAppReady } from '../lifecycle-callbacks.js';
9
10
 
10
11
  const initRequire = () => { };
11
12
  initRequire.resolve = () => '';
@@ -89,21 +90,19 @@ const loadChildren = async (mainPath, appName) => {
89
90
  app = await moduleApp.default.callback();
90
91
  }
91
92
  App.pushCycle(app);
93
+ await dispatchRuntimeStatusChange({
94
+ appName,
95
+ previousStatus: 'discovered',
96
+ status: 'loading'
97
+ });
92
98
  const unMounted = async (e) => {
93
99
  showErrorModule(e);
94
100
  clearRuntimeAppKoaRouters(appName);
95
101
  updateRuntimeAppStatus(appName, 'failed', e);
96
102
  scheduleCancelByApp(appName);
97
103
  unregisterAppDir(appName);
104
+ await dispatchAppDispose(appName, e);
98
105
  App.un();
99
- try {
100
- if (app?.unMounted) {
101
- await app.unMounted(e);
102
- }
103
- }
104
- catch (e) {
105
- showErrorModule(e);
106
- }
107
106
  };
108
107
  try {
109
108
  if (app?.onCreated) {
@@ -133,10 +132,12 @@ const loadChildren = async (mainPath, appName) => {
133
132
  expose: hasExposeCapability
134
133
  });
135
134
  App.on();
135
+ const emptyStore = { response: [], responseMiddleware: {}, middleware: [] };
136
136
  try {
137
137
  if (app?.onMounted) {
138
- await app.onMounted({ response: [], responseMiddleware: {}, middleware: [] });
138
+ await app.onMounted(emptyStore);
139
139
  }
140
+ await dispatchAppReady(appName, emptyStore);
140
141
  updateRuntimeAppStatus(appName, 'ready');
141
142
  }
142
143
  catch (e) {
@@ -202,10 +203,12 @@ const loadChildren = async (mainPath, appName) => {
202
203
  event: resData.length > 0 || Object.keys(resAndMwData).length > 0 || mwData.length > 0
203
204
  });
204
205
  App.on();
206
+ const mountedStore = { response: resData, responseMiddleware: resAndMwData, middleware: mwData };
205
207
  try {
206
208
  if (app?.onMounted) {
207
- await app.onMounted({ response: resData, responseMiddleware: resAndMwData, middleware: mwData });
209
+ await app.onMounted(mountedStore);
208
210
  }
211
+ await dispatchAppReady(appName, mountedStore);
209
212
  updateRuntimeAppStatus(appName, 'ready');
210
213
  }
211
214
  catch (e) {
@@ -187,6 +187,8 @@ export declare class ChildrenApp {
187
187
  un(): void;
188
188
  get value(): import("../types").StoreChildrenApp;
189
189
  }
190
+ export declare const getChildrenApp: (name: string) => import("../types").StoreChildrenApp;
191
+ export declare const listChildrenApps: () => import("../types").StoreChildrenApp[];
190
192
  export declare const ProcessorEventAutoClearMap: Map<any, any>;
191
193
  export declare const ProcessorEventUserAutoClearMap: Map<any, any>;
192
194
  export declare const logger: any;
package/lib/app/store.js CHANGED
@@ -2,6 +2,7 @@ import { SinglyLinkedList } from './SinglyLinkedList.js';
2
2
  import { mkdirSync } from 'node:fs';
3
3
  import log4js from 'log4js';
4
4
  import { disposeExpose } from './expose.js';
5
+ import { dispatchRuntimeStatusChange } from './lifecycle-callbacks.js';
5
6
 
6
7
  const createLogger = () => {
7
8
  if (process.env.BROWSER_ENV === 'browser') {
@@ -157,11 +158,7 @@ const normalizeRuntimeAppError = (error) => {
157
158
  };
158
159
  };
159
160
  const sameRuntimeAppCapabilities = (left, right) => {
160
- return (left.event === right.event &&
161
- left.httpApi === right.httpApi &&
162
- left.web === right.web &&
163
- left.schedule === right.schedule &&
164
- left.expose === right.expose);
161
+ return (left.event === right.event && left.httpApi === right.httpApi && left.web === right.web && left.schedule === right.schedule && left.expose === right.expose);
165
162
  };
166
163
  const sameRuntimeAppError = (left, right) => {
167
164
  if (!left && !right) {
@@ -205,7 +202,7 @@ const registerRuntimeApp = (record) => {
205
202
  updatedAt: now
206
203
  };
207
204
  if (!current || current.status !== record.status) {
208
- logRuntimeAppStatus('info', runtimeApps[record.name]);
205
+ logRuntimeAppStatus(record.status === 'failed' ? 'warn' : 'debug', runtimeApps[record.name]);
209
206
  }
210
207
  return runtimeApps[record.name];
211
208
  };
@@ -216,13 +213,26 @@ const updateRuntimeAppStatus = (name, status, error) => {
216
213
  return;
217
214
  }
218
215
  const normalizedError = normalizeRuntimeAppError(error);
216
+ const previousStatus = current.status;
219
217
  if (current.status === status && sameRuntimeAppError(current.error, normalizedError)) {
220
218
  return current;
221
219
  }
222
220
  current.status = status;
223
221
  current.updatedAt = Date.now();
224
222
  current.error = normalizedError;
225
- logRuntimeAppStatus(status === 'failed' ? 'warn' : 'info', current);
223
+ const level = status === 'failed' ? 'warn' : status === 'disposed' ? 'info' : 'debug';
224
+ logRuntimeAppStatus(level, current);
225
+ void dispatchRuntimeStatusChange({
226
+ appName: name,
227
+ previousStatus,
228
+ status,
229
+ error: normalizedError
230
+ ? {
231
+ message: normalizedError.message,
232
+ time: normalizedError.time
233
+ }
234
+ : undefined
235
+ });
226
236
  return current;
227
237
  };
228
238
  const updateRuntimeAppCapabilities = (name, capabilities) => {
@@ -411,6 +421,13 @@ function mergeFileTree(target, source) {
411
421
  }
412
422
  }
413
423
  }
424
+ const attachRouteAppName = (appName, routes = []) => {
425
+ return routes.map(route => ({
426
+ ...route,
427
+ appName,
428
+ children: route.children ? attachRouteAppName(appName, route.children) : route.children
429
+ }));
430
+ };
414
431
  class MiddlewareTree {
415
432
  #cache = null;
416
433
  #cacheVersion = -1;
@@ -459,10 +476,10 @@ class ResponseRouter {
459
476
  return [];
460
477
  }
461
478
  if (alemonjsCore.storeChildrenApp[key].register?.responseRouter) {
462
- return alemonjsCore.storeChildrenApp[key].register?.responseRouter?.current ?? [];
479
+ return attachRouteAppName(key, alemonjsCore.storeChildrenApp[key].register?.responseRouter?.current ?? []);
463
480
  }
464
481
  if (alemonjsCore.storeChildrenApp[key].register?.response) {
465
- return alemonjsCore.storeChildrenApp[key].register?.response?.current ?? [];
482
+ return attachRouteAppName(key, alemonjsCore.storeChildrenApp[key].register?.response?.current ?? []);
466
483
  }
467
484
  return [];
468
485
  });
@@ -483,10 +500,10 @@ class MiddlewareRouter {
483
500
  return [];
484
501
  }
485
502
  if (alemonjsCore.storeChildrenApp[key].register?.middlewareRouter) {
486
- return alemonjsCore.storeChildrenApp[key].register?.middlewareRouter?.current ?? [];
503
+ return attachRouteAppName(key, alemonjsCore.storeChildrenApp[key].register?.middlewareRouter?.current ?? []);
487
504
  }
488
505
  if (alemonjsCore.storeChildrenApp[key].register?.middleware) {
489
- return alemonjsCore.storeChildrenApp[key].register?.middleware?.current ?? [];
506
+ return attachRouteAppName(key, alemonjsCore.storeChildrenApp[key].register?.middleware?.current ?? []);
490
507
  }
491
508
  return [];
492
509
  });
@@ -635,6 +652,12 @@ class ChildrenApp {
635
652
  return alemonjsCore.storeChildrenApp[this.#name];
636
653
  }
637
654
  }
655
+ const getChildrenApp = (name) => {
656
+ return alemonjsCore.storeChildrenApp[name] ?? null;
657
+ };
658
+ const listChildrenApps = () => {
659
+ return Object.values(alemonjsCore.storeChildrenApp);
660
+ };
638
661
  const ProcessorEventAutoClearMap = new Map();
639
662
  const ProcessorEventUserAutoClearMap = new Map();
640
663
  const logger = new Logger().value;
@@ -648,4 +671,4 @@ process?.on?.('exit', code => {
648
671
  logger.info?.(`[alemonjs][exit] 进程退出,code=${code}`);
649
672
  });
650
673
 
651
- 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 };
674
+ export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, MiddlewareTree, ProcessorEventAutoClearMap, ProcessorEventUserAutoClearMap, Response, ResponseMiddleware, ResponseRouter, ResponseTree, State, StateSubscribe, SubscribeList, bumpStoreVersion, clearRuntimeAppKoaRouters, core, disposeAllRuntimeApps, disposeRuntimeApp, getChildrenApp, getRuntimeApp, getRuntimeAppKoaRouters, getSubscribeList, hasRuntimeAppCapability, listChildrenApps, listRuntimeAppKoaRouters, listRuntimeApps, logger, registerRuntimeApp, setRuntimeAppKoaRouters, toRuntimeAppSnapshot, updateRuntimeAppCapabilities, updateRuntimeAppStatus };
package/lib/client.js CHANGED
@@ -34,20 +34,27 @@ import './app/message-api.js';
34
34
  import './process/platform.js';
35
35
  import './process/module.js';
36
36
  import { createServer } from './server/main.js';
37
+ import { dispatchDisposeAllApps } from './app/lifecycle-callbacks.js';
37
38
 
38
39
  global.__client_loaded = true;
39
40
  let runtimeDisposed = false;
40
- const disposeRuntime = () => {
41
+ const disposeRuntime = async () => {
41
42
  if (runtimeDisposed) {
42
43
  return;
43
44
  }
44
45
  runtimeDisposed = true;
46
+ await dispatchDisposeAllApps();
45
47
  const apps = disposeAllRuntimeApps();
46
48
  apps.forEach(app => {
47
49
  scheduleCancelByApp(app.name);
48
50
  unregisterAppDir(app.name);
49
51
  });
50
52
  };
53
+ const shutdown = async (reason) => {
54
+ logger.info?.(`[alemonjs][${reason}] 收到信号,正在关闭...`);
55
+ await disposeRuntime();
56
+ process.exit(0);
57
+ };
51
58
  const mainServer = () => {
52
59
  const port = process.env.serverPort;
53
60
  if (!port) {
@@ -83,13 +90,11 @@ const mainProcess = () => {
83
90
  });
84
91
  ['SIGINT', 'SIGTERM', 'SIGQUIT', 'disconnect'].forEach(sig => {
85
92
  process?.on?.(sig, () => {
86
- logger.info?.(`[alemonjs][${sig}] 收到信号,正在关闭...`);
87
- disposeRuntime();
88
- setImmediate(() => process.exit(0));
93
+ void shutdown(sig);
89
94
  });
90
95
  });
91
96
  process?.on?.('exit', code => {
92
- disposeRuntime();
97
+ void disposeRuntime();
93
98
  logger.info?.(`[alemonjs][exit] 进程退出,code=${code}`);
94
99
  });
95
100
  process.on('message', msg => {
@@ -100,8 +105,7 @@ const mainProcess = () => {
100
105
  mainServer();
101
106
  }
102
107
  else if (data?.type === 'stop') {
103
- disposeRuntime();
104
- process.exit(0);
108
+ void shutdown('stop');
105
109
  }
106
110
  }
107
111
  catch { }
@@ -1,6 +1,9 @@
1
1
  import type { Package } from '../types';
2
2
  type ConfigValue = {
3
3
  [key: string]: any;
4
+ apps?: string[] | {
5
+ [key: string]: boolean;
6
+ };
4
7
  master_key?: {
5
8
  [key: string]: boolean;
6
9
  } | string[];
@@ -35,9 +38,6 @@ type ConfigValue = {
35
38
  repeated_event_time?: number;
36
39
  repeated_user_time?: number;
37
40
  };
38
- apps?: string[] | {
39
- [key: string]: boolean;
40
- };
41
41
  };
42
42
  type ConfigListener<T extends ConfigValue = ConfigValue> = (value: T) => void;
43
43
  declare class ConfigCore<T extends ConfigValue = ConfigValue> {
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, clearRuntimeAppKoaRouters, core, disposeAllRuntimeApps, disposeRuntimeApp, getRuntimeApp, getRuntimeAppKoaRouters, getSubscribeList, hasRuntimeAppCapability, listRuntimeAppKoaRouters, listRuntimeApps, logger, registerRuntimeApp, setRuntimeAppKoaRouters, toRuntimeAppSnapshot, updateRuntimeAppCapabilities, updateRuntimeAppStatus } 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, getChildrenApp, getRuntimeApp, getRuntimeAppKoaRouters, getSubscribeList, hasRuntimeAppCapability, listChildrenApps, 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';
@@ -42,7 +42,7 @@ export { useUser } from './app/hook-use/user.js';
42
42
  export { useObserver, useSubscribe } from './app/hook-use/subscribe.js';
43
43
  export { createEvent, useEvent } from './app/hook-use/event.js';
44
44
  export { clearInterval, clearTimeout, listSchedule, pauseSchedule, resumeSchedule, setCron, setInterval, setTimeout } from './app/api/schedule.js';
45
- export { finishCurrentTrace, getCurrentEvent, getCurrentNext, markEventSendAttempt, markEventSendFailure, markEventSendSuccess, recordEventSendResults, withEventContext, withProcessorTrace } from './app/hook-event-context.js';
45
+ export { finishCurrentTrace, getCurrentAppName, getCurrentEvent, getCurrentNext, getCurrentPhase, markEventSendAttempt, markEventSendFailure, markEventSendSuccess, recordEventSendResults, withEventContext, withProcessorTrace } from './app/hook-event-context.js';
46
46
  export { registerAppDir, scheduleCancel, scheduleCancelAll, scheduleCancelByApp, scheduleCron, scheduleInterval, scheduleList, schedulePause, scheduleResume, scheduleTimeout, unregisterAppDir } from './app/schedule-store.js';
47
47
  export { createEventValue, createSelects, onSelects, onState, unChildren, unState, useState } from './app/event-utils.js';
48
48
  export { MessageDirect, createDataFormat, format, getMessageIntent, sendToChannel, sendToUser } from './app/message-api.js';
@@ -10,6 +10,7 @@ import { ResultCode } from '../../core/variable.js';
10
10
  import 'yaml';
11
11
  import '../../core/utils.js';
12
12
  import { listRuntimeApps, getRuntimeApp, toRuntimeAppSnapshot, listRuntimeAppKoaRouters, hasRuntimeAppCapability, getRuntimeAppKoaRouters } from '../../app/store.js';
13
+ import { dispatchHttpError } from '../../app/lifecycle-callbacks.js';
13
14
 
14
15
  const initRequire = () => { };
15
16
  initRequire.resolve = () => '';
@@ -104,14 +105,41 @@ const dispatchRegisteredKoaRouters = async (ctx) => {
104
105
  }
105
106
  const routers = getRuntimeAppKoaRouters(item.name);
106
107
  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;
108
+ try {
109
+ const beforeMatched = Array.isArray(ctx.matched) ? ctx.matched.length : 0;
110
+ await koaRouter.routes()(ctx, async () => { });
111
+ const afterMatched = Array.isArray(ctx.matched) ? ctx.matched.length : 0;
112
+ if (afterMatched <= beforeMatched) {
113
+ continue;
114
+ }
115
+ await koaRouter.allowedMethods()(ctx, async () => { });
116
+ return true;
117
+ }
118
+ catch (error) {
119
+ const handled = await dispatchHttpError({
120
+ ctx,
121
+ error,
122
+ appName: item.name,
123
+ path: ctx.path,
124
+ method: ctx.method,
125
+ kind: 'koa-router'
126
+ });
127
+ if (handled) {
128
+ return true;
129
+ }
130
+ logger.warn({
131
+ code: ResultCode.Fail,
132
+ message: `Error request ${ctx.path}:`,
133
+ data: error instanceof Error ? error.message : String(error)
134
+ });
135
+ ctx.status = 500;
136
+ ctx.body = {
137
+ code: 500,
138
+ message: '处理 Koa Router 请求时发生错误。',
139
+ error: error instanceof Error ? error.message : String(error)
140
+ };
141
+ return true;
112
142
  }
113
- await koaRouter.allowedMethods()(ctx, async () => { });
114
- return true;
115
143
  }
116
144
  }
117
145
  return false;
@@ -239,12 +267,22 @@ router.all('app/{*path}', async (ctx) => {
239
267
  await runMiddlewares(middlewares, ctx, handler);
240
268
  }
241
269
  catch (err) {
242
- console.error(`Error handling API request ${ctx.path}`);
270
+ const handled = await dispatchHttpError({
271
+ ctx,
272
+ error: err,
273
+ appName: 'main',
274
+ path: ctx.path,
275
+ method: ctx.method,
276
+ kind: 'api'
277
+ });
278
+ if (handled) {
279
+ return;
280
+ }
243
281
  ctx.status = 500;
244
282
  ctx.body = {
245
283
  code: 500,
246
284
  message: '处理 API 请求时发生错误。',
247
- error: err.message
285
+ error: err instanceof Error ? err.message : String(err)
248
286
  };
249
287
  }
250
288
  return;
@@ -264,11 +302,22 @@ router.all('app/{*path}', async (ctx) => {
264
302
  root = readWebRootConfig(packageRoot);
265
303
  }
266
304
  catch (err) {
305
+ const handled = await dispatchHttpError({
306
+ ctx,
307
+ error: err,
308
+ appName: 'main',
309
+ path: ctx.path,
310
+ method: ctx.method,
311
+ kind: 'web'
312
+ });
313
+ if (handled) {
314
+ return;
315
+ }
267
316
  ctx.status = 500;
268
317
  ctx.body = {
269
318
  code: 500,
270
319
  message: '加载 package.json 时发生错误。',
271
- error: err.message
320
+ error: err instanceof Error ? err.message : String(err)
272
321
  };
273
322
  return;
274
323
  }
@@ -291,6 +340,17 @@ router.all('app/{*path}', async (ctx) => {
291
340
  ctx.status = 200;
292
341
  }
293
342
  catch (err) {
343
+ const handled = await dispatchHttpError({
344
+ ctx,
345
+ error: err,
346
+ appName: 'main',
347
+ path: ctx.path,
348
+ method: ctx.method,
349
+ kind: 'web'
350
+ });
351
+ if (handled) {
352
+ return;
353
+ }
294
354
  if (err?.status === 404) {
295
355
  ctx.status = 404;
296
356
  ctx.body = {
@@ -304,7 +364,7 @@ router.all('app/{*path}', async (ctx) => {
304
364
  ctx.body = {
305
365
  code: 500,
306
366
  message: '加载资源时发生服务器错误。',
307
- error: err.message
367
+ error: err instanceof Error ? err.message : String(err)
308
368
  };
309
369
  }
310
370
  }
@@ -370,16 +430,27 @@ router.all('apps/:app/{*path}', async (ctx) => {
370
430
  await runMiddlewares(middlewares, ctx, handler);
371
431
  }
372
432
  catch (err) {
433
+ const handled = await dispatchHttpError({
434
+ ctx,
435
+ error: err,
436
+ appName,
437
+ path: ctx.path,
438
+ method: ctx.method,
439
+ kind: 'api'
440
+ });
441
+ if (handled) {
442
+ return;
443
+ }
373
444
  logger.warn({
374
445
  code: ResultCode.Fail,
375
446
  message: `Error request ${ctx.path}:`,
376
- data: err?.message ?? ''
447
+ data: err instanceof Error ? err.message : String(err)
377
448
  });
378
449
  ctx.status = 500;
379
450
  ctx.body = {
380
451
  code: 500,
381
452
  message: '处理 API 请求时发生错误。',
382
- error: err.message
453
+ error: err instanceof Error ? err.message : String(err)
383
454
  };
384
455
  }
385
456
  return;
@@ -399,11 +470,22 @@ router.all('apps/:app/{*path}', async (ctx) => {
399
470
  root = readWebRootConfig(packageRoot);
400
471
  }
401
472
  catch (err) {
473
+ const handled = await dispatchHttpError({
474
+ ctx,
475
+ error: err,
476
+ appName,
477
+ path: ctx.path,
478
+ method: ctx.method,
479
+ kind: 'web'
480
+ });
481
+ if (handled) {
482
+ return;
483
+ }
402
484
  ctx.status = 500;
403
485
  ctx.body = {
404
486
  code: 500,
405
487
  message: '加载 package.json 时发生错误。',
406
- error: err.message
488
+ error: err instanceof Error ? err.message : String(err)
407
489
  };
408
490
  return;
409
491
  }
@@ -426,6 +508,17 @@ router.all('apps/:app/{*path}', async (ctx) => {
426
508
  ctx.status = 200;
427
509
  }
428
510
  catch (err) {
511
+ const handled = await dispatchHttpError({
512
+ ctx,
513
+ error: err,
514
+ appName,
515
+ path: ctx.path,
516
+ method: ctx.method,
517
+ kind: 'web'
518
+ });
519
+ if (handled) {
520
+ return;
521
+ }
429
522
  if (err?.status === 404) {
430
523
  ctx.status = 404;
431
524
  ctx.body = {
@@ -438,13 +531,13 @@ router.all('apps/:app/{*path}', async (ctx) => {
438
531
  logger.warn({
439
532
  code: ResultCode.Fail,
440
533
  message: `Error request ${ctx.path}:`,
441
- data: err?.message ?? ''
534
+ data: err instanceof Error ? err.message : String(err)
442
535
  });
443
536
  ctx.status = 500;
444
537
  ctx.body = {
445
538
  code: 500,
446
539
  message: `加载子应用 '${appName}' 资源时发生服务器错误。`,
447
- error: err.message
540
+ error: err instanceof Error ? err.message : String(err)
448
541
  };
449
542
  }
450
543
  }
@@ -1,4 +1,7 @@
1
1
  import { StoreMiddlewareItem, StoreResponseItem } from '../store/res';
2
+ import { Events } from '../event';
3
+ import { EventKeys } from '../event/map';
4
+ import type KoaRouter from 'koa-router';
2
5
  type StroreParam = {
3
6
  response: StoreResponseItem[];
4
7
  responseMiddleware: {
@@ -6,10 +9,57 @@ type StroreParam = {
6
9
  };
7
10
  middleware: StoreMiddlewareItem[];
8
11
  };
12
+ export type EventErrorPhase = 'middleware' | 'response' | 'subscribe' | 'route';
13
+ export type EventTraceReason = 'filtered' | 'completed' | 'consumed' | 'error';
14
+ export type RuntimeLifecycleStatus = 'discovered' | 'loading' | 'ready' | 'failed' | 'disposed';
15
+ export type EventStartContext<T extends EventKeys = EventKeys> = {
16
+ event: Events[T];
17
+ name: T;
18
+ };
19
+ export type EventFinishedContext<T extends EventKeys = EventKeys> = {
20
+ event: Events[T];
21
+ name: T;
22
+ reason: EventTraceReason;
23
+ duration: number;
24
+ hasSendAttempted: boolean;
25
+ hasSendSucceeded: boolean;
26
+ lastSendError: string | null;
27
+ };
28
+ export type EventErrorContext<T extends EventKeys = EventKeys> = {
29
+ event: Events[T];
30
+ error: unknown;
31
+ appName: string;
32
+ phase: EventErrorPhase;
33
+ };
34
+ export type HttpErrorKind = 'api' | 'web' | 'koa-router';
35
+ export type HttpErrorContext = {
36
+ ctx: KoaRouter.RouterContext;
37
+ error: unknown;
38
+ appName: string;
39
+ path: string;
40
+ method: string;
41
+ kind: HttpErrorKind;
42
+ };
43
+ export type RuntimeStatusChangeContext = {
44
+ appName: string;
45
+ previousStatus?: RuntimeLifecycleStatus;
46
+ status: RuntimeLifecycleStatus;
47
+ error?: {
48
+ message: string;
49
+ time: number;
50
+ };
51
+ };
9
52
  export type ChildrenCycle = {
10
53
  onCreated?: () => void | Promise<void>;
11
54
  onMounted?: (store: StroreParam) => void | Promise<void>;
55
+ onReady?: (store: StroreParam) => void | Promise<void>;
56
+ onEventStart?: <T extends EventKeys>(context: EventStartContext<T>) => void | Promise<void>;
57
+ onEventError?: <T extends EventKeys>(context: EventErrorContext<T>) => void | 'continue' | Promise<void | 'continue'>;
58
+ onEventFinished?: <T extends EventKeys>(context: EventFinishedContext<T>) => void | Promise<void>;
59
+ onHttpError?: (context: HttpErrorContext) => void | 'handled' | Promise<void | 'handled'>;
60
+ onRuntimeStatusChange?: (context: RuntimeStatusChangeContext) => void | Promise<void>;
12
61
  unMounted?: (error: any) => void | Promise<void>;
62
+ onDispose?: (error?: unknown) => void | Promise<void>;
13
63
  };
14
64
  export type Next = (...cns: boolean[]) => void;
15
65
  export type EventCycleEnum = 'create' | 'mount' | 'unmount';
@@ -35,6 +35,7 @@ export type OnDataFormatFunc = (...data: DataEnums[]) => DataEnums[];
35
35
  export type OnGroupItem<C = any, T extends EventKeys = EventKeys> = OnResponseValue<C, T> | OnMiddlewareValue<C, T>;
36
36
  export type OnGroupFunc = <C, T extends EventKeys, TFirst extends OnGroupItem<C, T>>(...calls: [TFirst, ...Array<TFirst>]) => TFirst;
37
37
  export type ResponseRoute = {
38
+ appName?: string;
38
39
  platform?: string | string[];
39
40
  regular?: RegExp;
40
41
  prefix?: string;
@@ -2,6 +2,7 @@ import { SinglyLinkedList } from '../../app/SinglyLinkedList';
2
2
  import { EventCycleEnum } from '../cycle';
3
3
  import { EventKeys } from '../event/map';
4
4
  export type SubscribeValue = {
5
+ appName?: string;
5
6
  choose: EventCycleEnum;
6
7
  selects: EventKeys[];
7
8
  keys: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alemonjs",
3
- "version": "2.1.81",
3
+ "version": "2.1.83",
4
4
  "description": "bot script",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",