@cdklabs/cdk-construct-connect-datalake 0.0.13 → 0.0.14

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 (124) hide show
  1. package/.jsii +9 -9
  2. package/lib/index.js +1 -1
  3. package/node_modules/@aws-sdk/client-cloudformation/package.json +5 -5
  4. package/node_modules/@aws-sdk/client-connect/package.json +5 -5
  5. package/node_modules/@aws-sdk/client-glue/dist-cjs/index.js +6 -4
  6. package/node_modules/@aws-sdk/client-glue/dist-cjs/schemas/schemas_0.js +57 -30
  7. package/node_modules/@aws-sdk/client-glue/dist-es/models/enums.js +6 -4
  8. package/node_modules/@aws-sdk/client-glue/dist-es/schemas/schemas_0.js +42 -15
  9. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetTableCommand.d.ts +121 -0
  10. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetTableVersionCommand.d.ts +118 -0
  11. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetTableVersionsCommand.d.ts +118 -0
  12. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetTablesCommand.d.ts +119 -1
  13. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetUnfilteredTableMetadataCommand.d.ts +118 -0
  14. package/node_modules/@aws-sdk/client-glue/dist-types/commands/SearchTablesCommand.d.ts +118 -0
  15. package/node_modules/@aws-sdk/client-glue/dist-types/commands/UpdateCrawlerScheduleCommand.d.ts +2 -1
  16. package/node_modules/@aws-sdk/client-glue/dist-types/models/enums.d.ts +14 -12
  17. package/node_modules/@aws-sdk/client-glue/dist-types/models/models_2.d.ts +87 -6
  18. package/node_modules/@aws-sdk/client-glue/dist-types/models/models_3.d.ts +11 -1
  19. package/node_modules/@aws-sdk/client-glue/dist-types/schemas/schemas_0.d.ts +1 -0
  20. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/commands/UpdateCrawlerScheduleCommand.d.ts +2 -4
  21. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/models/enums.d.ts +8 -6
  22. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/models/models_2.d.ts +18 -1
  23. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/models/models_3.d.ts +3 -0
  24. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/schemas/schemas_0.d.ts +1 -0
  25. package/node_modules/@aws-sdk/client-glue/package.json +5 -5
  26. package/node_modules/@aws-sdk/client-lakeformation/package.json +5 -5
  27. package/node_modules/@aws-sdk/client-ram/package.json +5 -5
  28. package/node_modules/@aws-sdk/client-sts/package.json +6 -6
  29. package/node_modules/@aws-sdk/core/dist-cjs/index.js +47 -2297
  30. package/node_modules/@aws-sdk/core/dist-es/index.js +3 -3
  31. package/node_modules/@aws-sdk/core/dist-types/index.d.ts +5 -5
  32. package/node_modules/@aws-sdk/core/dist-types/ts3.4/index.d.ts +5 -5
  33. package/node_modules/@aws-sdk/core/package.json +8 -8
  34. package/node_modules/@aws-sdk/credential-provider-env/package.json +4 -4
  35. package/node_modules/@aws-sdk/credential-provider-http/package.json +6 -5
  36. package/node_modules/@aws-sdk/credential-provider-ini/package.json +11 -11
  37. package/node_modules/@aws-sdk/credential-provider-login/package.json +5 -5
  38. package/node_modules/@aws-sdk/credential-provider-node/package.json +9 -9
  39. package/node_modules/@aws-sdk/credential-provider-process/package.json +4 -4
  40. package/node_modules/@aws-sdk/credential-provider-sso/dist-cjs/index.js +4 -1
  41. package/node_modules/@aws-sdk/credential-provider-sso/dist-es/resolveSSOCredentials.js +4 -1
  42. package/node_modules/@aws-sdk/credential-provider-sso/package.json +9 -7
  43. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +5 -5
  44. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
  45. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
  46. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
  47. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
  48. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
  49. package/node_modules/@aws-sdk/nested-clients/package.json +6 -6
  50. package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +7 -6
  51. package/node_modules/@aws-sdk/token-providers/package.json +5 -5
  52. package/node_modules/@aws-sdk/types/package.json +2 -2
  53. package/node_modules/@aws-sdk/util-locate-window/package.json +2 -2
  54. package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/CHANGELOG.md +8 -0
  55. package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/README.md +9 -0
  56. package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/package.json +4 -1
  57. package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/strnum.js +9 -1
  58. package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
  59. package/node_modules/@nodable/entities/package.json +4 -1
  60. package/node_modules/@nodable/entities/src/EntityDecoder.js +104 -3
  61. package/node_modules/@nodable/entities/src/index.d.ts +77 -0
  62. package/node_modules/@nodable/entities/src/index.js +1 -1
  63. package/node_modules/@smithy/core/dist-cjs/index.js +13 -15
  64. package/node_modules/@smithy/core/dist-cjs/submodules/cbor/index.js +29 -31
  65. package/node_modules/@smithy/core/dist-cjs/submodules/checksum/index.browser.js +2 -4
  66. package/node_modules/@smithy/core/dist-cjs/submodules/checksum/index.js +7 -9
  67. package/node_modules/@smithy/core/dist-cjs/submodules/checksum/index.native.js +3 -5
  68. package/node_modules/@smithy/core/dist-cjs/submodules/client/index.js +22 -23
  69. package/node_modules/@smithy/core/dist-cjs/submodules/config/index.browser.js +10 -11
  70. package/node_modules/@smithy/core/dist-cjs/submodules/config/index.js +38 -39
  71. package/node_modules/@smithy/core/dist-cjs/submodules/config/index.native.js +10 -11
  72. package/node_modules/@smithy/core/dist-cjs/submodules/endpoints/index.browser.js +14 -17
  73. package/node_modules/@smithy/core/dist-cjs/submodules/endpoints/index.js +20 -23
  74. package/node_modules/@smithy/core/dist-cjs/submodules/event-streams/index.browser.js +9 -11
  75. package/node_modules/@smithy/core/dist-cjs/submodules/event-streams/index.js +11 -13
  76. package/node_modules/@smithy/core/dist-cjs/submodules/protocols/index.js +59 -60
  77. package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.browser.js +26 -27
  78. package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.js +28 -29
  79. package/node_modules/@smithy/core/dist-cjs/submodules/schema/index.js +5 -7
  80. package/node_modules/@smithy/core/dist-cjs/submodules/serde/index.browser.js +4 -6
  81. package/node_modules/@smithy/core/dist-cjs/submodules/serde/index.js +23 -25
  82. package/node_modules/@smithy/core/dist-cjs/submodules/serde/index.native.js +4 -6
  83. package/node_modules/@smithy/core/dist-cjs/submodules/transport/index.js +2 -4
  84. package/node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-auth-scheme/httpAuthSchemeMiddleware.js +1 -1
  85. package/node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-signing/httpSigningMiddleware.js +1 -1
  86. package/node_modules/@smithy/core/dist-es/submodules/cbor/SmithyRpcV2CborProtocol.js +1 -1
  87. package/node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/endpointMiddleware.js +1 -1
  88. package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retry-pre-sra-deprecated/StandardRetryStrategy.js +2 -2
  89. package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRateLimiter.js +1 -1
  90. package/node_modules/@smithy/core/dist-types/submodules/schema/TypeRegistry.d.ts +1 -1
  91. package/node_modules/@smithy/core/package.json +18 -17
  92. package/node_modules/@smithy/credential-provider-imds/dist-cjs/index.js +23 -24
  93. package/node_modules/@smithy/credential-provider-imds/package.json +7 -7
  94. package/node_modules/@smithy/fetch-http-handler/dist-cjs/index.js +6 -8
  95. package/node_modules/@smithy/fetch-http-handler/package.json +10 -10
  96. package/node_modules/@smithy/node-http-handler/dist-cjs/index.js +11 -13
  97. package/node_modules/@smithy/node-http-handler/package.json +8 -8
  98. package/node_modules/@smithy/signature-v4/dist-cjs/index.js +27 -29
  99. package/node_modules/@smithy/signature-v4/package.json +7 -7
  100. package/node_modules/@smithy/types/dist-cjs/index.js +23 -18
  101. package/node_modules/@smithy/types/package.json +10 -10
  102. package/node_modules/anynum/LICENSE +21 -0
  103. package/node_modules/anynum/README.md +142 -0
  104. package/node_modules/anynum/anynum.js +135 -0
  105. package/node_modules/anynum/digitTable.js +116 -0
  106. package/node_modules/anynum/package.json +42 -0
  107. package/node_modules/watchpack/README.md +9 -0
  108. package/node_modules/watchpack/lib/DirectoryWatcher.js +182 -25
  109. package/node_modules/watchpack/lib/index.js +55 -15
  110. package/node_modules/watchpack/lib/reducePlan.js +56 -35
  111. package/node_modules/watchpack/lib/util/globToRegExp.js +142 -0
  112. package/node_modules/watchpack/lib/watchEventSource.js +18 -10
  113. package/node_modules/watchpack/package.json +17 -23
  114. package/node_modules/watchpack/types/DirectoryWatcher.d.ts +2 -0
  115. package/node_modules/watchpack/types/index.d.ts +121 -113
  116. package/node_modules/watchpack/types/util/globToRegExp.d.ts +2 -0
  117. package/node_modules/watchpack/types/watchpack.d.ts +1 -1
  118. package/package.json +10 -10
  119. package/node_modules/glob-to-regexp/.travis.yml +0 -4
  120. package/node_modules/glob-to-regexp/README.md +0 -75
  121. package/node_modules/glob-to-regexp/index.js +0 -130
  122. package/node_modules/glob-to-regexp/package.json +0 -23
  123. package/node_modules/glob-to-regexp/test.js +0 -235
  124. /package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/LICENSE +0 -0
