@eggjs/core 7.0.2-beta.2 → 7.0.2-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/egg.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Timing } from "./utils/timing.js";
4
4
  import { Lifecycle } from "./lifecycle.js";
5
5
  import { EggAppConfig } from "./types.js";
6
6
  import { EggLoader } from "./loader/egg_loader.js";
7
+ import { LoaderFS } from "./loader/loader_fs.js";
7
8
  import { SingletonCreateMethod } from "./singleton.js";
8
9
  import { Application as KoaApplication, Context as KoaContext, MiddlewareFunc as KoaMiddlewareFunc, Next, Request as KoaRequest, Response as KoaResponse } from "@eggjs/koa";
9
10
  import { EggRouter as Router, RegisterOptions, ResourcesController } from "@eggjs/router";
@@ -18,6 +19,18 @@ interface EggCoreOptions {
18
19
  plugins?: any;
19
20
  serverScope?: string;
20
21
  env?: string;
22
+ /** Skip lifecycle hooks, only trigger loadMetadata for manifest generation */
23
+ metadataOnly?: boolean;
24
+ /** Loader-facing filesystem abstraction */
25
+ loaderFS?: LoaderFS;
26
+ /**
27
+ * When true, lifecycle stops after the `configWillLoad` phase.
28
+ * `configDidLoad`, `didLoad`, `willReady`, `didReady`, and `serverDidReady`
29
+ * are skipped. Used for V8 startup snapshot construction — SDKs typically
30
+ * execute during `configDidLoad`, opening connections and starting timers
31
+ * which are not serializable. Analogous to `metadataOnly` mode.
32
+ */
33
+ snapshot?: boolean;
21
34
  }
22
35
  type EggCoreInitOptions = Partial<EggCoreOptions>;
