@decohere/flux 0.0.1-dev.94 → 0.0.1-dev.96
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/commonjs/src/flux.module.js +3 -4
- package/commonjs/src/hot-reload-server.js +36 -21
- package/commonjs/src/source-module-loader.js +5 -5
- package/es6/src/flux.module.js +3 -4
- package/es6/src/hot-reload-server.js +36 -21
- package/es6/src/source-module-loader.js +7 -7
- package/package.json +6 -6
- package/types/src/hot-reload-server.d.ts +10 -0
|
@@ -8,7 +8,7 @@ Object.defineProperty(exports, "FluxModule", {
|
|
|
8
8
|
return FluxModule;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const
|
|
11
|
+
const _forge = require("@decohere/forge");
|
|
12
12
|
const _cli = require("@decohere/nexus/cli");
|
|
13
13
|
const _deferredscriptrunner = require("./deferred-script-runner.js");
|
|
14
14
|
const _fluxcommand = require("./flux.command.js");
|
|
@@ -27,8 +27,7 @@ class FluxModule extends _cli.ModuleManifest {
|
|
|
27
27
|
builder.registerSingleton(_deferredscriptrunner.DeferredScriptRunner);
|
|
28
28
|
}
|
|
29
29
|
createWorkspace(options) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
30
|
+
// Same workspace as `flux.run` / `flux.serve` and forge (#516).
|
|
31
|
+
return (0, _forge.createWorkspace)(options);
|
|
33
32
|
}
|
|
34
33
|
}
|
|
@@ -160,8 +160,10 @@ class HotReloadServer extends _nodeevents.EventEmitter {
|
|
|
160
160
|
try {
|
|
161
161
|
const mod = await this.loader.importEntry(entryPath);
|
|
162
162
|
state.module = mod?.default;
|
|
163
|
+
state.failed = false;
|
|
163
164
|
} catch (error) {
|
|
164
165
|
state.module = undefined;
|
|
166
|
+
state.failed = true;
|
|
165
167
|
this.logger.error("There was an error booting the entry point. Your application may not function as expected", error);
|
|
166
168
|
}
|
|
167
169
|
}
|
|
@@ -309,6 +311,7 @@ class HotReloadServer extends _nodeevents.EventEmitter {
|
|
|
309
311
|
this.loader.setSourceFile(fullPath, project);
|
|
310
312
|
this.moduleCache.set(fullPath, null);
|
|
311
313
|
this.reloadIfAffected(fullPath);
|
|
314
|
+
this.retryFailedEntries(fullPath);
|
|
312
315
|
}
|
|
313
316
|
return;
|
|
314
317
|
}
|
|
@@ -316,7 +319,25 @@ class HotReloadServer extends _nodeevents.EventEmitter {
|
|
|
316
319
|
if (!project.isSourceFile(file)) return;
|
|
317
320
|
this.loader.invalidateTranspile(fullPath);
|
|
318
321
|
this.reloadIfAffected(fullPath);
|
|
322
|
+
this.retryFailedEntries(fullPath);
|
|
319
323
|
};
|
|
324
|
+
/**
|
|
325
|
+
* A failed boot (e.g. importing a file that doesn't exist yet) may have recorded no
|
|
326
|
+
* dependency edges, so the graph can't tell whether this save fixes it. Any source
|
|
327
|
+
* add or change retries failed entries (#512).
|
|
328
|
+
*/ retryFailedEntries(fullPath) {
|
|
329
|
+
const failed = [
|
|
330
|
+
...this.entries.values()
|
|
331
|
+
].filter((entry)=>entry.failed);
|
|
332
|
+
if (failed.length === 0) return;
|
|
333
|
+
for (const entry of failed){
|
|
334
|
+
// New URL version: Node caches the failed load and would replay the same error.
|
|
335
|
+
this.invalidateModuleCaches(entry.path);
|
|
336
|
+
this.pendingEntryReboots.add(entry.path);
|
|
337
|
+
}
|
|
338
|
+
this.pendingTriggers.add(fullPath);
|
|
339
|
+
this.scheduleReboot();
|
|
340
|
+
}
|
|
320
341
|
reloadIfAffected(fullPath) {
|
|
321
342
|
const deps = this.getDependencyGraph(fullPath);
|
|
322
343
|
const modules = [
|
|
@@ -345,30 +366,24 @@ class HotReloadServer extends _nodeevents.EventEmitter {
|
|
|
345
366
|
return true;
|
|
346
367
|
}
|
|
347
368
|
// ── Dependency graph ─────────────────────────────────────────────────────
|
|
348
|
-
|
|
369
|
+
/**
|
|
370
|
+
* Every module that imports `moduleId`, directly or transitively. Visits each module
|
|
371
|
+
* once: import graphs share modules (diamonds) and may legally contain cycles (#515).
|
|
372
|
+
*/ getDependencyGraph(moduleId) {
|
|
349
373
|
if (this.isSelf(moduleId)) return [];
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
...parentModules
|
|
361
|
-
]);
|
|
362
|
-
subDeps = [
|
|
363
|
-
...subDeps,
|
|
364
|
-
...subs
|
|
365
|
-
];
|
|
374
|
+
const found = new Set();
|
|
375
|
+
const queue = [
|
|
376
|
+
moduleId
|
|
377
|
+
];
|
|
378
|
+
for(let next = queue.shift(); next != null; next = queue.shift()){
|
|
379
|
+
for (const importer of this.dependencies.get(next) ?? []){
|
|
380
|
+
if (found.has(importer)) continue;
|
|
381
|
+
found.add(importer);
|
|
382
|
+
if (this.loader.hasSourceFile(importer) && !this.isSelf(importer)) queue.push(importer);
|
|
383
|
+
}
|
|
366
384
|
}
|
|
367
385
|
return [
|
|
368
|
-
...
|
|
369
|
-
...moduleDeps,
|
|
370
|
-
...subDeps
|
|
371
|
-
])
|
|
386
|
+
...found
|
|
372
387
|
];
|
|
373
388
|
}
|
|
374
389
|
trackDependency(filePath, parentPath) {
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "SourceModuleLoader", {
|
|
|
10
10
|
});
|
|
11
11
|
const _nodemodule = require("node:module");
|
|
12
12
|
const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
|
|
13
|
+
const _nodeurl = require("node:url");
|
|
13
14
|
const _decorators = require("@decohere/crucible/decorators");
|
|
14
15
|
const _forge = require("@decohere/forge");
|
|
15
16
|
const _mesh = require("@decohere/mesh");
|
|
@@ -102,10 +103,7 @@ class SourceModuleLoader {
|
|
|
102
103
|
this.disposed = false;
|
|
103
104
|
this.transpileCache.clear();
|
|
104
105
|
this.buildConfig = options.buildConfig;
|
|
105
|
-
this.workspace = options.workspace ?? await
|
|
106
|
-
..._forge.WorkspaceDefaults,
|
|
107
|
-
rootDir: process.cwd()
|
|
108
|
-
});
|
|
106
|
+
this.workspace = options.workspace ?? await (0, _forge.createWorkspace)();
|
|
109
107
|
this.versionFileUrl = options.versionFileUrl ?? ((fileUrl)=>fileUrl);
|
|
110
108
|
this.onResolved = options.onResolved;
|
|
111
109
|
this.fileInfoMap = await this.buildFileInfoMap(this.buildConfig);
|
|
@@ -182,7 +180,9 @@ class SourceModuleLoader {
|
|
|
182
180
|
}
|
|
183
181
|
loadTs(url) {
|
|
184
182
|
if (!url.startsWith("file:")) return null;
|
|
185
|
-
|
|
183
|
+
// fileURLToPath, not URL.pathname: decodes %20 and gives Windows paths, so files
|
|
184
|
+
// whose path has a space still match Mesh's list (#511).
|
|
185
|
+
const cleanPath = (0, _nodeurl.fileURLToPath)(url);
|
|
186
186
|
const fileInfo = this.fileInfoMap.get(cleanPath);
|
|
187
187
|
if (fileInfo == null) return null;
|
|
188
188
|
const cached = this.transpileCache.get(cleanPath);
|
package/es6/src/flux.module.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createWorkspace } from "@decohere/forge";
|
|
2
2
|
import { ModuleManifest } from "@decohere/nexus/cli";
|
|
3
3
|
import { DeferredScriptRunner } from "./deferred-script-runner.js";
|
|
4
4
|
import { FluxCommand } from "./flux.command.js";
|
|
@@ -17,8 +17,7 @@ export class FluxModule extends ModuleManifest {
|
|
|
17
17
|
builder.registerSingleton(DeferredScriptRunner);
|
|
18
18
|
}
|
|
19
19
|
createWorkspace(options) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
20
|
+
// Same workspace as `flux.run` / `flux.serve` and forge (#516).
|
|
21
|
+
return createWorkspace(options);
|
|
23
22
|
}
|
|
24
23
|
}
|
|
@@ -128,8 +128,10 @@ export class HotReloadServer extends EventEmitter {
|
|
|
128
128
|
try {
|
|
129
129
|
const mod = await this.loader.importEntry(entryPath);
|
|
130
130
|
state.module = mod?.default;
|
|
131
|
+
state.failed = false;
|
|
131
132
|
} catch (error) {
|
|
132
133
|
state.module = undefined;
|
|
134
|
+
state.failed = true;
|
|
133
135
|
this.logger.error("There was an error booting the entry point. Your application may not function as expected", error);
|
|
134
136
|
}
|
|
135
137
|
}
|
|
@@ -277,6 +279,7 @@ export class HotReloadServer extends EventEmitter {
|
|
|
277
279
|
this.loader.setSourceFile(fullPath, project);
|
|
278
280
|
this.moduleCache.set(fullPath, null);
|
|
279
281
|
this.reloadIfAffected(fullPath);
|
|
282
|
+
this.retryFailedEntries(fullPath);
|
|
280
283
|
}
|
|
281
284
|
return;
|
|
282
285
|
}
|
|
@@ -284,7 +287,25 @@ export class HotReloadServer extends EventEmitter {
|
|
|
284
287
|
if (!project.isSourceFile(file)) return;
|
|
285
288
|
this.loader.invalidateTranspile(fullPath);
|
|
286
289
|
this.reloadIfAffected(fullPath);
|
|
290
|
+
this.retryFailedEntries(fullPath);
|
|
287
291
|
};
|
|
292
|
+
/**
|
|
293
|
+
* A failed boot (e.g. importing a file that doesn't exist yet) may have recorded no
|
|
294
|
+
* dependency edges, so the graph can't tell whether this save fixes it. Any source
|
|
295
|
+
* add or change retries failed entries (#512).
|
|
296
|
+
*/ retryFailedEntries(fullPath) {
|
|
297
|
+
const failed = [
|
|
298
|
+
...this.entries.values()
|
|
299
|
+
].filter((entry)=>entry.failed);
|
|
300
|
+
if (failed.length === 0) return;
|
|
301
|
+
for (const entry of failed){
|
|
302
|
+
// New URL version: Node caches the failed load and would replay the same error.
|
|
303
|
+
this.invalidateModuleCaches(entry.path);
|
|
304
|
+
this.pendingEntryReboots.add(entry.path);
|
|
305
|
+
}
|
|
306
|
+
this.pendingTriggers.add(fullPath);
|
|
307
|
+
this.scheduleReboot();
|
|
308
|
+
}
|
|
288
309
|
reloadIfAffected(fullPath) {
|
|
289
310
|
const deps = this.getDependencyGraph(fullPath);
|
|
290
311
|
const modules = [
|
|
@@ -313,30 +334,24 @@ export class HotReloadServer extends EventEmitter {
|
|
|
313
334
|
return true;
|
|
314
335
|
}
|
|
315
336
|
// ── Dependency graph ─────────────────────────────────────────────────────
|
|
316
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Every module that imports `moduleId`, directly or transitively. Visits each module
|
|
339
|
+
* once: import graphs share modules (diamonds) and may legally contain cycles (#515).
|
|
340
|
+
*/ getDependencyGraph(moduleId) {
|
|
317
341
|
if (this.isSelf(moduleId)) return [];
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
...parentModules
|
|
329
|
-
]);
|
|
330
|
-
subDeps = [
|
|
331
|
-
...subDeps,
|
|
332
|
-
...subs
|
|
333
|
-
];
|
|
342
|
+
const found = new Set();
|
|
343
|
+
const queue = [
|
|
344
|
+
moduleId
|
|
345
|
+
];
|
|
346
|
+
for(let next = queue.shift(); next != null; next = queue.shift()){
|
|
347
|
+
for (const importer of this.dependencies.get(next) ?? []){
|
|
348
|
+
if (found.has(importer)) continue;
|
|
349
|
+
found.add(importer);
|
|
350
|
+
if (this.loader.hasSourceFile(importer) && !this.isSelf(importer)) queue.push(importer);
|
|
351
|
+
}
|
|
334
352
|
}
|
|
335
353
|
return [
|
|
336
|
-
...
|
|
337
|
-
...moduleDeps,
|
|
338
|
-
...subDeps
|
|
339
|
-
])
|
|
354
|
+
...found
|
|
340
355
|
];
|
|
341
356
|
}
|
|
342
357
|
trackDependency(filePath, parentPath) {
|
|
@@ -6,9 +6,10 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
6
6
|
}
|
|
7
7
|
import { registerHooks } from "node:module";
|
|
8
8
|
import path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { injectable } from "@decohere/crucible/decorators";
|
|
10
|
-
import { BuildTargets,
|
|
11
|
-
import { packageAliases
|
|
11
|
+
import { BuildTargets, createWorkspace, SwcTranspiler } from "@decohere/forge";
|
|
12
|
+
import { packageAliases } from "@decohere/mesh";
|
|
12
13
|
import { FileEx, io } from "@decohere/nexus/io";
|
|
13
14
|
import { scribe } from "@decohere/scribe";
|
|
14
15
|
import { rewriteServeSpecifier } from "./rewrite-serve-specifier.js";
|
|
@@ -46,10 +47,7 @@ export class SourceModuleLoader {
|
|
|
46
47
|
this.disposed = false;
|
|
47
48
|
this.transpileCache.clear();
|
|
48
49
|
this.buildConfig = options.buildConfig;
|
|
49
|
-
this.workspace = options.workspace ?? await
|
|
50
|
-
...WorkspaceDefaults,
|
|
51
|
-
rootDir: process.cwd()
|
|
52
|
-
});
|
|
50
|
+
this.workspace = options.workspace ?? await createWorkspace();
|
|
53
51
|
this.versionFileUrl = options.versionFileUrl ?? ((fileUrl)=>fileUrl);
|
|
54
52
|
this.onResolved = options.onResolved;
|
|
55
53
|
this.fileInfoMap = await this.buildFileInfoMap(this.buildConfig);
|
|
@@ -126,7 +124,9 @@ export class SourceModuleLoader {
|
|
|
126
124
|
}
|
|
127
125
|
loadTs(url) {
|
|
128
126
|
if (!url.startsWith("file:")) return null;
|
|
129
|
-
|
|
127
|
+
// fileURLToPath, not URL.pathname: decodes %20 and gives Windows paths, so files
|
|
128
|
+
// whose path has a space still match Mesh's list (#511).
|
|
129
|
+
const cleanPath = fileURLToPath(url);
|
|
130
130
|
const fileInfo = this.fileInfoMap.get(cleanPath);
|
|
131
131
|
if (fileInfo == null) return null;
|
|
132
132
|
const cached = this.transpileCache.get(cleanPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decohere/flux",
|
|
3
|
-
"version": "0.0.1-dev.
|
|
3
|
+
"version": "0.0.1-dev.96",
|
|
4
4
|
"description": "Hot module reload server for Node.js ESM applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@decohere/crucible": "0.0.1-dev.
|
|
29
|
-
"@decohere/forge": "0.0.1-dev.
|
|
30
|
-
"@decohere/mesh": "0.0.1-dev.
|
|
31
|
-
"@decohere/nexus": "0.0.1-dev.
|
|
32
|
-
"@decohere/scribe": "0.0.1-dev.
|
|
28
|
+
"@decohere/crucible": "0.0.1-dev.96",
|
|
29
|
+
"@decohere/forge": "0.0.1-dev.96",
|
|
30
|
+
"@decohere/mesh": "0.0.1-dev.96",
|
|
31
|
+
"@decohere/nexus": "0.0.1-dev.96",
|
|
32
|
+
"@decohere/scribe": "0.0.1-dev.96",
|
|
33
33
|
"commander": "^13.1.0",
|
|
34
34
|
"rxjs": "^7.8.2"
|
|
35
35
|
},
|
|
@@ -70,7 +70,17 @@ export declare class HotReloadServer extends EventEmitter<HotReloadServerEvents>
|
|
|
70
70
|
*/
|
|
71
71
|
private onPostResolve;
|
|
72
72
|
private onFileChange;
|
|
73
|
+
/**
|
|
74
|
+
* A failed boot (e.g. importing a file that doesn't exist yet) may have recorded no
|
|
75
|
+
* dependency edges, so the graph can't tell whether this save fixes it. Any source
|
|
76
|
+
* add or change retries failed entries (#512).
|
|
77
|
+
*/
|
|
78
|
+
private retryFailedEntries;
|
|
73
79
|
private reloadIfAffected;
|
|
80
|
+
/**
|
|
81
|
+
* Every module that imports `moduleId`, directly or transitively. Visits each module
|
|
82
|
+
* once: import graphs share modules (diamonds) and may legally contain cycles (#515).
|
|
83
|
+
*/
|
|
74
84
|
private getDependencyGraph;
|
|
75
85
|
private trackDependency;
|
|
76
86
|
private getCachedOrNew;
|