@@ -28,13 +28,28 @@ let FS_ACCURACY = 2000;
28
28
  const IS_OSX = require("os").platform() === "darwin";
29
29
  const IS_WIN = require("os").platform() === "win32";
30
30
 
31
- const { WATCHPACK_POLLING } = process.env;
31
+ const { WATCHPACK_POLLING, WATCHPACK_RETRIES } = process.env;
32
32
  const FORCE_POLLING =
33
33
  // @ts-expect-error avoid additional checks
34
34
  `${+WATCHPACK_POLLING}` === WATCHPACK_POLLING
35
35
  ? +WATCHPACK_POLLING
36
36
  : Boolean(WATCHPACK_POLLING) && WATCHPACK_POLLING !== "false";
37
37
 
38
+ // Number of retries (and delay between retries, in ms) when an fs operation
39
+ // returns EBUSY. EBUSY is transient on Windows when an AV scanner, indexer,
40
+ // or another process briefly locks a file; retrying avoids incorrectly
41
+ // reporting the file as removed (see webpack/watchpack#223, #44).
42
+ //
43
+ // Configurable via `WATCHPACK_RETRIES` env var: an integer >= 0, or "false"
44
+ // to disable retries entirely. Unset / invalid values fall back to 3.
45
+ const BUSY_RETRIES = (() => {
46
+ if (WATCHPACK_RETRIES === undefined) return 3;
47
+ if (WATCHPACK_RETRIES === "false") return 0;
48
+ const n = Number(WATCHPACK_RETRIES);
49
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 3;
50
+ })();
51
+ const BUSY_RETRY_DELAY = 100;
52
+
38
53
  /**
39
54
  * @param {string} str string
40
55
  * @returns {string} lower cased string
@@ -77,6 +92,38 @@ function ensureFsAccuracy(mtime) {
77
92
  else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000;
78
93
  }
79
94
 
95
+ /**
96
+ * Call `fs.lstat` with retries on EBUSY. Transient EBUSY errors are common
97
+ * on Windows when another process (AV scanner, indexer, editor) holds an
98
+ * open handle on the file. See webpack/watchpack#223, #44.
99
+ *
100
+ * The retry count is taken from the `WATCHPACK_RETRIES` env var (default
101
+ * 3, set to "0" or "false" to disable retrying). The hot path is a single
102
+ * `fs.lstat` call with one inline callback; the timer and the recursive
103
+ * call are only scheduled when an EBUSY is actually observed.
104
+ * @param {string} target target path
105
+ * @param {{ closed: boolean }} watcher owning watcher (checked between retries)
106
+ * @param {(err: NodeJS.ErrnoException | null, stats: import("fs").Stats) => void} callback callback
107
+ * @param {number=} remaining retries remaining (defaults to `BUSY_RETRIES`)
108
+ */
109
+ function lstatWithRetry(target, watcher, callback, remaining = BUSY_RETRIES) {
110
+ fs.lstat(target, (err, stats) => {
111
+ if (
112
+ err &&
113
+ /** @type {NodeJS.ErrnoException} */ (err).code === "EBUSY" &&
114
+ remaining > 0 &&
115
+ !watcher.closed
116
+ ) {
117
+ setTimeout(
118
+ () => lstatWithRetry(target, watcher, callback, remaining - 1),
119
+ BUSY_RETRY_DELAY,
120
+ );
121
+ return;
122
+ }
123
+ callback(err, stats);
124
+ });
125
+ }
126
+
80
127
  /**
81
128
  * @typedef {object} FileWatcherEvents
82
129
  * @property {(type: EventType) => void} initial-missing initial missing event
@@ -167,6 +214,8 @@ class DirectoryWatcher extends EventEmitter {
167
214
  this.filesWithoutCase = new Map();
168
215
  /** @type {Map<string, Watcher<DirectoryWatcherEvents> | boolean>} */
