fluxion-ts 0.5.1 → 0.6.1

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/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  Fluxion is a filesystem-routing dynamic server for Node.js.
14
14
 
15
- - Route files from a dynamic directory(by chokidar)
15
+ - Route files from a dynamic directory by chokidar or native `fs.watch`
16
16
  - Load API handlers by extension, default: `.ts`
17
17
  - Serve other files as static resources
18
18
  - Run the business server in worker processes
@@ -283,6 +283,7 @@ interface FluxionOptions {
283
283
  port: number;
284
284
  reloadDelay?: number;
285
285
  metaPort?: number;
286
+ nativeWatcher?: boolean;
286
287
  injections?: InjectionConfig[];
287
288
  moduleDir?: string;
288
289
  workerOptions?: Partial<WorkerOptions>;
@@ -318,6 +319,28 @@ Primary meta API port. Defaults to `port + 1` and must be different from `port`.
318
319
 
319
320
  Debounce delay for file re-registration. Defaults to `300` and must be at least `50`.
320
321
 
322
+ ### `nativeWatcher`
323
+
324
+ Use native file watcher (`fs.watch`) instead of chokidar. Defaults to `false`.
325
+
326
+ When set to `true`, Fluxion uses Node.js built-in `fs.watch()` for file watching. When `false` (default), it uses `chokidar` for better cross-platform compatibility.
327
+
328
+ **Trade-offs:**
329
+
330
+ - `chokidar` (default): Better cross-platform support, more stable, handles edge cases
331
+ - `fs.watch`: Native implementation, lighter weight, but may have platform-specific quirks
332
+
333
+ Example:
334
+
335
+ ```ts
336
+ fluxion({
337
+ dir: './dynamicDirectory',
338
+ host: '127.0.0.1',
339
+ port: 3000,
340
+ nativeWatcher: true, // use native fs.watch instead of chokidar
341
+ });
342
+ ```
343
+
321
344
  ### `apiExts`
322
345
 
323
346
  Extensions registered as API handlers. Defaults to:
@@ -428,6 +451,28 @@ Primary meta API port. Defaults to `port + 1` and must be different from `port`.
428
451
 
429
452
  Debounce delay for file re-registration. Defaults to `300` and must be at least `50`.
430
453
 
454
+ ### `nativeWatcher`
455
+
456
+ Use native file watcher (`fs.watch`) instead of chokidar. Defaults to `false`.
457
+
458
+ When set to `true`, Fluxion uses Node.js built-in `fs.watch()` for file watching. When `false` (default), it uses `chokidar` for better cross-platform compatibility.
459
+
460
+ **Trade-offs:**
461
+
462
+ - `chokidar` (default): Better cross-platform support, more stable, handles edge cases
463
+ - `fs.watch`: Native implementation, lighter weight, but may have platform-specific quirks
464
+
465
+ Example:
466
+
467
+ ```ts
468
+ fluxion({
469
+ dir: './dynamicDirectory',
470
+ host: '127.0.0.1',
471
+ port: 3000,
472
+ nativeWatcher: true, // use native fs.watch instead of chokidar
473
+ });
474
+ ```
475
+
431
476
  ### `apiExts`
432
477
 
433
478
  Extensions registered as API handlers. Defaults to:
package/dist/index.cjs CHANGED
@@ -355,7 +355,7 @@ function normalizeOptions(options) {
355
355
  '**/.nyc_output/**',
356
356
  '**/*.tmp',
357
357
  '**/*.temp',
358
- ], https, } = options;
358
+ ], https, nativeWatcher = false, } = options;
359
359
  const logger = options.logger ?? 'one-line';
360
360
  expectLoggerOption(logger);
361
361
  expect.isString(dir, 'FluxionOptions.dir must be a string');
@@ -397,6 +397,7 @@ function normalizeOptions(options) {
397
397
  include,
398
398
  apiInclude,
399
399
  exclude,
400
+ nativeWatcher,
400
401
  https: normalizeHttpsOptions(https, moduleDir),
401
402
  };
402
403
  }
@@ -2854,6 +2855,99 @@ class FluxionWatcher {
2854
2855
  }
2855
2856
  }
2856
2857
 