23
36
  declare class Request$1 extends KoaRequest {
@@ -197,6 +210,18 @@ declare class EggCore extends KoaApplication {
197
210
  */
198
211
  beforeClose(fn: Fun, name?: string): void;
199
212
  /**
213
+ * Trigger snapshotWillSerialize lifecycle hooks on all boots in reverse order.
214
+ * Called by the build script before V8 serializes the heap.
215
+ * Cleans up non-serializable resources: file handles, timers, listeners, connections.
216
+ */
217
+ triggerSnapshotWillSerialize(): Promise<void>;
218
+ /**
219
+ * Trigger snapshotDidDeserialize lifecycle hooks on all boots in forward order.
220
+ * Called by the restore entry after V8 deserializes the heap.
221
+ * Restores non-serializable resources and resumes the lifecycle from configDidLoad.
222
+ */
223
+ triggerSnapshotDidDeserialize(): Promise<void>;
224
+ /**
200
225
  * Close all, it will close
201
226
  * - callbacks registered by beforeClose
202
227
  * - emit `close` event
package/dist/egg.js CHANGED
@@ -107,7 +107,8 @@ var EggCore = class EggCore extends KoaApplication {
107
107
  this.lifecycle = new Lifecycle({
108
108
  baseDir: options.baseDir,
109
109
  app: this,
110
- logger: this.console
110
+ logger: this.console,
111
+ snapshot: options.snapshot
111
112
  });
112
113
  this.lifecycle.on("error", (err) => this.emit("error", err));
113
114
  this.lifecycle.on("ready_timeout", (id) => this.emit("ready_timeout", id));
@@ -130,7 +131,9 @@ var EggCore = class EggCore extends KoaApplication {
130
131
  logger: this.console,
131
132
  serverScope: options.serverScope,
132
133
  env: options.env ?? "",
133
- EggCoreClass: EggCore
134
+ EggCoreClass: EggCore,
135
+ metadataOnly: options.metadataOnly,
136
+ loaderFS: options.loaderFS
134
137
  });
135
138
  }
136
139
  get logger() {
@@ -274,6 +277,22 @@ var EggCore = class EggCore extends KoaApplication {
274
277
  this.lifecycle.registerBeforeClose(fn, name);
275
278
  }
276
279
  /**
280
+ * Trigger snapshotWillSerialize lifecycle hooks on all boots in reverse order.
281
+ * Called by the build script before V8 serializes the heap.
282
+ * Cleans up non-serializable resources: file handles, timers, listeners, connections.
283
+ */
284
+ async triggerSnapshotWillSerialize() {
285
+ return this.lifecycle.triggerSnapshotWillSerialize();
286
+ }
287
+ /**
288
+ * Trigger snapshotDidDeserialize lifecycle hooks on all boots in forward order.
289
+ * Called by the restore entry after V8 deserializes the heap.
290
+ * Restores non-serializable resources and resumes the lifecycle from configDidLoad.
291
+ */
292
+ async triggerSnapshotDidDeserialize() {
293
+ return this.lifecycle.triggerSnapshotDidDeserialize();
294
+ }
295
+ /**
277
296
  * Close all, it will close
278
297
  * - callbacks registered by beforeClose
279
298
  * - emit `close` event
package/dist/index.d.ts CHANGED
@@ -3,10 +3,14 @@ import { BaseContextClass } from "./base_context_class.js";
3
3
  import { Timing, TimingItem } from "./utils/timing.js";
4
4
  import { BootImplClass, FunWithFullPath, ILifecycleBoot, Lifecycle, LifecycleOptions } from "./lifecycle.js";
5
5
  import { CustomLoaderConfigItem, EggAppConfig, EggAppInfo, EggPluginInfo } from "./types.js";
6
+ import { ManifestGenerateOptions, ManifestInvalidation, ManifestStore, StartupManifest } from "./loader/manifest.js";
6
7
  import { CaseStyle, CaseStyleFunction, EXPORTS, FULLPATH, FileLoader, FileLoaderFilter, FileLoaderInitializer, FileLoaderOptions, FileLoaderParseItem } from "./loader/file_loader.js";
7
8
  import { ClassLoader, ClassLoaderOptions, ContextLoader, ContextLoaderOptions } from "./loader/context_loader.js";
8
9
  import { EggDirInfo, EggDirInfoType, EggLoader, EggLoaderOptions } from "./loader/egg_loader.js";
10
+ import { ManifestLoaderFS } from "./loader/loader_fs.js";
9
11
  import { Singleton, SingletonCreateMethod, SingletonOptions } from "./singleton.js";
10
12
  import { Context, EGG_LOADER, EggCore, EggCoreInitOptions, EggCoreOptions, KoaApplication, KoaContext, KoaMiddlewareFunc, KoaRequest, KoaResponse, MiddlewareFunc, Next, Request, Response, Router } from "./egg.js";
11
13
  import { SequencifyResult, SequencifyTask, sequencify } from "./utils/sequencify.js";
12
- export { BaseContextClass, BootImplClass, CaseStyle, CaseStyleFunction, ClassLoader, ClassLoaderOptions, Context, ContextLoader, ContextLoaderOptions, CustomLoaderConfigItem, EGG_LOADER, EXPORTS, EggAppConfig, EggAppInfo, EggCore, EggCoreInitOptions, EggCoreOptions, EggDirInfo, EggDirInfoType, EggLoader, EggLoaderOptions, EggPluginInfo, FULLPATH, FileLoader, FileLoaderFilter, FileLoaderInitializer, FileLoaderOptions, FileLoaderParseItem, FunWithFullPath, ILifecycleBoot, KoaApplication, KoaContext, KoaMiddlewareFunc, KoaRequest, KoaResponse, Lifecycle, LifecycleOptions, MiddlewareFunc, Next, Request, Response, Router, SequencifyResult, SequencifyTask, Singleton, SingletonCreateMethod, SingletonOptions, Timing, TimingItem, sequencify, utils };
14
+ import "@eggjs/typings/global";
15
+ export * from "@eggjs/loader-fs";
16
+ export { BaseContextClass, BootImplClass, CaseStyle, CaseStyleFunction, ClassLoader, ClassLoaderOptions, Context, ContextLoader, ContextLoaderOptions, CustomLoaderConfigItem, EGG_LOADER, EXPORTS, EggAppConfig, EggAppInfo, EggCore, EggCoreInitOptions, EggCoreOptions, EggDirInfo, EggDirInfoType, EggLoader, EggLoaderOptions, EggPluginInfo, FULLPATH, FileLoader, FileLoaderFilter, FileLoaderInitializer, FileLoaderOptions, FileLoaderParseItem, FunWithFullPath, ILifecycleBoot, KoaApplication, KoaContext, KoaMiddlewareFunc, KoaRequest, KoaResponse, Lifecycle, LifecycleOptions, ManifestGenerateOptions, ManifestInvalidation, ManifestLoaderFS, ManifestStore, MiddlewareFunc, Next, Request, Response, Router, SequencifyResult, SequencifyTask, Singleton, SingletonCreateMethod, SingletonOptions, StartupManifest, Timing, TimingItem, sequencify, utils };
package/dist/index.js CHANGED
@@ -5,8 +5,13 @@ import { sequencify } from "./utils/sequencify.js";
5
5
  import { Timing } from "./utils/timing.js";
6
6
  import { CaseStyle, EXPORTS, FULLPATH, FileLoader } from "./loader/file_loader.js";
7
7
  import { ClassLoader, ContextLoader } from "./loader/context_loader.js";
8
+ import { ManifestStore } from "./loader/manifest.js";
8
9
  import { EggLoader } from "./loader/egg_loader.js";
9
10
  import { Singleton } from "./singleton.js";
10
11
  import { Context, EGG_LOADER, EggCore, KoaApplication, KoaContext, KoaRequest, KoaResponse, Request, Response, Router } from "./egg.js";
12
+ import { ManifestLoaderFS } from "./loader/loader_fs.js";
13
+ import "@eggjs/typings/global";
11
14
 
12
- export { BaseContextClass, CaseStyle, ClassLoader, Context, ContextLoader, EGG_LOADER, EXPORTS, EggCore, EggLoader, FULLPATH, FileLoader, KoaApplication, KoaContext, KoaRequest, KoaResponse, Lifecycle, Request, Response, Router, Singleton, Timing, sequencify, utils_default as utils };
15
+ export * from "@eggjs/loader-fs"
16
+
17
+ export { BaseContextClass, CaseStyle, ClassLoader, Context, ContextLoader, EGG_LOADER, EXPORTS, EggCore, EggLoader, FULLPATH, FileLoader, KoaApplication, KoaContext, KoaRequest, KoaResponse, Lifecycle, ManifestLoaderFS, ManifestStore, Request, Response, Router, Singleton, Timing, sequencify, utils_default as utils };
@@ -40,12 +40,42 @@ interface ILifecycleBoot {
40
40
  * Do some thing before app close
41
41
  */
42
42
  beforeClose?(): Promise<void>;
43
+ /**
44
+ * Collect metadata for manifest generation (metadataOnly mode).
45
+ * Called instead of configWillLoad/configDidLoad/didLoad/willReady
46
+ * when the application is started with metadataOnly: true.
47
+ */
48
+ loadMetadata?(): Promise<void> | void;
49
+ /**
50
+ * Called before V8 serializes the heap for startup snapshot.
51
+ * Clean up non-serializable resources: close file handles, clear timers,
52
+ * remove process listeners, close network connections.
53
+ * Executed in REVERSE registration order (like beforeClose).
54
+ */
55
+ snapshotWillSerialize?(): Promise<void> | void;
56
+ /**
57
+ * Called after V8 deserializes the heap from a startup snapshot.
58
+ * Restore non-serializable resources: reopen file handles, recreate timers,
59
+ * re-register process listeners, reinitialize connections.
60
+ * Executed in FORWARD registration order (like configWillLoad).
61
+ * After all hooks complete, the normal lifecycle resumes from configDidLoad.
62
+ */
63
+ snapshotDidDeserialize?(): Promise<void> | void;
43
64
  }
44
65
  type BootImplClass<T = ILifecycleBoot> = new (...args: any[]) => T;
45
66
  interface LifecycleOptions {
46
67
  baseDir: string;
47
68
  app: EggCore;
48
69
  logger: EggConsoleLogger;
70
+ /**
71
+ * When true, the lifecycle stops after configWillLoad phase completes.
72
+ * configDidLoad, didLoad, willReady, didReady, and serverDidReady hooks
73
+ * are NOT called. Used for V8 startup snapshot construction — SDKs
74
+ * typically execute during configDidLoad, opening connections and starting
75
+ * timers which are not serializable. The handling is analogous to
76
+ * metadataOnly mode: both short-circuit the lifecycle chain early.
77
+ */
78
+ snapshot?: boolean;
49
79
  }
50
80
  type FunWithFullPath = Fun & {
51
81
  fullPath?: string;
@@ -60,6 +90,18 @@ declare class Lifecycle extends EventEmitter {
60
90
  ready(): Promise<void>;
61
91
  ready(flagOrFunction: ReadyFunctionArg): void;
62
92
  get app(): EggCore;
93
+ /**
94
+ * Whether `close()` has finished. Useful to guard lazy work (logger creation,
95
+ * close-hook registration) that may run after the app/agent was torn down.
96
+ */
97
+ get isClosed(): boolean;
98
+ /**
99
+ * Whether `close()` is currently running (started but not yet finished). A
100
+ * close hook registered during this window would be added after the close
101
+ * callback snapshot is taken and would never run, so `registerBeforeClose()`
102
+ * also refuses registration while closing.
103
+ */
104
+ get isClosing(): boolean;
63
105
  get logger(): EggConsoleLogger;
64
106
  get timing(): Timing;
65
107
  legacyReadyCallback(name: string, opt?: object): (...args: unknown[]) => void;
@@ -70,7 +112,12 @@ declare class Lifecycle extends EventEmitter {
70
112
  */
71
113
  init(): void;
72
114
  registerBeforeStart(scope: Fun, name: string): void;
73
- registerBeforeClose(fn: FunWithFullPath, fullPath?: string): void;
115
+ /**
116
+ * Register a function to run during `close()`. Returns `false` when the
117
+ * registration is refused because the app/agent is already closing or closed
118
+ * (the hook would never run); `true` otherwise.
119
+ */
120
+ registerBeforeClose(fn: FunWithFullPath, fullPath?: string): boolean;
74
121
  close(): Promise<void>;
75
122
  triggerConfigWillLoad(): void;
76
123
  triggerConfigDidLoad(): void;
@@ -78,6 +125,20 @@ declare class Lifecycle extends EventEmitter {
78
125
  triggerWillReady(): void;
79
126
  triggerDidReady(err?: Error): Promise<void>;
80
127
  triggerServerDidReady(): Promise<void>;
128
+ triggerLoadMetadata(): Promise<void>;
129
+ /**
130
+ * Trigger snapshotWillSerialize on all boots in REVERSE order.
131
+ * Called by the build script before V8 serializes the heap.
132
+ */
133
+ triggerSnapshotWillSerialize(): Promise<void>;
134
+ /**
135
+ * Trigger snapshotDidDeserialize on all boots in FORWARD order.
136
+ * Called by the restore entry after V8 deserializes the heap.
137
+ * After all hooks complete, resets the ready state and resumes the normal
138
+ * lifecycle from configDidLoad. The returned promise resolves when the
139
+ * full lifecycle (configDidLoad → didLoad → willReady) has completed.
140
+ */
141
+ triggerSnapshotDidDeserialize(): Promise<void>;
81
142
  }
82
143
  //#endregion
83
144
  export { BootImplClass, FunWithFullPath, ILifecycleBoot, Lifecycle, LifecycleOptions };
package/dist/lifecycle.js CHANGED
@@ -15,6 +15,9 @@ var Lifecycle = class extends EventEmitter {
15
15
  #bootHooks;
16
16
  #boots;
17
17
  #isClosed;
18
+ #isClosing;
19
+ #metadataOnly;
20
+ #snapshotBuilding;
18
21
  #closeFunctionSet;
19
22
  loadReady;
20
23
  bootReady;
@@ -29,6 +32,9 @@ var Lifecycle = class extends EventEmitter {
29
32
  this.#boots = [];
30
33
  this.#closeFunctionSet = /* @__PURE__ */ new Set();
31
34
  this.#isClosed = false;
35
+ this.#isClosing = false;
36
+ this.#metadataOnly = false;
37
+ this.#snapshotBuilding = false;
32
38
  this.#init = false;
33
39
  this.timing.start(`${this.options.app.type} Start`);
34
40
  const eggReadyTimeoutEnv = Number.parseInt(process.env.EGG_READY_TIMEOUT_ENV || "10000");
@@ -41,7 +47,7 @@ var Lifecycle = class extends EventEmitter {
41
47
  this.logger.warn("[egg/core/lifecycle:ready_timeout] %s seconds later %s was still unable to finish.", this.readyTimeout / 1e3, id);
42
48
  });
43
49
  this.ready((err) => {
44
- this.triggerDidReady(err);
50
+ if (!this.#metadataOnly && !this.options.snapshot) this.triggerDidReady(err);
45
51
  debug("app ready");
46
52
  this.timing.end(`${this.options.app.type} Start`);
47
53
  });
@@ -53,6 +59,22 @@ var Lifecycle = class extends EventEmitter {
53
59
  get app() {
54
60
  return this.options.app;
55
61
  }
62
+ /**
63
+ * Whether `close()` has finished. Useful to guard lazy work (logger creation,
64
+ * close-hook registration) that may run after the app/agent was torn down.
65
+ */
66
+ get isClosed() {
67
+ return this.#isClosed;
68
+ }
69
+ /**
70
+ * Whether `close()` is currently running (started but not yet finished). A
71
+ * close hook registered during this window would be added after the close
72
+ * callback snapshot is taken and would never run, so `registerBeforeClose()`
73
+ * also refuses registration while closing.
74
+ */
75
+ get isClosing() {
76
+ return this.#isClosing;
77
+ }
56
78
  get logger() {
57
79
  return this.options.logger;
58
80
  }
@@ -117,20 +139,35 @@ var Lifecycle = class extends EventEmitter {
117
139
  scopeFullName: name
118
140
  });
119
141
  }
142
+ /**
143
+ * Register a function to run during `close()`. Returns `false` when the
144
+ * registration is refused because the app/agent is already closing or closed
145
+ * (the hook would never run); `true` otherwise.
146
+ */
120
147
  registerBeforeClose(fn, fullPath) {
121
148
  assert(typeof fn === "function", "argument should be function");
122
- assert(this.#isClosed === false, "app has been closed");
149
+ if (this.#isClosing || this.#isClosed) {
150
+ debug("%s skip registerBeforeClose at %o, app is closing or has been closed", this.app.type, fullPath);
151
+ return false;
152
+ }
123
153
  if (fullPath) fn.fullPath = fullPath;
124
154
  this.#closeFunctionSet.add(fn);
125
155
  debug("%s register beforeClose at %o, count: %d", this.app.type, fullPath, this.#closeFunctionSet.size);
156
+ return true;
126
157
  }
127
158
  async close() {
128
- const closeFns = Array.from(this.#closeFunctionSet);
129
- debug("%s start trigger %d beforeClose functions", this.app.type, closeFns.length);
130
- for (const fn of closeFns.reverse()) {
131
- debug("%s trigger beforeClose at %o", this.app.type, fn.fullPath);
132
- await utils_default.callFn(fn);
133
- this.#closeFunctionSet.delete(fn);
159
+ this.#isClosing = true;
160
+ if (this.#metadataOnly || this.#snapshotBuilding) {
161
+ debug("%s skip beforeClose functions in early-exit lifecycle mode", this.app.type);
162
+ this.#closeFunctionSet.clear();
163
+ } else {
164
+ const closeFns = Array.from(this.#closeFunctionSet);
165
+ debug("%s start trigger %d beforeClose functions", this.app.type, closeFns.length);
166
+ for (const fn of closeFns.reverse()) {
167
+ debug("%s trigger beforeClose at %o", this.app.type, fn.fullPath);
168
+ await utils_default.callFn(fn);
169
+ this.#closeFunctionSet.delete(fn);
170
+ }
134
171
  }
135
172
  this.app.emit("close");
136
173
  this.removeAllListeners();
@@ -145,6 +182,12 @@ var Lifecycle = class extends EventEmitter {
145
182
  boot.configWillLoad();
146
183
  }
147
184
  debug("trigger configWillLoad end");
185
+ if (this.options.snapshot) {
186
+ debug("snapshot mode: stopping after configWillLoad, skipping configDidLoad and later phases");
187
+ this.#snapshotBuilding = true;
188
+ this.loadReady.start();
189
+ return;
190
+ }
148
191
  this.triggerConfigDidLoad();
149
192
  }
150
193
  triggerConfigDidLoad() {
@@ -221,6 +264,85 @@ var Lifecycle = class extends EventEmitter {
221
264
  debug("trigger serverDidReady end");
222
265
  })();
223
266
  }
267
+ async triggerLoadMetadata() {
268
+ this.#metadataOnly = true;
269
+ debug("trigger loadMetadata start");
270
+ let firstError;
271
+ for (const boot of this.#boots) if (typeof boot.loadMetadata === "function") {
272
+ debug("trigger loadMetadata at %o", boot.fullPath);
273
+ try {
274
+ await boot.loadMetadata();
275
+ } catch (err) {
276
+ const error = err instanceof Error ? err : new Error(String(err));
277
+ if (!firstError) firstError = error;
278
+ debug("trigger loadMetadata error at %o, error: %s", boot.fullPath, error);
279
+ this.emit("error", error);
280
+ }
281
+ }
282
+ debug("trigger loadMetadata end");
283
+ this.ready(firstError ?? true);
284
+ }
285
+ /**
286
+ * Trigger snapshotWillSerialize on all boots in REVERSE order.
287
+ * Called by the build script before V8 serializes the heap.
288
+ */
289
+ async triggerSnapshotWillSerialize() {
290
+ if (!this.options.snapshot) throw new Error("triggerSnapshotWillSerialize() can only be called on a snapshot-mode lifecycle");
291
+ debug("trigger snapshotWillSerialize start");
292
+ const boots = [...this.#boots].reverse();
293
+ for (const boot of boots) {
294
+ if (typeof boot.snapshotWillSerialize !== "function") continue;
295
+ const fullPath = boot.fullPath ?? "unknown";
296
+ debug("trigger snapshotWillSerialize at %o", fullPath);
297
+ const timingKey = `Snapshot Will Serialize in ${utils_default.getResolvedFilename(fullPath, this.app.baseDir)}`;
298
+ this.timing.start(timingKey);
299
+ try {
300
+ await utils_default.callFn(boot.snapshotWillSerialize.bind(boot));
301
+ } catch (err) {
302
+ debug("trigger snapshotWillSerialize error at %o, error: %s", fullPath, err);
303
+ this.timing.end(timingKey);
304
+ throw err;
305
+ }
306
+ this.timing.end(timingKey);
307
+ }
308
+ debug("trigger snapshotWillSerialize end");
309
+ }
310
+ /**
311
+ * Trigger snapshotDidDeserialize on all boots in FORWARD order.
312
+ * Called by the restore entry after V8 deserializes the heap.
313
+ * After all hooks complete, resets the ready state and resumes the normal
314
+ * lifecycle from configDidLoad. The returned promise resolves when the
315
+ * full lifecycle (configDidLoad → didLoad → willReady) has completed.
316
+ */
317
+ async triggerSnapshotDidDeserialize() {
318
+ if (!this.options.snapshot) throw new Error("triggerSnapshotDidDeserialize() can only be called on a snapshot-mode lifecycle");
319
+ debug("trigger snapshotDidDeserialize start");
320
+ for (const boot of this.#boots) {
321
+ if (typeof boot.snapshotDidDeserialize !== "function") continue;
322
+ const fullPath = boot.fullPath ?? "unknown";
323
+ debug("trigger snapshotDidDeserialize at %o", fullPath);
324
+ const timingKey = `Snapshot Did Deserialize in ${utils_default.getResolvedFilename(fullPath, this.app.baseDir)}`;
325
+ this.timing.start(timingKey);
326
+ try {
327
+ await utils_default.callFn(boot.snapshotDidDeserialize.bind(boot));
328
+ } catch (err) {
329
+ debug("trigger snapshotDidDeserialize error at %o, error: %s", fullPath, err);
330
+ this.timing.end(timingKey);
331
+ throw err;
332
+ }
333
+ this.timing.end(timingKey);
334
+ }
335
+ debug("trigger snapshotDidDeserialize end");
336
+ this.#snapshotBuilding = false;
337
+ this.#readyObject = new Ready();
338
+ this.#initReady();
339
+ this.ready((err) => {
340
+ this.triggerDidReady(err);
341
+ debug("app ready after snapshot deserialize");
342
+ });
343
+ this.triggerConfigDidLoad();
344
+ await this.ready();
345
+ }
224
346
  #initReady() {
225
347
  debug("loadReady init");
226
348
  this.loadReady = new Ready$1({
@@ -232,6 +354,7 @@ var Lifecycle = class extends EventEmitter {
232
354
  debug("loadReady end, err: %o", err);
233
355
  debug("trigger didLoad end");
234
356
  if (err) this.ready(err);
357
+ else if (this.#snapshotBuilding) this.ready(true);
235
358
  else this.triggerWillReady();
236
359
  });
237
360
  debug("bootReady init");
@@ -1,10 +1,12 @@
1
1
  import { Timing } from "../utils/timing.js";
2
2
  import { Lifecycle } from "../lifecycle.js";
3
3
  import { EggAppConfig, EggAppInfo, EggPluginInfo } from "../types.js";
4
+ import { ManifestStore, StartupManifest } from "./manifest.js";
4
5
  import { FileLoader, FileLoaderOptions } from "./file_loader.js";
5
6
  import { ContextLoader, ContextLoaderOptions } from "./context_loader.js";
6
7
  import { EggCore } from "../egg.js";
7
8
  import { Logger } from "egg-logger";
9
+ import { LoaderFS } from "@eggjs/loader-fs";
8
10
 
9
11
  //#region src/loader/egg_loader.d.ts
10
12
  interface EggLoaderOptions {
@@ -21,6 +23,10 @@ interface EggLoaderOptions {
21
23
  serverScope?: string;
22
24
  /** custom plugins */
23
25
  plugins?: Record<string, EggPluginInfo>;
26
+ /** Skip lifecycle hooks, only trigger loadMetadata for manifest generation */
27
+ metadataOnly?: boolean;
28
+ /** Loader-facing filesystem abstraction */
29
+ loaderFS?: LoaderFS;
24
30
  }
25
31
  type EggDirInfoType = "app" | "plugin" | "framework";
26
32
  interface EggDirInfo {
@@ -36,7 +42,11 @@ declare class EggLoader {
36
42
  readonly serverEnv: string;
37
43
  readonly serverScope: string;
38
44
  readonly appInfo: EggAppInfo;
45
+ readonly outDir?: string;
39
46
  dirs?: EggDirInfo[];
47
+ /** Startup manifest — loaded from cache or collecting for generation */
48
+ readonly manifest: ManifestStore;
49
+ readonly loaderFS: LoaderFS;
40
50
  /**
41
51
  * @class
42
52
  * @param {Object} options - options
@@ -368,6 +378,11 @@ declare class EggLoader {
368
378
  get ContextLoader(): typeof ContextLoader;
369
379
  getTypeFiles(filename: string): string[];
370
380
  resolveModule(filepath: string): string | undefined;
381
+ /**
382
+ * Generate startup manifest from collected data.
383
+ * Should be called after all loading phases complete.
384
+ */
385
+ generateManifest(): StartupManifest;
371
386
  }
372
387
  //#endregion
373
388
  export { EggDirInfo, EggDirInfoType, EggLoader, EggLoaderOptions };