@c9up/ream 0.2.23 → 0.2.24

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.
Files changed (53) hide show
  1. package/dist/Ignitor.d.ts +22 -0
  2. package/dist/Ignitor.d.ts.map +1 -1
  3. package/dist/Ignitor.js.map +1 -1
  4. package/dist/dev/fullReload.d.ts +23 -0
  5. package/dist/dev/fullReload.d.ts.map +1 -0
  6. package/dist/dev/fullReload.js +26 -0
  7. package/dist/dev/fullReload.js.map +1 -0
  8. package/dist/dev/hotGlob.d.ts +28 -0
  9. package/dist/dev/hotGlob.d.ts.map +1 -0
  10. package/dist/dev/hotGlob.js +67 -0
  11. package/dist/dev/hotGlob.js.map +1 -0
  12. package/dist/dev/hotGraph.d.ts +58 -0
  13. package/dist/dev/hotGraph.d.ts.map +1 -0
  14. package/dist/dev/hotGraph.js +91 -0
  15. package/dist/dev/hotGraph.js.map +1 -0
  16. package/dist/dev/hotWatcher.d.ts +34 -0
  17. package/dist/dev/hotWatcher.d.ts.map +1 -0
  18. package/dist/dev/hotWatcher.js +72 -0
  19. package/dist/dev/hotWatcher.js.map +1 -0
  20. package/dist/env/loadEnvFiles.d.ts +13 -0
  21. package/dist/env/loadEnvFiles.d.ts.map +1 -1
  22. package/dist/env/loadEnvFiles.js +65 -13
  23. package/dist/env/loadEnvFiles.js.map +1 -1
  24. package/dist/hot.d.ts +13 -16
  25. package/dist/hot.d.ts.map +1 -1
  26. package/dist/hot.js +110 -86
  27. package/dist/hot.js.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +1 -1
  30. package/events.darwin-arm64.node +0 -0
  31. package/events.darwin-x64.node +0 -0
  32. package/events.linux-arm64-gnu.node +0 -0
  33. package/events.linux-x64-gnu.node +0 -0
  34. package/events.win32-x64-msvc.node +0 -0
  35. package/index.darwin-arm64.node +0 -0
  36. package/index.darwin-x64.node +0 -0
  37. package/index.linux-arm64-gnu.node +0 -0
  38. package/index.linux-x64-gnu.node +0 -0
  39. package/index.win32-x64-msvc.node +0 -0
  40. package/package.json +4 -7
  41. package/scheduler.darwin-arm64.node +0 -0
  42. package/scheduler.darwin-x64.node +0 -0
  43. package/scheduler.linux-arm64-gnu.node +0 -0
  44. package/scheduler.linux-x64-gnu.node +0 -0
  45. package/scheduler.win32-x64-msvc.node +0 -0
  46. package/src/Ignitor.ts +24 -0
  47. package/src/dev/fullReload.ts +34 -0
  48. package/src/dev/hotGlob.ts +66 -0
  49. package/src/dev/hotGraph.ts +109 -0
  50. package/src/dev/hotWatcher.ts +89 -0
  51. package/src/env/loadEnvFiles.ts +69 -14
  52. package/src/hot.ts +116 -105
  53. package/src/index.ts +1 -1
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Watching the project for the changes a hot reload reacts to.
3
+ *
4
+ * `fs.watch` with `recursive: true`, which Node implements on Linux, macOS and
5
+ * Windows alike — the reason a watcher used to be a dependency here, and is not
6
+ * any more.
7
+ *
8
+ * Two behaviours are worth stating because they are not obvious from the API:
9
+ *
10
+ * A save is rarely one event. Editors write, rename and touch, and a single
11
+ * Ctrl-S can arrive as several notifications for the same file within a few
12
+ * milliseconds. Each one would walk the graph and bump a version, so they are
13
+ * coalesced.
14
+ *
15
+ * `ream dev` also watches from the outside, in Rust. That is not a duplicate:
16
+ * this watcher dies with the process it lives in, so a server that crashed on a
17
+ * syntax error has nothing left to notice the fix. The outer one covers exactly
18
+ * that window, and hands back over by restarting the process.
19
+ */
20
+ import { watch } from 'node:fs';
21
+ import { join, resolve } from 'node:path';
22
+ /** How long to wait for a save to finish arriving before acting on it. */
23
+ const SETTLE_MS = 40;
24
+ export function watchProject(options) {
25
+ const watchers = [];
26
+ const pending = new Map();
27
+ const report = (file) => {
28
+ const existing = pending.get(file);
29
+ if (existing !== undefined)
30
+ clearTimeout(existing);
31
+ const timer = setTimeout(() => {
32
+ pending.delete(file);
33
+ options.onChange(file);
34
+ }, SETTLE_MS);
35
+ // The dev server must still be able to exit while a save is settling.
36
+ timer.unref?.();
37
+ pending.set(file, timer);
38
+ };
39
+ for (const directory of options.directories) {
40
+ const absolute = resolve(options.root, directory);
41
+ try {
42
+ watchers.push(watch(absolute, { recursive: true }, (_event, name) => {
43
+ if (typeof name !== 'string' || name === '')
44
+ return;
45
+ report(join(absolute, name));
46
+ }));
47
+ }
48
+ catch {
49
+ // A directory this project does not have is not an error: the list is
50
+ // the framework's conventional layout, not a requirement.
51
+ }
52
+ }
53
+ for (const file of options.files) {
54
+ const absolute = resolve(options.root, file);
55
+ try {
56
+ watchers.push(watch(absolute, () => report(absolute)));
57
+ }
58
+ catch {
59
+ // Same: a `.env` that does not exist is simply not watched.
60
+ }
61
+ }
62
+ return {
63
+ close: () => {
64
+ for (const timer of pending.values())
65
+ clearTimeout(timer);
66
+ pending.clear();
67
+ for (const watcher of watchers)
68
+ watcher.close();
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=hotWatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotWatcher.js","sourceRoot":"","sources":["../../src/dev/hotWatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAkB,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,0EAA0E;AAC1E,MAAM,SAAS,GAAG,EAAE,CAAA;AAiBpB,MAAM,UAAU,YAAY,CAAC,OAAuB;IAClD,MAAM,QAAQ,GAAgB,EAAE,CAAA;IAChC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAA;IAEjD,MAAM,MAAM,GAAG,CAAC,IAAY,EAAQ,EAAE;QACpC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,QAAQ,KAAK,SAAS;YAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;QAClD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YACpB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC,EAAE,SAAS,CAAC,CAAA;QACb,sEAAsE;QACtE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC1B,CAAC,CAAA;IAED,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QACjD,IAAI,CAAC;YACH,QAAQ,CAAC,IAAI,CACX,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;gBACpD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE;oBAAE,OAAM;gBACnD,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAA;YAC9B,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;YACtE,0DAA0D;QAC5D,CAAC;IACH,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC5C,IAAI,CAAC;YACH,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,4DAA4D;QAC9D,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,GAAG,EAAE;YACV,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;YACzD,OAAO,CAAC,KAAK,EAAE,CAAA;YACf,KAAK,MAAM,OAAO,IAAI,QAAQ;gBAAE,OAAO,CAAC,KAAK,EAAE,CAAA;QACjD,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -1,3 +1,16 @@
1
+ /**
2
+ * The `.env*` files that will be read, most-specific first.
3
+ *
4
+ * Exported because `@c9up/ream/hot` has to WATCH exactly this list: nothing
5
+ * imports an env file, so hot-hook's dependency tree never sees one and an
6
+ * edit to `.env` changed nothing at all. A second copy of the list in the
7
+ * loader entry would drift from this one the first time the naming changes.
8
+ *
9
+ * @param options.skipEnvLocal Skip `.env.local` — see {@link loadEnvFiles}.
10
+ */
11
+ export declare function envFileNames(options?: {
12
+ skipEnvLocal?: boolean;
13
+ }): string[];
1
14
  /**
2
15
  * Load `.env` files into `process.env` — the shared primitive behind both the
3
16
  * Ignitor (HTTP/console boot) and `Env.create()` (config/test flow), mirroring
@@ -1 +1 @@
1
- {"version":3,"file":"loadEnvFiles.d.ts","sourceRoot":"","sources":["../../src/env/loadEnvFiles.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,CAgCzF"}
1
+ {"version":3,"file":"loadEnvFiles.d.ts","sourceRoot":"","sources":["../../src/env/loadEnvFiles.ts"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,EAAE,CAW/E;AA4BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,CAsCzF"}
@@ -1,8 +1,56 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { fileURLToPath } from 'node:url';
2
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
3
  import { parseEnv } from 'node:util';
4
4
  import { interpolate } from './interpolate.js';
5
5
  import { normalizeNodeEnv } from './nodeEnv.js';
6
+ /**
7
+ * The `.env*` files that will be read, most-specific first.
8
+ *
9
+ * Exported because `@c9up/ream/hot` has to WATCH exactly this list: nothing
10
+ * imports an env file, so hot-hook's dependency tree never sees one and an
11
+ * edit to `.env` changed nothing at all. A second copy of the list in the
12
+ * loader entry would drift from this one the first time the naming changes.
13
+ *
14
+ * @param options.skipEnvLocal Skip `.env.local` — see {@link loadEnvFiles}.
15
+ */
16
+ export function envFileNames(options = {}) {
17
+ // Normalised, so `NODE_ENV=prod` loads `.env.production` — the file the
18
+ // deployment actually wrote — instead of looking for `.env.prod`.
19
+ const raw = normalizeNodeEnv(process.env.NODE_ENV);
20
+ const nodeEnv = raw === 'unknown' ? undefined : raw;
21
+ return [
22
+ nodeEnv ? `.env.${nodeEnv}.local` : null,
23
+ options.skipEnvLocal ? null : '.env.local',
24
+ nodeEnv ? `.env.${nodeEnv}` : null,
25
+ '.env',
26
+ ].filter((name) => name !== null);
27
+ }
28
+ /**
29
+ * Where the `.env*` files are read from.
30
+ *
31
+ * Normally beside the application, which is what `appRoot` names. A BUILT
32
+ * application moves: `start/env.js` sits in `dist/`, so `new URL('../',
33
+ * import.meta.url)` resolves there and the files at the project root become
34
+ * invisible — `ream start` died on a `.env` it was standing next to.
35
+ *
36
+ * `ENV_PATH` is AdonisJS's own variable for this, named and shaped the same
37
+ * way: a DIRECTORY to read the files from, as in
38
+ * `ENV_PATH=/etc/secrets node server.js`. An application moving over keeps
39
+ * whatever it already sets.
40
+ *
41
+ * It is also why the ordering rule stays here rather than in the CLI: node's
42
+ * `--env-file` applies files last-wins while this loader applies them
43
+ * most-specific-first, and a second copy of that would be one to keep in step
44
+ * forever.
45
+ */
46
+ function envDirectory(appRoot) {
47
+ const override = process.env.ENV_PATH;
48
+ if (override === undefined || override.trim() === '')
49
+ return appRoot;
50
+ // A directory URL, so `new URL('.env', directory)` lands inside it rather
51
+ // than beside it.
52
+ return pathToFileURL(override.endsWith('/') ? override : `${override}/`);
53
+ }
6
54
  /**
7
55
  * Load `.env` files into `process.env` — the shared primitive behind both the
8
56
  * Ignitor (HTTP/console boot) and `Env.create()` (config/test flow), mirroring
@@ -17,24 +65,17 @@ import { normalizeNodeEnv } from './nodeEnv.js';
17
65
  * developer's local overrides don't leak into tests).
18
66
  */
19
67
  export function loadEnvFiles(appRoot, options = {}) {
20
- // Normalised, so `NODE_ENV=prod` loads `.env.production` — the file the
21
- // deployment actually wrote — instead of looking for `.env.prod`.
22
- const raw = normalizeNodeEnv(process.env.NODE_ENV);
23
- const nodeEnv = raw === 'unknown' ? undefined : raw;
24
- const files = [
25
- nodeEnv ? `.env.${nodeEnv}.local` : null,
26
- options.skipEnvLocal ? null : '.env.local',
27
- nodeEnv ? `.env.${nodeEnv}` : null,
28
- '.env',
29
- ].filter((name) => name !== null);
30
- for (const name of files) {
68
+ const directory = envDirectory(appRoot);
69
+ let found = false;
70
+ for (const name of envFileNames(options)) {
31
71
  let contents;
32
72
  try {
33
- contents = readFileSync(fileURLToPath(new URL(name, appRoot)), 'utf8');
73
+ contents = readFileSync(fileURLToPath(new URL(name, directory)), 'utf8');
34
74
  }
35
75
  catch {
36
76
  continue; // file absent — nothing to load
37
77
  }
78
+ found = true;
38
79
  const parsed = parseEnv(contents);
39
80
  for (const [key, value] of Object.entries(parsed)) {
40
81
  if (typeof value !== 'string' || process.env[key] !== undefined)
@@ -50,5 +91,16 @@ export function loadEnvFiles(appRoot, options = {}) {
50
91
  });
51
92
  }
52
93
  }
94
+ // Upstream raises when `ENV_PATH` names a place with no env file in it, and
95
+ // for the reason silence would be wrong: the variable is somebody saying
96
+ // where the file IS. A typo in the path would otherwise start a server on
97
+ // whatever defaults happened to be around, which is how a deployment reads
98
+ // its staging config in production and nobody finds out until later.
99
+ //
100
+ // Without `ENV_PATH` a missing file stays silent: an application may have
101
+ // none at all, and every value may come from the environment.
102
+ if (!found && process.env.ENV_PATH !== undefined && process.env.ENV_PATH.trim() !== '') {
103
+ throw new Error(`ENV_PATH points at ${process.env.ENV_PATH}, which holds none of ${envFileNames(options).join(', ')}`);
104
+ }
53
105
  }
54
106
  //# sourceMappingURL=loadEnvFiles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loadEnvFiles.js","sourceRoot":"","sources":["../../src/env/loadEnvFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,OAAY,EAAE,UAAsC,EAAE;IACjF,wEAAwE;IACxE,kEAAkE;IAClE,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAClD,MAAM,OAAO,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA;IACnD,MAAM,KAAK,GAAG;QACZ,OAAO,CAAC,CAAC,CAAC,QAAQ,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI;QACxC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY;QAC1C,OAAO,CAAC,CAAC,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI;QAClC,MAAM;KACP,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;IAEjD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,QAAgB,CAAA;QACpB,IAAI,CAAC;YACH,QAAQ,GAAG,YAAY,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;QACxE,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ,CAAC,gCAAgC;QAC3C,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;QACjC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS;gBAAE,SAAQ;YACzE,uEAAuE;YACvE,iEAAiE;YACjE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;gBAC7C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;gBACrC,IAAI,WAAW,KAAK,SAAS;oBAAE,OAAO,WAAW,CAAA;gBACjD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;gBAC7B,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;YAC5D,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"loadEnvFiles.js","sourceRoot":"","sources":["../../src/env/loadEnvFiles.ts"],"names":[],"mappings":"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"}
package/dist/hot.d.ts CHANGED
@@ -5,25 +5,22 @@
5
5
  *
6
6
  * node --import @swc-node/register/esm-register --import @c9up/ream/hot bin/server.ts
7
7
  *
8
- * It is a thin shim over `hot-hook`, which does the actual work: it registers
9
- * an ESM loader that tracks the import graph, and when a file inside a declared
10
- * boundary changes it invalidates just that module instead of the process. The
11
- * `hotHook` key in the application's `package.json` configures it, exactly as
12
- * upstream reads it.
8
+ * It replaces a module that changed instead of the process that was serving it,
9
+ * so the container, the pools and the connections survive a save.
13
10
  *
14
- * NAMED DEVIATION — why this file exists at all.
11
+ * NAMED DEVIATION — this is ream's own, where AdonisJS uses `hot-hook`.
15
12
  *
16
- * Upstream's dev server is itself a Node process: it forks the app, so the two
17
- * talk over Node's IPC channel, and `hot-hook` reports a change it cannot swap
18
- * by calling `process.send({ type: 'hot-hook:full-reload' })`. Our dev server is
19
- * a Rust binary. A process it spawns has NO IPC channel, so `process.send` is
20
- * `undefined` and that message goes nowhere: the server would keep serving the
21
- * old module and say nothing.
13
+ * Upstream ships that package in its starter kit, which means every application
14
+ * installs it, and one created before the convention existed silently loses hot
15
+ * reloading. The parts that once justified a library are now in Node:
16
+ * `module.registerHooks` gives the import graph, `fs.watch` gives the changes.
17
+ * What is left is the decision of what to invalidate, which is this file and
18
+ * its three neighbours — and no entry in anyone's `package.json`.
22
19
  *
23
- * `hot.init()` takes an `onFullReloadAsked` callback for exactly this case, and
24
- * that is the whole of the shim: turn "I cannot hot-swap this" into an exit
25
- * code the Rust parent understands and restarts on. Everything else is
26
- * upstream's behaviour, unmodified.
20
+ * The graph comes from the resolver rather than from reading files: a resolve
21
+ * hook is handed the importer and the imported, which is exactly an edge, and
22
+ * it sees a dynamic import at the moment it happens rather than guessing at it
23
+ * from the source.
27
24
  */
28
25
  export {};
29
26
  //# sourceMappingURL=hot.d.ts.map
package/dist/hot.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hot.d.ts","sourceRoot":"","sources":["../src/hot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
1
+ {"version":3,"file":"hot.d.ts","sourceRoot":"","sources":["../src/hot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
package/dist/hot.js CHANGED
@@ -5,108 +5,132 @@
5
5
  *
6
6
  * node --import @swc-node/register/esm-register --import @c9up/ream/hot bin/server.ts
7
7
  *
8
- * It is a thin shim over `hot-hook`, which does the actual work: it registers
9
- * an ESM loader that tracks the import graph, and when a file inside a declared
10
- * boundary changes it invalidates just that module instead of the process. The
11
- * `hotHook` key in the application's `package.json` configures it, exactly as
12
- * upstream reads it.
8
+ * It replaces a module that changed instead of the process that was serving it,
9
+ * so the container, the pools and the connections survive a save.
13
10
  *
14
- * NAMED DEVIATION — why this file exists at all.
11
+ * NAMED DEVIATION — this is ream's own, where AdonisJS uses `hot-hook`.
15
12
  *
16
- * Upstream's dev server is itself a Node process: it forks the app, so the two
17
- * talk over Node's IPC channel, and `hot-hook` reports a change it cannot swap
18
- * by calling `process.send({ type: 'hot-hook:full-reload' })`. Our dev server is
19
- * a Rust binary. A process it spawns has NO IPC channel, so `process.send` is
20
- * `undefined` and that message goes nowhere: the server would keep serving the
21
- * old module and say nothing.
13
+ * Upstream ships that package in its starter kit, which means every application
14
+ * installs it, and one created before the convention existed silently loses hot
15
+ * reloading. The parts that once justified a library are now in Node:
16
+ * `module.registerHooks` gives the import graph, `fs.watch` gives the changes.
17
+ * What is left is the decision of what to invalidate, which is this file and
18
+ * its three neighbours — and no entry in anyone's `package.json`.
22
19
  *
23
- * `hot.init()` takes an `onFullReloadAsked` callback for exactly this case, and
24
- * that is the whole of the shim: turn "I cannot hot-swap this" into an exit
25
- * code the Rust parent understands and restarts on. Everything else is
26
- * upstream's behaviour, unmodified.
20
+ * The graph comes from the resolver rather than from reading files: a resolve
21
+ * hook is handed the importer and the imported, which is exactly an edge, and
22
+ * it sees a dynamic import at the moment it happens rather than guessing at it
23
+ * from the source.
27
24
  */
25
+ import { existsSync, writeSync } from 'node:fs';
26
+ import { readFile } from 'node:fs/promises';
27
+ import { registerHooks } from 'node:module';
28
+ import { dirname, relative, resolve as resolvePath } from 'node:path';
29
+ import { fileURLToPath } from 'node:url';
30
+ import { fullReloadNotice } from './dev/fullReload.js';
28
31
  import { hotReloadHappened } from './dev/hmr.js';
29
32
  import { readHotHookConfig } from './dev/hotConfig.js';
33
+ import { globMatcher } from './dev/hotGlob.js';
34
+ import { HotGraph } from './dev/hotGraph.js';
35
+ import { watchProject } from './dev/hotWatcher.js';
36
+ import { envFileNames } from './env/loadEnvFiles.js';
30
37
  /** Exit code meaning "restart me" — see `EXIT_RESTART` in the CLI's dev module. */
31
38
  const FULL_RELOAD_EXIT_CODE = 75;
32
- const { readFile } = await import('node:fs/promises');
33
- const { dirname, resolve } = await import('node:path');
34
- const packageJsonPath = resolve(process.cwd(), 'package.json');
39
+ /** The query parameter that makes the loader fetch a module again. */
40
+ const VERSION_PARAM = 'ream_hot';
41
+ /** Directories a project keeps its own code in — the CLI watches the same list. */
42
+ const WATCH_DIRS = ['app', 'bin', 'config', 'start', 'database', 'providers', 'commands'];
43
+ const packageJsonPath = resolvePath(process.cwd(), 'package.json');
44
+ const root = dirname(packageJsonPath);
35
45
  let config = {};
36
46
  try {
37
47
  config = readHotHookConfig(JSON.parse(await readFile(packageJsonPath, 'utf8')));
38
48
  }
39
49
  catch {
40
- // No package.json, or unreadable: fall through to the conventional
41
- // boundaries below rather than stopping dev over it.
50
+ // No package.json, or unreadable: a project with no boundaries declared gets
51
+ // a working server that restarts on change, which is the old behaviour.
42
52
  }
43
- // `hot-hook` is an optional peer, declared by the application because it is a
44
- // dev dependency there and has no business in a production install. Imported by
45
- // specifier built at runtime so a production bundle never resolves it.
46
- function isHotHook(value) {
47
- return (typeof value === 'object' && value !== null && typeof Reflect.get(value, 'init') === 'function');
48
- }
49
- // `hot-hook` belongs to the APPLICATION: it is a dev dependency there and has
50
- // no business in a production install of the framework. So it is resolved from
51
- // the project directory rather than from this file — under pnpm's strict layout
52
- // a bare `import("hot-hook")` here looks inside @c9up/ream and finds nothing.
53
- // It is the same reason upstream passes `hot-hook/register` from the app's own
54
- // cwd instead of re-exporting it from the framework.
55
- const { createRequire } = await import('node:module');
56
- const { pathToFileURL } = await import('node:url');
57
- let hot;
58
- try {
59
- const resolved = createRequire(packageJsonPath).resolve('hot-hook');
60
- const module = await import(pathToFileURL(resolved).href);
61
- const candidate = typeof module === 'object' && module !== null ? Reflect.get(module, 'hot') : undefined;
62
- if (!isHotHook(candidate)) {
63
- throw new Error('hot-hook did not expose a `hot` object with init()');
53
+ const { boundaries = [], restart = [] } = config;
54
+ // Env files are not modules: nothing imports one, so the graph never sees them
55
+ // and an edit to `.env` did nothing at all — the values are read once, at boot.
56
+ // They always restart.
57
+ const restartFiles = new Set(envFileNames()
58
+ .map((name) => resolvePath(root, name))
59
+ .filter((file) => existsSync(file)));
60
+ const relativeToRoot = (file) => relative(root, file).replace(/\\/g, '/');
61
+ const matchesBoundary = globMatcher(boundaries);
62
+ const matchesRestartGlob = globMatcher(restart);
63
+ const graph = new HotGraph({
64
+ isBoundary: (file) => matchesBoundary(relativeToRoot(file)),
65
+ isRestart: (file) => restartFiles.has(file) || matchesRestartGlob(relativeToRoot(file)),
66
+ });
67
+ /**
68
+ * Track every edge the resolver reports, and hand back a versioned URL for a
69
+ * module that has been invalidated.
70
+ *
71
+ * The version travels in the URL because that is the only handle V8 offers: a
72
+ * module is keyed by its URL, so a different one is a different module. The
73
+ * parameter is stripped off the importer before delegating, or a relative
74
+ * specifier would be resolved against a URL that does not name a directory.
75
+ */
76
+ registerHooks({
77
+ resolve(specifier, context, nextResolve) {
78
+ let parentURL = context.parentURL;
79
+ let parentPath;
80
+ if (typeof parentURL === 'string' && parentURL.startsWith('file:')) {
81
+ const url = new URL(parentURL);
82
+ if (url.searchParams.has(VERSION_PARAM)) {
83
+ url.searchParams.delete(VERSION_PARAM);
84
+ parentURL = url.href;
85
+ }
86
+ parentPath = fileURLToPath(url);
87
+ }
88
+ const result = nextResolve(specifier, { ...context, parentURL });
89
+ if (!result.url.startsWith('file:'))
90
+ return result;
91
+ const resolved = new URL(result.url);
92
+ resolved.searchParams.delete(VERSION_PARAM);
93
+ const file = fileURLToPath(resolved);
94
+ graph.link(file, parentPath);
95
+ const version = graph.version(file);
96
+ if (version === 0)
97
+ return result;
98
+ resolved.searchParams.set(VERSION_PARAM, String(version));
99
+ return { ...result, url: resolved.href };
100
+ },
101
+ });
102
+ /** Print a line that must survive the exit on the very next statement. */
103
+ function sayNow(line) {
104
+ // Synchronously: a piped stdout — what `ream dev` hands this process when it
105
+ // runs an asset watcher alongside — is asynchronous, and the line would be
106
+ // dropped on the way out.
107
+ try {
108
+ writeSync(1, `${line}\n`);
109
+ }
110
+ catch {
111
+ // The parent can close the pipe first on Ctrl-C. Losing the line is fine.
64
112
  }
65
- hot = candidate;
66
- }
67
- catch (error) {
68
- // Loud, and it names the fix. Silence here would start a perfectly healthy
69
- // server that never picks up an edit, which reads as "HMR is broken" rather
70
- // than "HMR is not installed".
71
- //
72
- // It does NOT throw: a dev server that refuses to start is worse than one
73
- // without hot reloading, and the CLI only loads this file when it has
74
- // already seen hot-hook in the project — so reaching here means it went
75
- // missing under a running session.
76
- console.error(`[ream] hot module replacement is unavailable: ${error instanceof Error ? error.message : String(error)}\n` +
77
- '[ream] Install it with: pnpm add -D hot-hook, then restart `ream dev`.\n' +
78
- '[ream] Until then the server runs, but an edit will not be picked up.');
79
113
  }
80
- // Impersonate the IPC channel upstream's dev server provides.
81
- //
82
- // hot-hook reports BOTH of its outcomes through `process.send`: a change it
83
- // swapped in place (`hot-hook:invalidated`) and one it could not
84
- // (`hot-hook:full-reload`). Under a Node parent that function exists and both
85
- // arrive; under our Rust parent it is `undefined` and both are dropped —
86
- // `onFullReloadAsked` alone recovers only half of that, leaving a hot swap
87
- // invisible to the very process it happened in, and so to the browser.
88
- //
89
- // This is not a trick played on hot-hook: it is exactly the contract it
90
- // expects, provided by us instead of by Node.
91
- const previousSend = process.send;
92
- process.send = (message, ...rest) => {
93
- const type = typeof message === 'object' && message !== null ? Reflect.get(message, 'type') : undefined;
94
- if (type === 'hot-hook:invalidated') {
114
+ watchProject({
115
+ root,
116
+ directories: WATCH_DIRS,
117
+ files: [...restartFiles].map((file) => relative(root, file)),
118
+ onChange: (file) => {
119
+ const decision = graph.decide(file);
120
+ if (decision.kind === 'ignore')
121
+ return;
122
+ if (decision.kind === 'reload') {
123
+ sayNow(fullReloadNotice(decision.file, decision.reason, relativeToRoot));
124
+ // Exiting on a known code is how the Rust parent learns it must restart —
125
+ // see `EXIT_RESTART` in the CLI's dev module.
126
+ process.exit(FULL_RELOAD_EXIT_CODE);
127
+ }
128
+ // The router caches a promoted controller per route, and the kernel drops
129
+ // that cache when this counter moves. Without it the swap happens and the
130
+ // process keeps serving the class it promoted: a stable PID and stale
131
+ // output, which is exactly how it was first reported.
95
132
  hotReloadHappened();
96
- }
97
- // If a real channel ever exists (embedded under a Node supervisor) it still
98
- // gets its message: this observes, it does not intercept.
99
- if (typeof previousSend === 'function') {
100
- return Reflect.apply(previousSend, process, [message, ...rest]) === true;
101
- }
102
- return true;
103
- };
104
- await hot?.init({
105
- ...config,
106
- rootDirectory: dirname(packageJsonPath),
107
- root: config.root ? resolve(dirname(packageJsonPath), config.root) : undefined,
108
- // Exiting on a known code is how the Rust parent learns it must restart —
109
- // see `EXIT_RESTART` in the CLI's dev module.
110
- onFullReloadAsked: () => process.exit(FULL_RELOAD_EXIT_CODE),
133
+ sayNow(`[ream] hot swap — ${relativeToRoot(file)}`);
134
+ },
111
135
  });
112
136
  //# sourceMappingURL=hot.js.map
package/dist/hot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hot.js","sourceRoot":"","sources":["../src/hot.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hot.js","sourceRoot":"","sources":["../src/hot.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -121,5 +121,5 @@ export { BaseEvent, Emitter, type EmitterResolver, type ListenerClass, } from '.
121
121
  export { type BufferedEvent, type BufferedEventName, type EventFinder, EventsBuffer, } from './events/EventsBuffer.js';
122
122
  export type { ErrorReporter, LockBackend, LockBackendFactory, LockRedisClient, LockRedisResolver, RunTaskOutcome, ScheduleEvent, ScheduleEventSink, ScheduleInvocation, ScheduleMetadata, ScheduleProviderOptions, SchedulerConfig, SchedulerOptions, ScheduleTaskCompletedEvent, ScheduleTaskFailedEvent, ScheduleTaskSkippedEvent, ScheduleTaskStartedEvent, TaskInfo, TaskStats, } from './scheduler/index.js';
123
123
  export { defineSchedulerConfig, getScheduleMetadata, locks, MemoryLockBackend, RedisLockBackend, SCHEDULE_METADATA_KEY, Schedule, ScheduleProvider, Scheduler, StatsTracker, } from './scheduler/index.js';
124
- export declare const VERSION = "0.2.23";
124
+ export declare const VERSION = "0.2.24";
125
125
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -92,5 +92,5 @@ export { defineConfig as defineEventsConfig } from './events/config.js';
92
92
  export { BaseEvent, Emitter, } from './events/Emitter.js';
93
93
  export { EventsBuffer, } from './events/EventsBuffer.js';
94
94
  export { defineSchedulerConfig, getScheduleMetadata, locks, MemoryLockBackend, RedisLockBackend, SCHEDULE_METADATA_KEY, Schedule, ScheduleProvider, Scheduler, StatsTracker, } from './scheduler/index.js';
95
- export const VERSION = '0.2.23';
95
+ export const VERSION = '0.2.24';
96
96
  //# sourceMappingURL=index.js.map
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/ream",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "description": "Ream — Rust-powered Node.js application framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -233,8 +233,7 @@
233
233
  },
234
234
  "peerDependencies": {
235
235
  "@c9up/comet": "^0.1.0",
236
- "@c9up/quasar": "^0.1.0 || ^0.2.0",
237
- "hot-hook": "^1.0.0"
236
+ "@c9up/quasar": "^0.1.0 || ^0.2.0"
238
237
  },
239
238
  "peerDependenciesMeta": {
240
239
  "@c9up/comet": {
@@ -242,9 +241,6 @@
242
241
  },
243
242
  "@c9up/quasar": {
244
243
  "optional": true
245
- },
246
- "hot-hook": {
247
- "optional": true
248
244
  }
249
245
  },
250
246
  "devDependencies": {
@@ -257,8 +253,9 @@
257
253
  "typescript": "^6.0.3",
258
254
  "vitest": "4.1.11"
259
255
  },
256
+ "//engines": "Node 24, not 22, because AsyncLocalStorage sits on the request hot path — the container resolves through it, HttpContext is it, and the emitter reads it on every event. Node 24 backs it with AsyncContextFrame by default, and measured on that exact pattern (one context per request, six async boundaries, several reads) it costs 0.61 us per request instead of 1.55 us: 2.5x, on every request, for free. Before 24 the same mechanism existed only behind an experimental flag, and a framework cannot base its performance on a flag the application has to remember to pass. This is NOT 'track the current LTS' — security fixes still land in 22 until 2027, npm 11 is irrelevant under pnpm, and node:sqlite is not what atlas uses.",
260
257
  "engines": {
261
- "node": ">=22.0.0"
258
+ "node": ">=24.0.0"
262
259
  },
263
260
  "publishConfig": {
264
261
  "access": "public"
Binary file
Binary file
Binary file
Binary file
Binary file