2858
+ class FluxionNativeWatcher {
2859
+ constructor(cx) {
2860
+ this.timer = null;
2861
+ this.watcher = null;
2862
+ this.filesChanged = new Set();
2863
+ this.cx = cx;
2864
+ }
2865
+ /**
2866
+ * Recursively register all files in the options directory.
2867
+ */
2868
+ init() {
2869
+ const dirPath = path$1.isAbsolute(this.cx.options.dir)
2870
+ ? this.cx.options.dir
2871
+ : path$1.join(process.cwd(), this.cx.options.dir);
2872
+ const registerRecursive = (dir, relativePath) => {
2873
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
2874
+ for (const entry of entries) {
2875
+ const entryPath = path$1.join(dir, entry.name);
2876
+ const entryRelativePath = path$1.join(relativePath, entry.name);
2877
+ if (entry.isDirectory()) {
2878
+ registerRecursive(entryPath, entryRelativePath);
2879
+ }
2880
+ else if (entry.isFile()) {
2881
+ try {
2882
+ this.cx.router.register(entryRelativePath);
2883
+ }
2884
+ catch (err) {
2885
+ this.cx.logger.error(`Error registering [${entryRelativePath}]: ${err.message}`);
2886
+ }
2887
+ }
2888
+ }
2889
+ };
2890
+ if (fs.existsSync(dirPath)) {
2891
+ registerRecursive(dirPath, '');
2892
+ this.cx.logger.info(`Initial registration complete for directory: ${this.cx.options.dir}`);
2893
+ }
2894
+ else {
2895
+ this.cx.logger.warn(`Directory does not exist: ${this.cx.options.dir}`);
2896
+ }
2897
+ return this;
2898
+ }
2899
+ /**
2900
+ * Since all actions are mapped to `rename` and `change` (WatchEventType).
2901
+ *
2902
+ * We could only record every file and reload them all.
2903
+ */
2904
+ start() {
2905
+ this.init();
2906
+ this.watcher = fs
2907
+ .watch(this.cx.options.dir, { recursive: true }, (_eventType, filename) => {
2908
+ if (!filename) {
2909
+ return;
2910
+ }
2911
+ this.filesChanged.add(filename);
2912
+ if (!this.timer) {
2913
+ this.timer = setTimeout(() => {
2914
+ this.filesChanged.forEach((p, _, s) => {
2915
+ try {
2916
+ this.cx.router.register(p);
2917
+ }
2918
+ catch (err) {
2919
+ this.cx.logger.error(`Error refreshing handlers: ${err.message}`);
2920
+ }
2921
+ finally {
2922
+ s.delete(p);
2923
+ }
2924
+ });
2925
+ this.timer = null;
2926
+ }, this.cx.options.reloadDelay);
2927
+ }
2928
+ })
2929
+ .on('error', (err) => {
2930
+ this.cx.logger.error(`Watcher error: ${err.message}`);
2931
+ this.cx.logger.error(`Restarting watcher...`);
2932
+ this.stop().start();
2933
+ });
2934
+ this.cx.logger.info(`Watcher started on directory: ${this.cx.options.dir}`);
2935
+ return this;
2936
+ }
2937
+ stop() {
2938
+ if (this.watcher) {
2939
+ this.watcher.close();
2940
+ this.watcher = null;
2941
+ }
2942
+ if (this.timer) {
2943
+ clearTimeout(this.timer);
2944
+ this.timer = null;
2945
+ }
2946
+ this.filesChanged.clear();
2947
+ return this;
2948
+ }
2949
+ }
2950
+
2857
2951
  const balanced = (a, b, str) => {
2858
2952
  const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
2859
2953
  const mb = b instanceof RegExp ? maybeMatch(b, str) : b;
@@ -5378,7 +5472,8 @@ async function fluxion(options) {
5378
5472
  // Replace logger with worker logger that prefixes PID
5379
5473
  context.logger = createWorkerLogger(context.logger, process.pid);
5380
5474
  // Only worker creates the watcher
5381
- context.watcher = new FluxionWatcher(context).start();
5475
+ const Watcher = context.options.nativeWatcher ? FluxionNativeWatcher : FluxionWatcher;
5476
+ context.watcher = new Watcher(context).start();
5382
5477
  initWorker(context);
5383
5478
  }
5384
5479
  }