@moostjs/vite 0.6.36 → 0.6.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +132 -58
- package/dist/index.mjs +132 -58
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -249,19 +249,63 @@ function _define_property(obj, key, value) {
|
|
|
249
249
|
* A reference to a shared LogsStorage instance that persists
|
|
250
250
|
* across init cycles in dev mode.
|
|
251
251
|
*/ let logsStorage;
|
|
252
|
+
/** Whether {@link patchMoostHandlerLogging} already installed its wrappers in this process. */ let installed = false;
|
|
252
253
|
/**
|
|
253
|
-
*
|
|
254
|
+
* The slot {@link captureMoostInit} fills: receives every `init()` promise the
|
|
255
|
+
* wrapper below produces. Unset in prod/test, where nothing consumes a capture.
|
|
256
|
+
*/ let onInitCaptured;
|
|
257
|
+
/**
|
|
258
|
+
* Hands every `Moost.prototype.init()` promise to `onCapture`, letting a boot
|
|
259
|
+
* await the app's **initialization** instead of only its module evaluation.
|
|
260
|
+
*
|
|
261
|
+
* The documented entry shape is `app.adapter(http).listen(port)` followed by an
|
|
262
|
+
* **un-awaited** `app.init()`: the HTTP middleware is therefore captured *before*
|
|
263
|
+
* init finishes, and nothing else observes a rejecting init — a bind error, a DI
|
|
264
|
+
* audit error or a throwing `@MoostInit` hook would leave the dev server serving
|
|
265
|
+
* a half-booted app (the routes bound before the failure answer 200, the rest
|
|
266
|
+
* fall through to the SPA fallback) instead of the plugin's 502. That is also
|
|
267
|
+
* why the plugin's `bootError` gate runs BEFORE the captured middleware, and why
|
|
268
|
+
* the SSR fallback checks it too: `listen()` ran first and left both a middleware
|
|
269
|
+
* and the local-fetch hook behind.
|
|
270
|
+
*
|
|
271
|
+
* Patches the plugin's **own** `moost` import, exactly like the handler logging
|
|
272
|
+
* does: in dev the runtime is externalized, so the plugin and the app share one
|
|
273
|
+
* native `moost` instance (the premise the DI eject in `restart-cleanup` rests on
|
|
274
|
+
* too, and what `ssrExternalCheck` warns about when it stops holding). Unlike the
|
|
275
|
+
* adapters there is nothing to re-establish per reload — a static import is never
|
|
276
|
+
* re-evaluated — and unlike them it must NOT be pulled through the SSR module
|
|
277
|
+
* runner: importing `moost` there registers it as the runner's first module and
|
|
278
|
+
* makes subsequent hot updates re-evaluate the entry only, serving every one of
|
|
279
|
+
* its dependencies (controllers included) from the stale cache.
|
|
280
|
+
*
|
|
281
|
+
* There is exactly ONE `init` wrapper per process (installed by
|
|
282
|
+
* {@link patchMoostHandlerLogging}) and this only fills its slot, so the newest
|
|
283
|
+
* plugin instance wins: Vite re-imports the config on every config-file
|
|
284
|
+
* `server.restart()`, re-running the plugin factory, and a per-instance wrapper
|
|
285
|
+
* would stack on the previous one and keep the dead instance's scope alive.
|
|
286
|
+
*
|
|
287
|
+
* @param onCapture receives each captured init promise (already marked handled).
|
|
288
|
+
*/ function captureMoostInit(onCapture) {
|
|
289
|
+
onInitCaptured = onCapture;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Patches Moost’s .init() and .logMappedHandler() methods — once per process —
|
|
293
|
+
* so that, during dev:
|
|
254
294
|
* - Only newly added handlers are logged normally.
|
|
255
295
|
* - Removed handlers are logged/stroked out to indicate removal.
|
|
256
296
|
* - Prevents repeated logs from flooding the console across multiple hot reloads.
|
|
297
|
+
* - Every init() promise reaches the {@link captureMoostInit} slot, if filled.
|
|
257
298
|
*/ function patchMoostHandlerLogging() {
|
|
299
|
+
if (installed) return;
|
|
300
|
+
installed = true;
|
|
258
301
|
const origInit = moost.Moost.prototype.init;
|
|
302
|
+
const origLogMappedHandler = moost.Moost.prototype.logMappedHandler;
|
|
259
303
|
/**
|
|
260
|
-
*
|
|
304
|
+
* The logging body of the patched Moost.init:
|
|
261
305
|
* 1. Start a new recording cycle in LogsStorage.
|
|
262
306
|
* 2. Call the real .init().
|
|
263
307
|
* 3. End the recording, logging any removed handlers.
|
|
264
|
-
*/
|
|
308
|
+
*/ const initWithLogging = async function() {
|
|
265
309
|
if (!logsStorage) logsStorage = new LogsStorage();
|
|
266
310
|
logsStorage.startRecording();
|
|
267
311
|
await origInit.call(this);
|
|
@@ -269,7 +313,14 @@ function _define_property(obj, key, value) {
|
|
|
269
313
|
origLogMappedHandler.call(this, item.eventName, item.classConstructor, item.method, true, "❌ ");
|
|
270
314
|
});
|
|
271
315
|
};
|
|
272
|
-
|
|
316
|
+
moost.Moost.prototype.init = function init() {
|
|
317
|
+
const promise = initWithLogging.call(this);
|
|
318
|
+
if (onInitCaptured) {
|
|
319
|
+
promise.catch(() => {});
|
|
320
|
+
onInitCaptured(promise);
|
|
321
|
+
}
|
|
322
|
+
return promise;
|
|
323
|
+
};
|
|
273
324
|
/**
|
|
274
325
|
* Monkey-patch Moost.logMappedHandler:
|
|
275
326
|
* If we haven’t seen this particular route+class+method combination
|
|
@@ -289,7 +340,7 @@ function _define_property(obj, key, value) {
|
|
|
289
340
|
/**
|
|
290
341
|
* Clean up Moost’s global containers and optionally remove specific instances from the registry.
|
|
291
342
|
*
|
|
292
|
-
* Every instance actually removed from
|
|
343
|
+
* Every instance actually removed from the registry is **disposed** before the
|
|
293
344
|
* caches are dropped: its `@MoostDispose` hooks (or `Symbol.asyncDispose` /
|
|
294
345
|
* `Symbol.dispose`) are awaited, so a singleton owning a connection, consumer,
|
|
295
346
|
* timer or file handle releases it instead of leaking one copy per reload. An
|
|
@@ -298,46 +349,25 @@ function _define_property(obj, key, value) {
|
|
|
298
349
|
*
|
|
299
350
|
* @param {Set<string>} [cleanupInstances] A set of module IDs to remove from the registry.
|
|
300
351
|
* @returns the instances that were ejected (and therefore disposed)
|
|
301
|
-
*/ async function moostRestartCleanup(
|
|
352
|
+
*/ async function moostRestartCleanup(onEject, cleanupInstances) {
|
|
302
353
|
const logger = getLogger();
|
|
303
354
|
const infact = (0, moost.getMoostInfact)();
|
|
304
|
-
const { registry
|
|
305
|
-
const registries = [registry, ...Object.values(scopes)];
|
|
355
|
+
const { registry } = infact;
|
|
306
356
|
infact._cleanup();
|
|
307
357
|
const mate = (0, moost.getMoostMate)();
|
|
308
|
-
/** Instances removed from
|
|
358
|
+
/** Instances removed from the registry by this run — disposed below, before the caches drop. */ const ejected = [];
|
|
309
359
|
if (cleanupInstances) {
|
|
310
|
-
for (const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
ejected.push(instance);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
for (const key of Object.getOwnPropertySymbols(reg)) {
|
|
321
|
-
const instance = reg[key];
|
|
322
|
-
scanParams(instance, (type) => {
|
|
323
|
-
if ((type === moost.Moost || type instanceof moost.Moost || type.prototype instanceof moost.Moost) && (!onEject || onEject(instance, type))) {
|
|
324
|
-
delete reg[key];
|
|
325
|
-
ejected.push(instance);
|
|
326
|
-
logger.debug(`✖️ Ejecting "${constructorName(instance)}" (depends on re-instantiated "Moost")`);
|
|
327
|
-
return true;
|
|
328
|
-
}
|
|
329
|
-
for (const adapter of adapters) if (adapter.compare(type) && (!onEject || onEject(instance, type))) {
|
|
330
|
-
delete reg[key];
|
|
331
|
-
ejected.push(instance);
|
|
332
|
-
logger.debug(`✖️ Ejecting "${constructorName(instance)}" (depends on re-instantiated "${adapter.constructor.name}")`);
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
});
|
|
360
|
+
for (const key of Object.getOwnPropertySymbols(registry)) {
|
|
361
|
+
const instance = registry[key];
|
|
362
|
+
const viteId = mate.read(instance)?.__vite_id;
|
|
363
|
+
if (viteId && cleanupInstances.has(viteId)) {
|
|
364
|
+
logger.debug(`🔃 Replacing "${constructorName(instance)}"`);
|
|
365
|
+
delete registry[key];
|
|
366
|
+
ejected.push(instance);
|
|
336
367
|
}
|
|
337
|
-
clearDependantRegistry(reg, onEject, ejected);
|
|
338
368
|
}
|
|
369
|
+
clearDependantRegistry(registry, onEject, ejected);
|
|
339
370
|
infact.registry = registry;
|
|
340
|
-
infact.scopes = scopes;
|
|
341
371
|
}
|
|
342
372
|
await disposeEjected(ejected);
|
|
343
373
|
(0, moost.getMoostMate)()._cleanup();
|
|
@@ -354,19 +384,21 @@ function _define_property(obj, key, value) {
|
|
|
354
384
|
for (const instance of disposed) logger.debug(`♻️ Disposed "${constructorName(instance)}"`);
|
|
355
385
|
for (const e of errors) logger.warn(`⚠️ Dispose hook "${constructorName(e.instance)}.${String(e.method)}" failed — the reload continues`);
|
|
356
386
|
}
|
|
357
|
-
|
|
387
|
+
/**
|
|
388
|
+
* Ejects, to a fixed point, every instance whose constructor dependency is not
|
|
389
|
+
* in the registry: an instance ejected in one pass ejects its consumers in the
|
|
390
|
+
* next (Moost → Database → Repository → Worker loses the whole chain). An
|
|
391
|
+
* `onEject` veto keeps the instance present, and with it its consumers.
|
|
392
|
+
*/ function clearDependantRegistry(registry, onEject, ejected) {
|
|
358
393
|
const logger = getLogger();
|
|
359
|
-
const objSet = /* @__PURE__ */ new Set();
|
|
360
394
|
let somethingIsDeleted = true;
|
|
361
395
|
while (somethingIsDeleted) {
|
|
362
396
|
somethingIsDeleted = false;
|
|
363
|
-
|
|
397
|
+
const keys = Object.getOwnPropertySymbols(registry);
|
|
398
|
+
const present = new Set(keys.map((key) => registry[key].constructor));
|
|
399
|
+
for (const key of keys) {
|
|
364
400
|
const instance = registry[key];
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
for (const key of Object.getOwnPropertySymbols(registry)) {
|
|
368
|
-
const instance = registry[key];
|
|
369
|
-
if (checkAndEject(instance, objSet, onEject, registry, key, logger)) {
|
|
401
|
+
if (checkAndEject(instance, present, onEject, registry, key, logger)) {
|
|
370
402
|
ejected.push(instance);
|
|
371
403
|
somethingIsDeleted = true;
|
|
372
404
|
}
|
|
@@ -378,7 +410,7 @@ function checkAndEject(instance, objSet, onEject, registry, key, logger) {
|
|
|
378
410
|
scanParams(instance, (type) => {
|
|
379
411
|
if (!objSet.has(type) && (!onEject || onEject(instance, type))) {
|
|
380
412
|
delete registry[key];
|
|
381
|
-
logger.debug(`✖️ Ejecting "${constructorName(instance)}" (depends on "${type.name}"
|
|
413
|
+
logger.debug(`✖️ Ejecting "${constructorName(instance)}" (depends on "${type.name}", rebuilt by this reload)`);
|
|
382
414
|
ejected = true;
|
|
383
415
|
return true;
|
|
384
416
|
}
|
|
@@ -643,6 +675,11 @@ function moostVite(options) {
|
|
|
643
675
|
*/ let bootGeneration = 0;
|
|
644
676
|
let bootingGeneration = 0;
|
|
645
677
|
/** Whether the HTTP listen() patch has ever captured a middleware (i.e. this is an HTTP app). */ let httpCaptured = false;
|
|
678
|
+
/**
|
|
679
|
+
* The `app.init()` promises the current boot handed us (see `captureMoostInit`) —
|
|
680
|
+
* several when one entry boots several apps, none for a boot that never calls
|
|
681
|
+
* `init()`. Reset by `beginBoot()`, consumed by `settleBootInit()`.
|
|
682
|
+
*/ let bootInitPromises = [];
|
|
646
683
|
/** Module IDs awaiting DI cleanup — consumed in runReload; see ejectApp. */ let pendingCleanup = null;
|
|
647
684
|
/** In middleware mode: maps req → next() for the onNoMatch callback */ const pendingNextMap = /* @__PURE__ */ new WeakMap();
|
|
648
685
|
const adapters = isTest ? [] : [
|
|
@@ -712,6 +749,41 @@ function moostVite(options) {
|
|
|
712
749
|
reloadRequired = true;
|
|
713
750
|
};
|
|
714
751
|
patchMoostHandlerLogging();
|
|
752
|
+
if (!isTest && !isProd) captureMoostInit((promise) => {
|
|
753
|
+
if (bootingGeneration !== bootGeneration) {
|
|
754
|
+
logger.error(`⚠️ A stale Moost boot called init() (boot generation ${bootingGeneration}, latest ${bootGeneration}) — an HMR reload race; a follow-up reload will replace it.`);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
bootInitPromises.push(promise);
|
|
758
|
+
});
|
|
759
|
+
/** Marks the boot about to run as authoritative for the current generation. */ const beginBoot = () => {
|
|
760
|
+
bootingGeneration = bootGeneration;
|
|
761
|
+
bootInitPromises = [];
|
|
762
|
+
};
|
|
763
|
+
/**
|
|
764
|
+
* Finishes a boot: awaits the `init()` promise(s) the entry that just executed
|
|
765
|
+
* handed us, so the boot is only "done" once the app is fully initialized —
|
|
766
|
+
* every controller bound, every `@MoostInit` hook run (see `captureMoostInit`).
|
|
767
|
+
* Owns `bootError`: cleared on success, set and logged when an init rejected;
|
|
768
|
+
* returns whether the boot succeeded.
|
|
769
|
+
*/ const settleBootInit = async () => {
|
|
770
|
+
const initPromises = bootInitPromises;
|
|
771
|
+
bootInitPromises = [];
|
|
772
|
+
try {
|
|
773
|
+
await Promise.all(initPromises);
|
|
774
|
+
bootError = null;
|
|
775
|
+
return true;
|
|
776
|
+
} catch (error) {
|
|
777
|
+
bootError = error;
|
|
778
|
+
logger.error(`✖️ Moost app init failed: ${error.message}`);
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
/** Answers a request while `bootError` is set (see the gate in configureServer). */ const answerBootError = (res) => {
|
|
783
|
+
res.statusCode = 502;
|
|
784
|
+
res.setHeader("Content-Type", "text/plain");
|
|
785
|
+
res.end(`Moost app failed to load: ${bootError.message}`);
|
|
786
|
+
};
|
|
715
787
|
/** Set by the middleware-mode `config` hook: the consumer externalized the runtime themselves. */ let runtimeExternalized = false;
|
|
716
788
|
let resolvedConfig;
|
|
717
789
|
const pluginConfig = {
|
|
@@ -875,7 +947,7 @@ function moostVite(options) {
|
|
|
875
947
|
reloadRequired = false;
|
|
876
948
|
reloadPromise = null;
|
|
877
949
|
pendingCleanup = null;
|
|
878
|
-
|
|
950
|
+
beginBoot();
|
|
879
951
|
const runReload = () => {
|
|
880
952
|
reloadRequired = false;
|
|
881
953
|
console.log();
|
|
@@ -883,16 +955,15 @@ function moostVite(options) {
|
|
|
883
955
|
console.log();
|
|
884
956
|
return (async () => {
|
|
885
957
|
try {
|
|
886
|
-
|
|
958
|
+
beginBoot();
|
|
887
959
|
const cleanupInstances = pendingCleanup ?? void 0;
|
|
888
960
|
pendingCleanup = null;
|
|
889
|
-
await moostRestartCleanup(
|
|
961
|
+
await moostRestartCleanup(options.onEject, cleanupInstances);
|
|
890
962
|
for (const adapter of adapters) if (adapter.detected) await adapter.init();
|
|
891
963
|
const entryModule = await getEntryModule(server.environments.ssr.moduleGraph);
|
|
892
964
|
if (entryModule?.transformResult) server.environments.ssr.moduleGraph.invalidateModule(entryModule);
|
|
893
965
|
await ssrImport(options.entry);
|
|
894
|
-
|
|
895
|
-
if (httpCaptured && !moostMiddleware) logger.error(`⚠️ Moost app reloaded but no HTTP middleware was captured — the entry did not re-run listen().`);
|
|
966
|
+
if (await settleBootInit() && httpCaptured && !moostMiddleware) logger.error(`⚠️ Moost app reloaded but no HTTP middleware was captured — the entry did not re-run listen().`);
|
|
896
967
|
} catch (error) {
|
|
897
968
|
bootError = error;
|
|
898
969
|
logger.error(`✖️ Failed to reload Moost App: ${error.message}`);
|
|
@@ -909,11 +980,16 @@ function moostVite(options) {
|
|
|
909
980
|
}
|
|
910
981
|
};
|
|
911
982
|
for (const adapter of adapters) adapter.ssrLoadModule = ssrImport;
|
|
912
|
-
await moostRestartCleanup(
|
|
983
|
+
await moostRestartCleanup(options.onEject);
|
|
913
984
|
await ssrImport(options.entry);
|
|
985
|
+
await settleBootInit();
|
|
914
986
|
server.middlewares.use(async (req, res, next) => {
|
|
915
987
|
await drainReload();
|
|
916
988
|
if (options.middleware && prefixes && !matchesPrefix(req.url || "", prefixes)) return next();
|
|
989
|
+
if (bootError) {
|
|
990
|
+
answerBootError(res);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
917
993
|
if (moostMiddleware) {
|
|
918
994
|
if (options.middleware) {
|
|
919
995
|
pendingNextMap.set(req, next);
|
|
@@ -922,12 +998,6 @@ function moostVite(options) {
|
|
|
922
998
|
}
|
|
923
999
|
return moostMiddleware(req, res);
|
|
924
1000
|
}
|
|
925
|
-
if (bootError) {
|
|
926
|
-
res.statusCode = 502;
|
|
927
|
-
res.setHeader("Content-Type", "text/plain");
|
|
928
|
-
res.end(`Moost app failed to load: ${bootError.message}`);
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
1001
|
next();
|
|
932
1002
|
});
|
|
933
1003
|
},
|
|
@@ -967,6 +1037,10 @@ function moostVite(options) {
|
|
|
967
1037
|
const url = req.originalUrl || req.url || "/";
|
|
968
1038
|
try {
|
|
969
1039
|
await drainReload();
|
|
1040
|
+
if (bootError) {
|
|
1041
|
+
answerBootError(res);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
970
1044
|
let template = await fs.readFile((0, node_path.resolve)(server.config.root, "index.html"), "utf8");
|
|
971
1045
|
template = await server.transformIndexHtml(url, template);
|
|
972
1046
|
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
package/dist/index.mjs
CHANGED
|
@@ -220,19 +220,63 @@ function _define_property(obj, key, value) {
|
|
|
220
220
|
* A reference to a shared LogsStorage instance that persists
|
|
221
221
|
* across init cycles in dev mode.
|
|
222
222
|
*/ let logsStorage;
|
|
223
|
+
/** Whether {@link patchMoostHandlerLogging} already installed its wrappers in this process. */ let installed = false;
|
|
223
224
|
/**
|
|
224
|
-
*
|
|
225
|
+
* The slot {@link captureMoostInit} fills: receives every `init()` promise the
|
|
226
|
+
* wrapper below produces. Unset in prod/test, where nothing consumes a capture.
|
|
227
|
+
*/ let onInitCaptured;
|
|
228
|
+
/**
|
|
229
|
+
* Hands every `Moost.prototype.init()` promise to `onCapture`, letting a boot
|
|
230
|
+
* await the app's **initialization** instead of only its module evaluation.
|
|
231
|
+
*
|
|
232
|
+
* The documented entry shape is `app.adapter(http).listen(port)` followed by an
|
|
233
|
+
* **un-awaited** `app.init()`: the HTTP middleware is therefore captured *before*
|
|
234
|
+
* init finishes, and nothing else observes a rejecting init — a bind error, a DI
|
|
235
|
+
* audit error or a throwing `@MoostInit` hook would leave the dev server serving
|
|
236
|
+
* a half-booted app (the routes bound before the failure answer 200, the rest
|
|
237
|
+
* fall through to the SPA fallback) instead of the plugin's 502. That is also
|
|
238
|
+
* why the plugin's `bootError` gate runs BEFORE the captured middleware, and why
|
|
239
|
+
* the SSR fallback checks it too: `listen()` ran first and left both a middleware
|
|
240
|
+
* and the local-fetch hook behind.
|
|
241
|
+
*
|
|
242
|
+
* Patches the plugin's **own** `moost` import, exactly like the handler logging
|
|
243
|
+
* does: in dev the runtime is externalized, so the plugin and the app share one
|
|
244
|
+
* native `moost` instance (the premise the DI eject in `restart-cleanup` rests on
|
|
245
|
+
* too, and what `ssrExternalCheck` warns about when it stops holding). Unlike the
|
|
246
|
+
* adapters there is nothing to re-establish per reload — a static import is never
|
|
247
|
+
* re-evaluated — and unlike them it must NOT be pulled through the SSR module
|
|
248
|
+
* runner: importing `moost` there registers it as the runner's first module and
|
|
249
|
+
* makes subsequent hot updates re-evaluate the entry only, serving every one of
|
|
250
|
+
* its dependencies (controllers included) from the stale cache.
|
|
251
|
+
*
|
|
252
|
+
* There is exactly ONE `init` wrapper per process (installed by
|
|
253
|
+
* {@link patchMoostHandlerLogging}) and this only fills its slot, so the newest
|
|
254
|
+
* plugin instance wins: Vite re-imports the config on every config-file
|
|
255
|
+
* `server.restart()`, re-running the plugin factory, and a per-instance wrapper
|
|
256
|
+
* would stack on the previous one and keep the dead instance's scope alive.
|
|
257
|
+
*
|
|
258
|
+
* @param onCapture receives each captured init promise (already marked handled).
|
|
259
|
+
*/ function captureMoostInit(onCapture) {
|
|
260
|
+
onInitCaptured = onCapture;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Patches Moost’s .init() and .logMappedHandler() methods — once per process —
|
|
264
|
+
* so that, during dev:
|
|
225
265
|
* - Only newly added handlers are logged normally.
|
|
226
266
|
* - Removed handlers are logged/stroked out to indicate removal.
|
|
227
267
|
* - Prevents repeated logs from flooding the console across multiple hot reloads.
|
|
268
|
+
* - Every init() promise reaches the {@link captureMoostInit} slot, if filled.
|
|
228
269
|
*/ function patchMoostHandlerLogging() {
|
|
270
|
+
if (installed) return;
|
|
271
|
+
installed = true;
|
|
229
272
|
const origInit = Moost.prototype.init;
|
|
273
|
+
const origLogMappedHandler = Moost.prototype.logMappedHandler;
|
|
230
274
|
/**
|
|
231
|
-
*
|
|
275
|
+
* The logging body of the patched Moost.init:
|
|
232
276
|
* 1. Start a new recording cycle in LogsStorage.
|
|
233
277
|
* 2. Call the real .init().
|
|
234
278
|
* 3. End the recording, logging any removed handlers.
|
|
235
|
-
*/
|
|
279
|
+
*/ const initWithLogging = async function() {
|
|
236
280
|
if (!logsStorage) logsStorage = new LogsStorage();
|
|
237
281
|
logsStorage.startRecording();
|
|
238
282
|
await origInit.call(this);
|
|
@@ -240,7 +284,14 @@ function _define_property(obj, key, value) {
|
|
|
240
284
|
origLogMappedHandler.call(this, item.eventName, item.classConstructor, item.method, true, "❌ ");
|
|
241
285
|
});
|
|
242
286
|
};
|
|
243
|
-
|
|
287
|
+
Moost.prototype.init = function init() {
|
|
288
|
+
const promise = initWithLogging.call(this);
|
|
289
|
+
if (onInitCaptured) {
|
|
290
|
+
promise.catch(() => {});
|
|
291
|
+
onInitCaptured(promise);
|
|
292
|
+
}
|
|
293
|
+
return promise;
|
|
294
|
+
};
|
|
244
295
|
/**
|
|
245
296
|
* Monkey-patch Moost.logMappedHandler:
|
|
246
297
|
* If we haven’t seen this particular route+class+method combination
|
|
@@ -260,7 +311,7 @@ function _define_property(obj, key, value) {
|
|
|
260
311
|
/**
|
|
261
312
|
* Clean up Moost’s global containers and optionally remove specific instances from the registry.
|
|
262
313
|
*
|
|
263
|
-
* Every instance actually removed from
|
|
314
|
+
* Every instance actually removed from the registry is **disposed** before the
|
|
264
315
|
* caches are dropped: its `@MoostDispose` hooks (or `Symbol.asyncDispose` /
|
|
265
316
|
* `Symbol.dispose`) are awaited, so a singleton owning a connection, consumer,
|
|
266
317
|
* timer or file handle releases it instead of leaking one copy per reload. An
|
|
@@ -269,46 +320,25 @@ function _define_property(obj, key, value) {
|
|
|
269
320
|
*
|
|
270
321
|
* @param {Set<string>} [cleanupInstances] A set of module IDs to remove from the registry.
|
|
271
322
|
* @returns the instances that were ejected (and therefore disposed)
|
|
272
|
-
*/ async function moostRestartCleanup(
|
|
323
|
+
*/ async function moostRestartCleanup(onEject, cleanupInstances) {
|
|
273
324
|
const logger = getLogger();
|
|
274
325
|
const infact = getMoostInfact();
|
|
275
|
-
const { registry
|
|
276
|
-
const registries = [registry, ...Object.values(scopes)];
|
|
326
|
+
const { registry } = infact;
|
|
277
327
|
infact._cleanup();
|
|
278
328
|
const mate = getMoostMate();
|
|
279
|
-
/** Instances removed from
|
|
329
|
+
/** Instances removed from the registry by this run — disposed below, before the caches drop. */ const ejected = [];
|
|
280
330
|
if (cleanupInstances) {
|
|
281
|
-
for (const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
ejected.push(instance);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
for (const key of Object.getOwnPropertySymbols(reg)) {
|
|
292
|
-
const instance = reg[key];
|
|
293
|
-
scanParams(instance, (type) => {
|
|
294
|
-
if ((type === Moost || type instanceof Moost || type.prototype instanceof Moost) && (!onEject || onEject(instance, type))) {
|
|
295
|
-
delete reg[key];
|
|
296
|
-
ejected.push(instance);
|
|
297
|
-
logger.debug(`✖️ Ejecting "${constructorName(instance)}" (depends on re-instantiated "Moost")`);
|
|
298
|
-
return true;
|
|
299
|
-
}
|
|
300
|
-
for (const adapter of adapters) if (adapter.compare(type) && (!onEject || onEject(instance, type))) {
|
|
301
|
-
delete reg[key];
|
|
302
|
-
ejected.push(instance);
|
|
303
|
-
logger.debug(`✖️ Ejecting "${constructorName(instance)}" (depends on re-instantiated "${adapter.constructor.name}")`);
|
|
304
|
-
return true;
|
|
305
|
-
}
|
|
306
|
-
});
|
|
331
|
+
for (const key of Object.getOwnPropertySymbols(registry)) {
|
|
332
|
+
const instance = registry[key];
|
|
333
|
+
const viteId = mate.read(instance)?.__vite_id;
|
|
334
|
+
if (viteId && cleanupInstances.has(viteId)) {
|
|
335
|
+
logger.debug(`🔃 Replacing "${constructorName(instance)}"`);
|
|
336
|
+
delete registry[key];
|
|
337
|
+
ejected.push(instance);
|
|
307
338
|
}
|
|
308
|
-
clearDependantRegistry(reg, onEject, ejected);
|
|
309
339
|
}
|
|
340
|
+
clearDependantRegistry(registry, onEject, ejected);
|
|
310
341
|
infact.registry = registry;
|
|
311
|
-
infact.scopes = scopes;
|
|
312
342
|
}
|
|
313
343
|
await disposeEjected(ejected);
|
|
314
344
|
getMoostMate()._cleanup();
|
|
@@ -325,19 +355,21 @@ function _define_property(obj, key, value) {
|
|
|
325
355
|
for (const instance of disposed) logger.debug(`♻️ Disposed "${constructorName(instance)}"`);
|
|
326
356
|
for (const e of errors) logger.warn(`⚠️ Dispose hook "${constructorName(e.instance)}.${String(e.method)}" failed — the reload continues`);
|
|
327
357
|
}
|
|
328
|
-
|
|
358
|
+
/**
|
|
359
|
+
* Ejects, to a fixed point, every instance whose constructor dependency is not
|
|
360
|
+
* in the registry: an instance ejected in one pass ejects its consumers in the
|
|
361
|
+
* next (Moost → Database → Repository → Worker loses the whole chain). An
|
|
362
|
+
* `onEject` veto keeps the instance present, and with it its consumers.
|
|
363
|
+
*/ function clearDependantRegistry(registry, onEject, ejected) {
|
|
329
364
|
const logger = getLogger();
|
|
330
|
-
const objSet = /* @__PURE__ */ new Set();
|
|
331
365
|
let somethingIsDeleted = true;
|
|
332
366
|
while (somethingIsDeleted) {
|
|
333
367
|
somethingIsDeleted = false;
|
|
334
|
-
|
|
368
|
+
const keys = Object.getOwnPropertySymbols(registry);
|
|
369
|
+
const present = new Set(keys.map((key) => registry[key].constructor));
|
|
370
|
+
for (const key of keys) {
|
|
335
371
|
const instance = registry[key];
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
for (const key of Object.getOwnPropertySymbols(registry)) {
|
|
339
|
-
const instance = registry[key];
|
|
340
|
-
if (checkAndEject(instance, objSet, onEject, registry, key, logger)) {
|
|
372
|
+
if (checkAndEject(instance, present, onEject, registry, key, logger)) {
|
|
341
373
|
ejected.push(instance);
|
|
342
374
|
somethingIsDeleted = true;
|
|
343
375
|
}
|
|
@@ -349,7 +381,7 @@ function checkAndEject(instance, objSet, onEject, registry, key, logger) {
|
|
|
349
381
|
scanParams(instance, (type) => {
|
|
350
382
|
if (!objSet.has(type) && (!onEject || onEject(instance, type))) {
|
|
351
383
|
delete registry[key];
|
|
352
|
-
logger.debug(`✖️ Ejecting "${constructorName(instance)}" (depends on "${type.name}"
|
|
384
|
+
logger.debug(`✖️ Ejecting "${constructorName(instance)}" (depends on "${type.name}", rebuilt by this reload)`);
|
|
353
385
|
ejected = true;
|
|
354
386
|
return true;
|
|
355
387
|
}
|
|
@@ -612,6 +644,11 @@ function moostVite(options) {
|
|
|
612
644
|
*/ let bootGeneration = 0;
|
|
613
645
|
let bootingGeneration = 0;
|
|
614
646
|
/** Whether the HTTP listen() patch has ever captured a middleware (i.e. this is an HTTP app). */ let httpCaptured = false;
|
|
647
|
+
/**
|
|
648
|
+
* The `app.init()` promises the current boot handed us (see `captureMoostInit`) —
|
|
649
|
+
* several when one entry boots several apps, none for a boot that never calls
|
|
650
|
+
* `init()`. Reset by `beginBoot()`, consumed by `settleBootInit()`.
|
|
651
|
+
*/ let bootInitPromises = [];
|
|
615
652
|
/** Module IDs awaiting DI cleanup — consumed in runReload; see ejectApp. */ let pendingCleanup = null;
|
|
616
653
|
/** In middleware mode: maps req → next() for the onNoMatch callback */ const pendingNextMap = /* @__PURE__ */ new WeakMap();
|
|
617
654
|
const adapters = [
|
|
@@ -681,6 +718,41 @@ function moostVite(options) {
|
|
|
681
718
|
reloadRequired = true;
|
|
682
719
|
};
|
|
683
720
|
patchMoostHandlerLogging();
|
|
721
|
+
captureMoostInit((promise) => {
|
|
722
|
+
if (bootingGeneration !== bootGeneration) {
|
|
723
|
+
logger.error(`⚠️ A stale Moost boot called init() (boot generation ${bootingGeneration}, latest ${bootGeneration}) — an HMR reload race; a follow-up reload will replace it.`);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
bootInitPromises.push(promise);
|
|
727
|
+
});
|
|
728
|
+
/** Marks the boot about to run as authoritative for the current generation. */ const beginBoot = () => {
|
|
729
|
+
bootingGeneration = bootGeneration;
|
|
730
|
+
bootInitPromises = [];
|
|
731
|
+
};
|
|
732
|
+
/**
|
|
733
|
+
* Finishes a boot: awaits the `init()` promise(s) the entry that just executed
|
|
734
|
+
* handed us, so the boot is only "done" once the app is fully initialized —
|
|
735
|
+
* every controller bound, every `@MoostInit` hook run (see `captureMoostInit`).
|
|
736
|
+
* Owns `bootError`: cleared on success, set and logged when an init rejected;
|
|
737
|
+
* returns whether the boot succeeded.
|
|
738
|
+
*/ const settleBootInit = async () => {
|
|
739
|
+
const initPromises = bootInitPromises;
|
|
740
|
+
bootInitPromises = [];
|
|
741
|
+
try {
|
|
742
|
+
await Promise.all(initPromises);
|
|
743
|
+
bootError = null;
|
|
744
|
+
return true;
|
|
745
|
+
} catch (error) {
|
|
746
|
+
bootError = error;
|
|
747
|
+
logger.error(`✖️ Moost app init failed: ${error.message}`);
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
/** Answers a request while `bootError` is set (see the gate in configureServer). */ const answerBootError = (res) => {
|
|
752
|
+
res.statusCode = 502;
|
|
753
|
+
res.setHeader("Content-Type", "text/plain");
|
|
754
|
+
res.end(`Moost app failed to load: ${bootError.message}`);
|
|
755
|
+
};
|
|
684
756
|
/** Set by the middleware-mode `config` hook: the consumer externalized the runtime themselves. */ let runtimeExternalized = false;
|
|
685
757
|
let resolvedConfig;
|
|
686
758
|
return [{
|
|
@@ -844,7 +916,7 @@ function moostVite(options) {
|
|
|
844
916
|
reloadRequired = false;
|
|
845
917
|
reloadPromise = null;
|
|
846
918
|
pendingCleanup = null;
|
|
847
|
-
|
|
919
|
+
beginBoot();
|
|
848
920
|
const runReload = () => {
|
|
849
921
|
reloadRequired = false;
|
|
850
922
|
console.log();
|
|
@@ -852,16 +924,15 @@ function moostVite(options) {
|
|
|
852
924
|
console.log();
|
|
853
925
|
return (async () => {
|
|
854
926
|
try {
|
|
855
|
-
|
|
927
|
+
beginBoot();
|
|
856
928
|
const cleanupInstances = pendingCleanup ?? void 0;
|
|
857
929
|
pendingCleanup = null;
|
|
858
|
-
await moostRestartCleanup(
|
|
930
|
+
await moostRestartCleanup(options.onEject, cleanupInstances);
|
|
859
931
|
for (const adapter of adapters) if (adapter.detected) await adapter.init();
|
|
860
932
|
const entryModule = await getEntryModule(server.environments.ssr.moduleGraph);
|
|
861
933
|
if (entryModule?.transformResult) server.environments.ssr.moduleGraph.invalidateModule(entryModule);
|
|
862
934
|
await ssrImport(options.entry);
|
|
863
|
-
|
|
864
|
-
if (httpCaptured && !moostMiddleware) logger.error(`⚠️ Moost app reloaded but no HTTP middleware was captured — the entry did not re-run listen().`);
|
|
935
|
+
if (await settleBootInit() && httpCaptured && !moostMiddleware) logger.error(`⚠️ Moost app reloaded but no HTTP middleware was captured — the entry did not re-run listen().`);
|
|
865
936
|
} catch (error) {
|
|
866
937
|
bootError = error;
|
|
867
938
|
logger.error(`✖️ Failed to reload Moost App: ${error.message}`);
|
|
@@ -878,11 +949,16 @@ function moostVite(options) {
|
|
|
878
949
|
}
|
|
879
950
|
};
|
|
880
951
|
for (const adapter of adapters) adapter.ssrLoadModule = ssrImport;
|
|
881
|
-
await moostRestartCleanup(
|
|
952
|
+
await moostRestartCleanup(options.onEject);
|
|
882
953
|
await ssrImport(options.entry);
|
|
954
|
+
await settleBootInit();
|
|
883
955
|
server.middlewares.use(async (req, res, next) => {
|
|
884
956
|
await drainReload();
|
|
885
957
|
if (options.middleware && prefixes && !matchesPrefix(req.url || "", prefixes)) return next();
|
|
958
|
+
if (bootError) {
|
|
959
|
+
answerBootError(res);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
886
962
|
if (moostMiddleware) {
|
|
887
963
|
if (options.middleware) {
|
|
888
964
|
pendingNextMap.set(req, next);
|
|
@@ -891,12 +967,6 @@ function moostVite(options) {
|
|
|
891
967
|
}
|
|
892
968
|
return moostMiddleware(req, res);
|
|
893
969
|
}
|
|
894
|
-
if (bootError) {
|
|
895
|
-
res.statusCode = 502;
|
|
896
|
-
res.setHeader("Content-Type", "text/plain");
|
|
897
|
-
res.end(`Moost app failed to load: ${bootError.message}`);
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
970
|
next();
|
|
901
971
|
});
|
|
902
972
|
},
|
|
@@ -928,6 +998,10 @@ function moostVite(options) {
|
|
|
928
998
|
const url = req.originalUrl || req.url || "/";
|
|
929
999
|
try {
|
|
930
1000
|
await drainReload();
|
|
1001
|
+
if (bootError) {
|
|
1002
|
+
answerBootError(res);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
931
1005
|
let template = await fs.readFile(resolve(server.config.root, "index.html"), "utf8");
|
|
932
1006
|
template = await server.transformIndexHtml(url, template);
|
|
933
1007
|
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moostjs/vite",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.38",
|
|
4
4
|
"description": "Vite Dev plugin for moostjs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composables",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"sirv": "^3.0.0",
|
|
60
60
|
"vite": "^8.0.0",
|
|
61
|
-
"@moostjs/event-http": "^0.6.
|
|
62
|
-
"moost": "^0.6.
|
|
61
|
+
"@moostjs/event-http": "^0.6.38",
|
|
62
|
+
"moost": "^0.6.38"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"sirv": {
|