169
216
  this.directories = new Map();
217
+ /** @type {Map<string, Watcher<FileWatcherEvents>>} */
218
+ this._symlinkTargetWatchers = new Map();
170
219
  this.lastWatchEvent = 0;
171
220
  this.initialScan = true;
172
221
  this.ignored = options.ignored || (() => false);
@@ -427,6 +476,10 @@ class DirectoryWatcher extends EventEmitter {
427
476
  (watcher).close();
428
477
  this.directories.set(directory, true);
429
478
  }
479
+ for (const w of this._symlinkTargetWatchers.values()) {
480
+ w.close();
481
+ }
482
+ this._symlinkTargetWatchers.clear();
430
483
  }
431
484
  }
432
485
  }
@@ -550,7 +603,7 @@ class DirectoryWatcher extends EventEmitter {
550
603
  const checkStats = () => {
551
604
  if (this.closed) return;
552
605
  this._activeEvents.set(filename, false);
553
- fs.lstat(target, (err, stats) => {
606
+ lstatWithRetry(target, this, (err, stats) => {
554
607
  if (this.closed) return;
555
608
  if (this._activeEvents.get(filename) === true) {
556
609
  process.nextTick(checkStats);
@@ -559,6 +612,9 @@ class DirectoryWatcher extends EventEmitter {
559
612
  this._activeEvents.delete(filename);
560
613
  // ENOENT happens when the file/directory doesn't exist
561
614
  // EPERM happens when the containing directory doesn't exist
615
+ // EBUSY happens when another process has the file locked (e.g.
616
+ // Windows AV scanner). lstatWithRetry already retried before
617
+ // giving up here.
562
618
  if (err) {
563
619
  if (
564
620
  err.code !== "ENOENT" &&
@@ -575,6 +631,13 @@ class DirectoryWatcher extends EventEmitter {
575
631
  }
576
632
  this.lastWatchEvent = Date.now();
577
633
  if (!stats) {
634
+ // On EBUSY we keep the tracked state: the file is likely still
635
+ // there and a later event or scan will reconcile. Emitting a
636
+ // remove here would make the watch appear to stop after a
637
+ // transient lock (webpack/watchpack#223, #44).
638
+ if (err && err.code === "EBUSY" && BUSY_RETRIES > 0) {
639
+ return;
640
+ }
578
641
  this.setMissing(target, false, eventType);
579
642
  } else if (stats.isDirectory()) {
580
643
  this.setDirectory(target, +stats.birthtime || 1, false, eventType);
@@ -724,7 +787,20 @@ class DirectoryWatcher extends EventEmitter {
724
787
  fs.readdir(this.path, (err, items) => {
725
788
  if (this.closed) return;
726
789
  if (err) {
727
- if (err.code === "ENOENT" || err.code === "EPERM") {
790
+ // Mirror the lstat error handling below: treat permission /
791
+ // invalid-argument / no-device errors on the directory itself
792
+ // as removed rather than logging "Watchpack Error (initial
793
+ // scan)". These surface for unreadable mounts (WSL `/mnt/c`,
794
+ // fuse mounts), unmounted devices (`/efi`), and libuv's
795
+ // post-Node 22.17 `EINVAL` on protected Windows paths
796
+ // (see #187).
797
+ if (
798
+ err.code === "ENOENT" ||
799
+ err.code === "EPERM" ||
800
+ err.code === "EACCES" ||
801
+ err.code === "ENODEV" ||
802
+ (err.code === "EINVAL" && IS_WIN)
803
+ ) {
728
804
  this.onDirectoryRemoved("scan readdir failed");
729
805
  } else {
730
806
  this.onScanError(err);
@@ -801,7 +877,7 @@ class DirectoryWatcher extends EventEmitter {
801
877
  }
802
878
  });
803
879
  for (const itemPath of itemPaths) {
804
- fs.lstat(itemPath, (err2, stats) => {
880
+ lstatWithRetry(itemPath, this, (err2, stats) => {
805
881
  if (this.closed) return;
806
882
  if (err2) {
807
883
  if (
@@ -809,39 +885,115 @@ class DirectoryWatcher extends EventEmitter {
809
885
  err2.code === "EPERM" ||
810
886
  err2.code === "EACCES" ||
811
887
  err2.code === "EBUSY" ||
888
+ err2.code === "ENODEV" ||
812
889
  // TODO https://github.com/libuv/libuv/pull/4566
813
890
  (err2.code === "EINVAL" && IS_WIN)
814
891
  ) {
815
- this.setMissing(itemPath, initial, `scan (${err2.code})`);
892
+ // readdir saw the entry but we can't stat it due to a
893
+ // transient lock — keep the previously-known entry instead
894
+ // of incorrectly flagging it as missing.
895
+ if (
896
+ !(
897
+ err2.code === "EBUSY" &&
898
+ BUSY_RETRIES > 0 &&
899
+ this.files.has(itemPath)
900
+ )
901
+ ) {
902
+ this.setMissing(itemPath, initial, `scan (${err2.code})`);
903
+ }
816
904
  } else {
817
905
  this.onScanError(err2);
818
906
  }
819
907
  itemFinished();
820
908
  return;
821
909
  }
822
- if (stats.isFile() || stats.isSymbolicLink()) {
823
- if (stats.mtime) {
824
- ensureFsAccuracy(+stats.mtime);
910
+ /**
911
+ * @param {string | null} realPath resolved real path for an outside-dir symlink target
912
+ */
913
+ const apply = (realPath) => {
914
+ if (stats.isFile() || stats.isSymbolicLink()) {
915
+ if (stats.mtime) ensureFsAccuracy(+stats.mtime);
916
+ this.setFileTime(
917
+ itemPath,
918
+ +stats.mtime || +stats.ctime || 1,
919
+ initial,
920
+ true,
921
+ "scan (file)",
922
+ );
923
+ if (realPath && !this._symlinkTargetWatchers.has(itemPath)) {
924
+ const w = this.watcherManager.watchFile(realPath, Date.now());
925
+ if (w) {
926
+ w.on("change", (mtime, type, wInitial) => {
927
+ if (wInitial) return;
928
+ this.setFileTime(itemPath, mtime, false, false, type);
929
+ });
930
+ this._symlinkTargetWatchers.set(itemPath, w);
931
+ }
932
+ }
933
+ } else if (
934
+ stats.isDirectory() &&
935
+ (!initial || !this.directories.has(itemPath))
936
+ ) {
937
+ this.setDirectory(
938
+ itemPath,
939
+ +stats.birthtime || 1,
940
+ initial,
941
+ "scan (dir)",
942
+ );
825
943
  }
826
- this.setFileTime(
827
- itemPath,
828
- +stats.mtime || +stats.ctime || 1,
829
- initial,
830
- true,
831
- "scan (file)",
832
- );
833
- } else if (
834
- stats.isDirectory() &&
835
- (!initial || !this.directories.has(itemPath))
944
+ itemFinished();
945
+ };
946
+ if (
947
+ this.options.followSymlinks &&
948
+ this.nestedWatching &&
949
+ stats.isSymbolicLink()
836
950
  ) {
837
- this.setDirectory(
838
- itemPath,
839
- +stats.birthtime || 1,
840
- initial,
841
- "scan (dir)",
842
- );
951
+ fs.realpath(itemPath, (err3, realPath) => {
952
+ if (this.closed) return;
953
+ if (
954
+ err3 ||
955
+ !realPath ||
956
+ withoutCase(path.dirname(realPath)) === withoutCase(this.path)
957
+ ) {
958
+ apply(null);
959
+ return;
960
+ }
961
+ // Cycle protection: when the symlink's target is the symlink
962
+ // path itself or one of its ancestors, descending would
963
+ // create an unbounded chain of `DirectoryWatcher`s as we walk
964
+ // back into territory we are already watching. Treat the
965
+ // symlink as a plain entry instead so the symlink itself is
966
+ // still tracked but no recursion happens.
967
+ const rel = path.relative(realPath, itemPath);
968
+ if (!path.isAbsolute(rel) && !rel.startsWith("..")) {
969
+ apply(null);
970
+ return;
971
+ }
972
+ fs.stat(realPath, (err4, targetStats) => {
973
+ if (this.closed) return;
974
+ if (err4 || !targetStats) {
975
+ apply(null);
976
+ } else if (targetStats.isFile()) {
977
+ apply(realPath);
978
+ } else if (targetStats.isDirectory()) {
979
+ // Treat a symlink whose target is a directory as a
980
+ // nested watched directory so files inside the target
981
+ // propagate change events to the symlink path.
982
+ this.setDirectory(
983
+ itemPath,
984
+ +targetStats.birthtime || +stats.birthtime || 1,
985
+ initial,
986
+ "scan (dir)",
987
+ );
988
+ itemFinished();
989
+ } else {
990
+ apply(null);
991
+ }
992
+ });
993
+ });
994
+ return;
843
995
  }
844
- itemFinished();
996
+ apply(null);
845
997
  });
846
998
  }
847
999
  itemFinished();
@@ -939,6 +1091,7 @@ class DirectoryWatcher extends EventEmitter {
939
1091
  }
940
1092
 
941
1093
  close() {
1094
+ if (this.closed) return;
942
1095
  this.closed = true;
943
1096
  this.initialScan = false;
944
1097
  if (this.watcher) {
@@ -952,6 +1105,10 @@ class DirectoryWatcher extends EventEmitter {
952
1105
  }
953
1106
  this.directories.clear();
954
1107
  }
1108
+ for (const w of this._symlinkTargetWatchers.values()) {
1109
+ w.close();
1110
+ }
1111
+ this._symlinkTargetWatchers.clear();
955
1112
  if (this.parentWatcher) {
956
1113
  this.parentWatcher.close();
957
1114
  this.parentWatcher = null;
@@ -5,9 +5,9 @@
5
5
  "use strict";
6
6
 
7
7
  const { EventEmitter } = require("events");
8
- const globToRegExp = require("glob-to-regexp");
9
8
  const LinkResolver = require("./LinkResolver");
10
9
  const getWatcherManager = require("./getWatcherManager");
10
+ const globToRegExp = require("./util/globToRegExp");
11
11
  const watchEventSource = require("./watchEventSource");
12
12
 
13
13
  /** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */
@@ -45,8 +45,9 @@ const watchEventSource = require("./watchEventSource");
45
45
  /** @typedef {`scan (${string})` | "change" | "rename" | `watch ${string}` | `directory-removed ${string}`} EventType */
46
46
  /** @typedef {{ safeTime: number, timestamp: number, accuracy: number }} Entry */
47
47
  /** @typedef {{ safeTime: number }} OnlySafeTimeEntry */
48
+
48
49
  // eslint-disable-next-line jsdoc/ts-no-empty-object-type
49
- /** @typedef {{}} ExistenceOnlyTimeEntry */
50
+ /** @typedef {{ }} ExistenceOnlyTimeEntry */
50
51
  /** @typedef {Map<string, Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | null>} TimeInfoEntries */
51
52
  /** @typedef {Set<string>} Changes */
52
53
  /** @typedef {Set<string>} Removals */
@@ -60,10 +61,8 @@ const watchEventSource = require("./watchEventSource");
60
61
  */
61
62
  function addWatchersToSet(watchers, set) {
62
63
  for (const ww of watchers) {
63
- const w = ww.watcher;
64
- if (!set.has(w.directoryWatcher)) {
65
- set.add(w.directoryWatcher);
66
- }
64
+ // Set.add is already idempotent, so skip the redundant has() probe.
65
+ set.add(ww.watcher.directoryWatcher);
67
66
  }
68
67
  }
69
68
 
@@ -75,31 +74,47 @@ const stringToRegexp = (ignored) => {
75
74
  if (ignored.length === 0) {
76
75
  return;
77
76
  }
78
- const { source } = globToRegExp(ignored, { globstar: true, extended: true });
79
- return `${source.slice(0, -1)}(?:$|\\/)`;
77
+ return `^${globToRegExp(ignored)}(?:$|\\/)`;
80
78
  };
81
79
 
80
+ /**
81
+ * Normalizes path separators for regex testing. `String.prototype.replace`
82
+ * always allocates a new string, even when the pattern finds nothing; for
83
+ * POSIX paths (the common case) that allocation is pure overhead. Check for
84
+ * a backslash with `indexOf` first so we skip the copy on paths that are
85
+ * already normalized.
86
+ * @param {string} item item
87
+ * @returns {string} item with backslashes normalized to forward slashes
88
+ */
89
+ const normalizeSeparators = (item) =>
90
+ item.includes("\\") ? item.replace(/\\/g, "/") : item;
91
+
82
92
  /**
83
93
  * @param {Ignored=} ignored ignored
84
94
  * @returns {(item: string) => boolean} ignored to function
85
95
  */
86
96
  const ignoredToFunction = (ignored) => {
87
97
  if (Array.isArray(ignored)) {
88
- const stringRegexps = ignored.map((i) => stringToRegexp(i)).filter(Boolean);
98
+ const stringRegexps =
99
+ /** @type {string[]} */
100
+ (ignored.map((i) => stringToRegexp(i)).filter(Boolean));
89
101
  if (stringRegexps.length === 0) {
90
102
  return () => false;
91
103
  }
92
- const regexp = new RegExp(stringRegexps.join("|"));
93
- return (item) => regexp.test(item.replace(/\\/g, "/"));
104
+ const regexp =
105
+ stringRegexps.length === 1
106
+ ? new RegExp(stringRegexps[0])
107
+ : new RegExp(stringRegexps.join("|"));
108
+ return (item) => regexp.test(normalizeSeparators(item));
94
109
  } else if (typeof ignored === "string") {
95
110
  const stringRegexp = stringToRegexp(ignored);
96
111
  if (!stringRegexp) {
97
112
  return () => false;
98
113
  }
99
114
  const regexp = new RegExp(stringRegexp);
100
- return (item) => regexp.test(item.replace(/\\/g, "/"));
115
+ return (item) => regexp.test(normalizeSeparators(item));
101
116
  } else if (ignored instanceof RegExp) {
102
- return (item) => ignored.test(item.replace(/\\/g, "/"));
117
+ return (item) => ignored.test(normalizeSeparators(item));
103
118
  } else if (typeof ignored === "function") {
104
119
  return ignored;
105
120
  } else if (ignored) {
@@ -463,8 +478,10 @@ class Watchpack extends EventEmitter {
463
478
  /** @type {Record<string, number>} */
464
479
  const obj = Object.create(null);
465
480
  for (const w of directoryWatchers) {
481
+ // getTimes() returns a prototype-less object, so for...in is safe
482
+ // and avoids the throwaway array that Object.keys would allocate.
466
483
  const times = w.getTimes();
467
- for (const file of Object.keys(times)) obj[file] = times[file];
484
+ for (const file in times) obj[file] = times[file];
468
485
  }
469
486
  return obj;
470
487
  }
@@ -551,4 +568,27 @@ class Watchpack extends EventEmitter {
551
568
  }
552
569
  }
553
570
 
554
- module.exports = Watchpack;
571
+ /**
572
+ * @template A
573
+ * @template B
574
+ * @param {A} obj input a
575
+ * @param {B} exports input b
576
+ * @returns {A & B} merged
577
+ */
578
+ const mergeExports = (obj, exports) => {
579
+ const descriptors = Object.getOwnPropertyDescriptors(exports);
580
+ Object.defineProperties(obj, descriptors);
581
+ return /** @type {A & B} */ (Object.freeze(obj));
582
+ };
583
+
584
+ /** @typedef {typeof Watchpack & { util: { readonly globToRegExp: typeof globToRegExp } }} WatchpackExports */
585
+
586
+ module.exports = /** @type {WatchpackExports} */ (
587
+ mergeExports(Watchpack, {
588
+ util: {
589
+ get globToRegExp() {
590
+ return globToRegExp;
591
+ },
592
+ },
593
+ })
594
+ );
@@ -67,45 +67,66 @@ module.exports = (plan, limit) => {
67
67
  }
68
68
  }
69
69
  }
70
- // Reduce until limit reached
71
- while (currentCount > limit) {
72
- // Select node that helps reaching the limit most effectively without overmerging
73
- const overLimit = currentCount - limit;
74
- let bestNode;
75
- let bestCost = Infinity;
70
+ // Reduce until limit reached. When no reduction is needed at all, skip
71
+ // building the candidate set entirely to avoid paying for the setup on the
72
+ // common fast path.
73
+ if (currentCount > limit) {
74
+ // Pre-filter candidate nodes so the inner selection loop skips structural
75
+ // non-candidates entirely. `children` length and parent presence are
76
+ // fixed after tree construction; only `entries` can change (it can only
77
+ // decrease), so a node that fails the `entries` check in a later round
78
+ // is simply skipped via `continue`. When we merge a subtree we drop the
79
+ // descendants from the candidate set to keep it shrinking over
80
+ // iterations.
81
+ /** @type {Set<TreeNode<T>>} */
82
+ const candidates = new Set();
76
83
  for (const node of treeMap.values()) {
77
- if (node.entries <= 1 || !node.children || !node.parent) continue;
84
+ if (!node.parent || !node.children) continue;
78
85
  if (node.children.length === 0) continue;
79
86
  if (node.children.length === 1 && !node.value) continue;
80
- // Try to select the node with has just a bit more entries than we need to reduce
81
- // When just a bit more is over 30% over the limit,
82
- // also consider just a bit less entries then we need to reduce
83
- const cost =
84
- node.entries - 1 >= overLimit
85
- ? node.entries - 1 - overLimit
86
- : overLimit - node.entries + 1 + limit * 0.3;
87
- if (cost < bestCost) {
88
- bestNode = node;
89
- bestCost = cost;
90
- }
91
- }
92
- if (!bestNode) break;
93
- // Merge all children
94
- const reduction = bestNode.entries - 1;
95
- bestNode.active = true;
96
- bestNode.entries = 1;
97
- currentCount -= reduction;
98
- let { parent } = bestNode;
99
- while (parent) {
100
- parent.entries -= reduction;
101
- parent = parent.parent;
87
+ candidates.add(node);
102
88
  }
103
- const queue = new Set(bestNode.children);
104
- for (const node of queue) {
105
- node.active = false;
106
- node.entries = 0;
107
- if (node.children) {
108
- for (const child of node.children) queue.add(child);
89
+ const costBias = limit * 0.3;
90
+ while (currentCount > limit) {
91
+ // Select node that helps reaching the limit most effectively without overmerging
92
+ const overLimit = currentCount - limit;
93
+ let bestNode;
94
+ let bestCost = Infinity;
95
+ for (const node of candidates) {
96
+ if (node.entries <= 1) continue;
97
+ // Try to select the node with has just a bit more entries than we need to reduce
98
+ // When just a bit more is over 30% over the limit,
99
+ // also consider just a bit less entries then we need to reduce
100
+ const diff = node.entries - 1 - overLimit;
101
+ const cost = diff >= 0 ? diff : -diff + costBias;
102
+ if (cost < bestCost) {
103
+ bestNode = node;
104
+ bestCost = cost;
105
+ // A cost of 0 means the merge reduces exactly to the limit;
106
+ // no further candidate can improve on that, so stop scanning.
107
+ if (cost === 0) break;
108
+ }
109
+ }
110
+ if (!bestNode) break;
111
+ // Merge all children
112
+ const reduction = bestNode.entries - 1;
113
+ bestNode.active = true;
114
+ bestNode.entries = 1;
115
+ candidates.delete(bestNode);
116
+ currentCount -= reduction;
117
+ let { parent } = bestNode;
118
+ while (parent) {
119
+ parent.entries -= reduction;
120
+ parent = parent.parent;
121
+ }
122
+ const queue = new Set(bestNode.children);
123
+ for (const node of queue) {
124
+ node.active = false;
125
+ node.entries = 0;
126
+ candidates.delete(node);
127
+ if (node.children) {
128
+ for (const child of node.children) queue.add(child);
129
+ }
109
130
  }
110
131
  }
111
132
  }
@@ -0,0 +1,142 @@
1
+ /*
2
+ MIT License http://www.opensource.org/licenses/mit-license.php
3
+ Author Haijie Xie @hai-x
4
+ */
5
+
6
+ "use strict";
7
+
8
+ // Based on https://github.com/fitzgen/glob-to-regexp (MIT)
9
+ // Specialized for watchpack: `extended` and `globstar` always enabled.
10
+ // Returns the regexp source without `^`/`$` anchors.
11
+
12
+ const CC_EXCLAMATION = 33; // "!"
13
+ const CC_DOLLAR = 36; // "$"
14
+ const CC_LEFT_PARENTHESIS = 40; // "("
15
+ const CC_RIGHT_PARENTHESIS = 41; // ")"
16
+ const CC_ASTERISK = 42; // "*"
17
+ const CC_PLUS = 43; // "+"
18
+ const CC_COMMA = 44; // ","
19
+ const CC_DOT = 46; // "."
20
+ const CC_SLASH = 47; // "/"
21
+ const CC_EQUAL = 61; // "="
22
+ const CC_QUESTION_MARK = 63; // "?"
23
+ const CC_LEFT_BRACKET = 91; // "["
24
+ const CC_RIGHT_BRACKET = 93; // "]"
25
+ const CC_CARET = 94; // "^"
26
+ const CC_LEFT_BRACE = 123; // "{"
27
+ const CC_PIPE = 124; // "|"
28
+ const CC_RIGHT_BRACE = 125; // "}"
29
+
30
+ /**
31
+ * @param {string} glob glob pattern
32
+ * @returns {string} regexp source without anchors
33
+ */
34
+ module.exports = (glob) => {
35
+ if (typeof glob !== "string") {
36
+ throw new TypeError("Expected a string");
37
+ }
38
+
39
+ const len = glob.length;
40
+ let reStr = "";
41
+ let inGroup = false;
42
+ // Start of the current run of literal characters, copied with one slice
43
+ let literalStart = 0;
44
+
45
+ for (let i = 0; i < len; i++) {
46
+ const cc = glob.charCodeAt(i);
47
+ const tokenStart = i;
48
+ let mapped;
49
+
50
+ switch (cc) {
51
+ case CC_SLASH:
52
+ mapped = "\\/";
53
+ break;
54
+ case CC_DOLLAR:
55
+ mapped = "\\$";
56
+ break;
57
+ case CC_CARET:
58
+ mapped = "\\^";
59
+ break;
60
+ case CC_PLUS:
61
+ mapped = "\\+";
62
+ break;
63
+ case CC_DOT:
64
+ mapped = "\\.";
65
+ break;
66
+ case CC_LEFT_PARENTHESIS:
67
+ mapped = "\\(";
68
+ break;
69
+ case CC_RIGHT_PARENTHESIS:
70
+ mapped = "\\)";
71
+ break;
72
+ case CC_EQUAL:
73
+ mapped = "\\=";
74
+ break;
75
+ case CC_EXCLAMATION:
76
+ mapped = "\\!";
77
+ break;
78
+ case CC_PIPE:
79
+ mapped = "\\|";
80
+ break;
81
+ case CC_QUESTION_MARK:
82
+ mapped = ".";
83
+ break;
84
+ case CC_LEFT_BRACKET:
85
+ mapped = "[";
86
+ break;
87
+ case CC_RIGHT_BRACKET:
88
+ mapped = "]";
89
+ break;
90
+ case CC_LEFT_BRACE:
91
+ inGroup = true;
92
+ mapped = "(";
93
+ break;
94
+ case CC_RIGHT_BRACE:
95
+ inGroup = false;
96
+ mapped = ")";
97
+ break;
98
+ case CC_COMMA:
99
+ mapped = inGroup ? "|" : "\\,";
100
+ break;
101
+ case CC_ASTERISK: {
102
+ const atStart = i === 0;
103
+ const afterSlash = !atStart && glob.charCodeAt(i - 1) === CC_SLASH;
104
+ let starCount = 1;
105
+ while (i + 1 < len && glob.charCodeAt(i + 1) === CC_ASTERISK) {
106
+ starCount++;
107
+ i++;
108
+ }
109
+ const atEnd = i + 1 === len;
110
+ const beforeSlash = !atEnd && glob.charCodeAt(i + 1) === CC_SLASH;
111
+ if (
112
+ starCount > 1 &&
113
+ (atStart || afterSlash) &&
114
+ (atEnd || beforeSlash)
115
+ ) {
116
+ // Globstar segment, matches zero or more path segments
117
+ mapped = "((?:[^/]*(?:\\/|$))*)";
118
+ i++; // Move over the "/"
119
+ } else {
120
+ // Not a globstar, matches one path segment
121
+ mapped = "([^/]*)";
122
+ }
123
+ break;
124
+ }
125
+ default:
126
+ // Literal character, extend the current run
127
+ continue;
128
+ }
129
+
130
+ if (literalStart < tokenStart) {
131
+ reStr += glob.slice(literalStart, tokenStart);
132
+ }
133
+ reStr += mapped;
134
+ literalStart = i + 1;
135
+ }
136
+
137
+ if (literalStart < len) {
138
+ reStr += literalStart === 0 ? glob : glob.slice(literalStart);
139
+ }
140
+
141
+ return reStr;
142
+ };