@clayroach/effect 3.19.14-source-capture.8 → 3.19.14-source-trace.2

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 (452) hide show
  1. package/SourceLocation/package.json +6 -0
  2. package/dist/cjs/Effect.js +2 -28
  3. package/dist/cjs/Effect.js.map +1 -1
  4. package/dist/cjs/FiberRef.js +12 -1
  5. package/dist/cjs/FiberRef.js.map +1 -1
  6. package/dist/cjs/Layer.js +2 -24
  7. package/dist/cjs/Layer.js.map +1 -1
  8. package/dist/cjs/RuntimeFlags.js +1 -29
  9. package/dist/cjs/RuntimeFlags.js.map +1 -1
  10. package/dist/cjs/SourceLocation.js +60 -0
  11. package/dist/cjs/SourceLocation.js.map +1 -0
  12. package/dist/cjs/Tracer.js +1 -15
  13. package/dist/cjs/Tracer.js.map +1 -1
  14. package/dist/cjs/Utils.js +1 -1
  15. package/dist/cjs/Utils.js.map +1 -1
  16. package/dist/cjs/index.js +3 -1
  17. package/dist/cjs/index.js.map +1 -1
  18. package/dist/cjs/internal/clock.js +1 -1
  19. package/dist/cjs/internal/clock.js.map +1 -1
  20. package/dist/cjs/internal/core.js +17 -50
  21. package/dist/cjs/internal/core.js.map +1 -1
  22. package/dist/cjs/internal/effect/circular.js +18 -30
  23. package/dist/cjs/internal/effect/circular.js.map +1 -1
  24. package/dist/cjs/internal/fiberRuntime.js +16 -65
  25. package/dist/cjs/internal/fiberRuntime.js.map +1 -1
  26. package/dist/cjs/internal/layer/circular.js +1 -5
  27. package/dist/cjs/internal/layer/circular.js.map +1 -1
  28. package/dist/cjs/internal/layer.js +1 -3
  29. package/dist/cjs/internal/layer.js.map +1 -1
  30. package/dist/cjs/internal/logger.js +25 -2
  31. package/dist/cjs/internal/logger.js.map +1 -1
  32. package/dist/cjs/internal/runtimeFlags.js +2 -11
  33. package/dist/cjs/internal/runtimeFlags.js.map +1 -1
  34. package/dist/cjs/internal/tracer.js +1 -114
  35. package/dist/cjs/internal/tracer.js.map +1 -1
  36. package/dist/dts/Config.d.ts +2 -2
  37. package/dist/dts/Config.d.ts.map +1 -1
  38. package/dist/dts/Effect.d.ts +8 -29
  39. package/dist/dts/Effect.d.ts.map +1 -1
  40. package/dist/dts/FiberRef.d.ts +12 -0
  41. package/dist/dts/FiberRef.d.ts.map +1 -1
  42. package/dist/dts/Layer.d.ts +0 -22
  43. package/dist/dts/Layer.d.ts.map +1 -1
  44. package/dist/dts/RuntimeFlags.d.ts +0 -28
  45. package/dist/dts/RuntimeFlags.d.ts.map +1 -1
  46. package/dist/dts/SourceLocation.d.ts +88 -0
  47. package/dist/dts/SourceLocation.d.ts.map +1 -0
  48. package/dist/dts/Tracer.d.ts +0 -15
  49. package/dist/dts/Tracer.d.ts.map +1 -1
  50. package/dist/dts/index.d.ts +6 -0
  51. package/dist/dts/index.d.ts.map +1 -1
  52. package/dist/dts/internal/core.d.ts.map +1 -1
  53. package/dist/dts/internal/layer.d.ts.map +1 -1
  54. package/dist/dts/internal/runtimeFlags.d.ts.map +1 -1
  55. package/dist/esm/Effect.js +0 -26
  56. package/dist/esm/Effect.js.map +1 -1
  57. package/dist/esm/FiberRef.js +11 -0
  58. package/dist/esm/FiberRef.js.map +1 -1
  59. package/dist/esm/Layer.js +0 -22
  60. package/dist/esm/Layer.js.map +1 -1
  61. package/dist/esm/RuntimeFlags.js +0 -28
  62. package/dist/esm/RuntimeFlags.js.map +1 -1
  63. package/dist/esm/SourceLocation.js +51 -0
  64. package/dist/esm/SourceLocation.js.map +1 -0
  65. package/dist/esm/Tracer.js +0 -14
  66. package/dist/esm/Tracer.js.map +1 -1
  67. package/dist/esm/Utils.js +1 -1
  68. package/dist/esm/Utils.js.map +1 -1
  69. package/dist/esm/index.js +6 -0
  70. package/dist/esm/index.js.map +1 -1
  71. package/dist/esm/internal/clock.js +1 -1
  72. package/dist/esm/internal/clock.js.map +1 -1
  73. package/dist/esm/internal/core.js +12 -45
  74. package/dist/esm/internal/core.js.map +1 -1
  75. package/dist/esm/internal/effect/circular.js +18 -30
  76. package/dist/esm/internal/effect/circular.js.map +1 -1
  77. package/dist/esm/internal/fiberRuntime.js +13 -60
  78. package/dist/esm/internal/fiberRuntime.js.map +1 -1
  79. package/dist/esm/internal/layer/circular.js +0 -4
  80. package/dist/esm/internal/layer/circular.js.map +1 -1
  81. package/dist/esm/internal/layer.js +0 -2
  82. package/dist/esm/internal/layer.js.map +1 -1
  83. package/dist/esm/internal/logger.js +25 -2
  84. package/dist/esm/internal/logger.js.map +1 -1
  85. package/dist/esm/internal/runtimeFlags.js +1 -9
  86. package/dist/esm/internal/runtimeFlags.js.map +1 -1
  87. package/dist/esm/internal/tracer.js +0 -111
  88. package/dist/esm/internal/tracer.js.map +1 -1
  89. package/package.json +12 -1
  90. package/src/Arbitrary.ts +1101 -0
  91. package/src/Array.ts +3589 -0
  92. package/src/BigDecimal.ts +1349 -0
  93. package/src/BigInt.ts +643 -0
  94. package/src/Boolean.ts +287 -0
  95. package/src/Brand.ts +360 -0
  96. package/src/Cache.ts +281 -0
  97. package/src/Cause.ts +1555 -0
  98. package/src/Channel.ts +2355 -0
  99. package/src/ChildExecutorDecision.ts +146 -0
  100. package/src/Chunk.ts +1495 -0
  101. package/src/Clock.ts +111 -0
  102. package/src/Config.ts +542 -0
  103. package/src/ConfigError.ts +270 -0
  104. package/src/ConfigProvider.ts +333 -0
  105. package/src/ConfigProviderPathPatch.ts +100 -0
  106. package/src/Console.ts +226 -0
  107. package/src/Context.ts +585 -0
  108. package/src/Cron.ts +706 -0
  109. package/src/Data.ts +596 -0
  110. package/src/DateTime.ts +1686 -0
  111. package/src/DefaultServices.ts +34 -0
  112. package/src/Deferred.ts +301 -0
  113. package/src/Differ.ts +450 -0
  114. package/src/Duration.ts +1000 -0
  115. package/src/Effect.ts +14817 -0
  116. package/src/Effectable.ts +107 -0
  117. package/src/Either.ts +1040 -0
  118. package/src/Encoding.ts +195 -0
  119. package/src/Equal.ts +98 -0
  120. package/src/Equivalence.ts +235 -0
  121. package/src/ExecutionPlan.ts +308 -0
  122. package/src/ExecutionStrategy.ts +119 -0
  123. package/src/Exit.ts +467 -0
  124. package/src/FastCheck.ts +9 -0
  125. package/src/Fiber.ts +744 -0
  126. package/src/FiberHandle.ts +540 -0
  127. package/src/FiberId.ts +195 -0
  128. package/src/FiberMap.ts +656 -0
  129. package/src/FiberRef.ts +444 -0
  130. package/src/FiberRefs.ts +204 -0
  131. package/src/FiberRefsPatch.ts +105 -0
  132. package/src/FiberSet.ts +491 -0
  133. package/src/FiberStatus.ts +108 -0
  134. package/src/Function.ts +1222 -0
  135. package/src/GlobalValue.ts +53 -0
  136. package/src/Graph.ts +3732 -0
  137. package/src/GroupBy.ts +103 -0
  138. package/src/HKT.ts +45 -0
  139. package/src/Hash.ts +195 -0
  140. package/src/HashMap.ts +519 -0
  141. package/src/HashRing.ts +317 -0
  142. package/src/HashSet.ts +2346 -0
  143. package/src/Inspectable.ts +287 -0
  144. package/src/Iterable.ts +1119 -0
  145. package/src/JSONSchema.ts +1044 -0
  146. package/src/KeyedPool.ts +167 -0
  147. package/src/Layer.ts +1228 -0
  148. package/src/LayerMap.ts +436 -0
  149. package/src/List.ts +977 -0
  150. package/src/LogLevel.ts +285 -0
  151. package/src/LogSpan.ts +25 -0
  152. package/src/Logger.ts +702 -0
  153. package/src/Mailbox.ts +268 -0
  154. package/src/ManagedRuntime.ts +180 -0
  155. package/src/Match.ts +1477 -0
  156. package/src/MergeDecision.ts +95 -0
  157. package/src/MergeState.ts +172 -0
  158. package/src/MergeStrategy.ts +107 -0
  159. package/src/Metric.ts +780 -0
  160. package/src/MetricBoundaries.ts +69 -0
  161. package/src/MetricHook.ts +151 -0
  162. package/src/MetricKey.ts +224 -0
  163. package/src/MetricKeyType.ts +262 -0
  164. package/src/MetricLabel.ts +47 -0
  165. package/src/MetricPair.ts +71 -0
  166. package/src/MetricPolling.ts +148 -0
  167. package/src/MetricRegistry.ts +48 -0
  168. package/src/MetricState.ts +257 -0
  169. package/src/Micro.ts +4405 -0
  170. package/src/ModuleVersion.ts +18 -0
  171. package/src/MutableHashMap.ts +411 -0
  172. package/src/MutableHashSet.ts +706 -0
  173. package/src/MutableList.ts +297 -0
  174. package/src/MutableQueue.ts +227 -0
  175. package/src/MutableRef.ts +202 -0
  176. package/src/NonEmptyIterable.ts +32 -0
  177. package/src/Number.ts +1071 -0
  178. package/src/Option.ts +2170 -0
  179. package/src/Order.ts +373 -0
  180. package/src/Ordering.ts +111 -0
  181. package/src/ParseResult.ts +2031 -0
  182. package/src/PartitionedSemaphore.ts +200 -0
  183. package/src/Pipeable.ts +566 -0
  184. package/src/Pool.ts +204 -0
  185. package/src/Predicate.ts +1405 -0
  186. package/src/Pretty.ts +205 -0
  187. package/src/PrimaryKey.ts +23 -0
  188. package/src/PubSub.ts +182 -0
  189. package/src/Queue.ts +644 -0
  190. package/src/Random.ts +204 -0
  191. package/src/RateLimiter.ts +138 -0
  192. package/src/RcMap.ts +141 -0
  193. package/src/RcRef.ts +122 -0
  194. package/src/Readable.ts +93 -0
  195. package/src/Record.ts +1274 -0
  196. package/src/RedBlackTree.ts +421 -0
  197. package/src/Redacted.ts +144 -0
  198. package/src/Ref.ts +180 -0
  199. package/src/RegExp.ts +38 -0
  200. package/src/Reloadable.ts +127 -0
  201. package/src/Request.ts +347 -0
  202. package/src/RequestBlock.ts +118 -0
  203. package/src/RequestResolver.ts +366 -0
  204. package/src/Resource.ts +119 -0
  205. package/src/Runtime.ts +383 -0
  206. package/src/RuntimeFlags.ts +336 -0
  207. package/src/RuntimeFlagsPatch.ts +183 -0
  208. package/src/STM.ts +2045 -0
  209. package/src/Schedule.ts +2219 -0
  210. package/src/ScheduleDecision.ts +62 -0
  211. package/src/ScheduleInterval.ts +151 -0
  212. package/src/ScheduleIntervals.ts +122 -0
  213. package/src/Scheduler.ts +353 -0
  214. package/src/Schema.ts +10914 -0
  215. package/src/SchemaAST.ts +3043 -0
  216. package/src/Scope.ts +204 -0
  217. package/src/ScopedCache.ts +151 -0
  218. package/src/ScopedRef.ts +117 -0
  219. package/src/Secret.ts +88 -0
  220. package/src/SingleProducerAsyncInput.ts +67 -0
  221. package/src/Sink.ts +1461 -0
  222. package/src/SortedMap.ts +287 -0
  223. package/src/SortedSet.ts +390 -0
  224. package/src/SourceLocation.ts +108 -0
  225. package/src/Stream.ts +6468 -0
  226. package/src/StreamEmit.ts +136 -0
  227. package/src/StreamHaltStrategy.ts +123 -0
  228. package/src/Streamable.ts +45 -0
  229. package/src/String.ts +778 -0
  230. package/src/Struct.ts +243 -0
  231. package/src/Subscribable.ts +100 -0
  232. package/src/SubscriptionRef.ts +298 -0
  233. package/src/Supervisor.ts +240 -0
  234. package/src/Symbol.ts +29 -0
  235. package/src/SynchronizedRef.ts +270 -0
  236. package/src/TArray.ts +495 -0
  237. package/src/TDeferred.ts +100 -0
  238. package/src/TMap.ts +515 -0
  239. package/src/TPriorityQueue.ts +223 -0
  240. package/src/TPubSub.ts +200 -0
  241. package/src/TQueue.ts +432 -0
  242. package/src/TRandom.ts +129 -0
  243. package/src/TReentrantLock.ts +224 -0
  244. package/src/TRef.ts +178 -0
  245. package/src/TSemaphore.ts +129 -0
  246. package/src/TSet.ts +365 -0
  247. package/src/TSubscriptionRef.ts +192 -0
  248. package/src/Take.ts +258 -0
  249. package/src/TestAnnotation.ts +158 -0
  250. package/src/TestAnnotationMap.ts +119 -0
  251. package/src/TestAnnotations.ts +117 -0
  252. package/src/TestClock.ts +556 -0
  253. package/src/TestConfig.ts +47 -0
  254. package/src/TestContext.ts +36 -0
  255. package/src/TestLive.ts +53 -0
  256. package/src/TestServices.ts +390 -0
  257. package/src/TestSized.ts +55 -0
  258. package/src/Tracer.ts +182 -0
  259. package/src/Trie.ts +840 -0
  260. package/src/Tuple.ts +305 -0
  261. package/src/Types.ts +353 -0
  262. package/src/Unify.ts +113 -0
  263. package/src/UpstreamPullRequest.ts +117 -0
  264. package/src/UpstreamPullStrategy.ts +121 -0
  265. package/src/Utils.ts +809 -0
  266. package/src/index.ts +1568 -0
  267. package/src/internal/array.ts +8 -0
  268. package/src/internal/blockedRequests.ts +520 -0
  269. package/src/internal/cache.ts +733 -0
  270. package/src/internal/cause.ts +1050 -0
  271. package/src/internal/channel/channelExecutor.ts +1200 -0
  272. package/src/internal/channel/channelState.ts +134 -0
  273. package/src/internal/channel/childExecutorDecision.ts +96 -0
  274. package/src/internal/channel/continuation.ts +200 -0
  275. package/src/internal/channel/mergeDecision.ts +113 -0
  276. package/src/internal/channel/mergeState.ts +120 -0
  277. package/src/internal/channel/mergeStrategy.ts +72 -0
  278. package/src/internal/channel/singleProducerAsyncInput.ts +259 -0
  279. package/src/internal/channel/subexecutor.ts +229 -0
  280. package/src/internal/channel/upstreamPullRequest.ts +84 -0
  281. package/src/internal/channel/upstreamPullStrategy.ts +87 -0
  282. package/src/internal/channel.ts +2603 -0
  283. package/src/internal/clock.ts +95 -0
  284. package/src/internal/completedRequestMap.ts +9 -0
  285. package/src/internal/concurrency.ts +54 -0
  286. package/src/internal/config.ts +716 -0
  287. package/src/internal/configError.ts +304 -0
  288. package/src/internal/configProvider/pathPatch.ts +97 -0
  289. package/src/internal/configProvider.ts +799 -0
  290. package/src/internal/console.ts +153 -0
  291. package/src/internal/context.ts +337 -0
  292. package/src/internal/core-effect.ts +2293 -0
  293. package/src/internal/core-stream.ts +998 -0
  294. package/src/internal/core.ts +3189 -0
  295. package/src/internal/data.ts +36 -0
  296. package/src/internal/dataSource.ts +327 -0
  297. package/src/internal/dateTime.ts +1277 -0
  298. package/src/internal/defaultServices/console.ts +100 -0
  299. package/src/internal/defaultServices.ts +163 -0
  300. package/src/internal/deferred.ts +46 -0
  301. package/src/internal/differ/chunkPatch.ts +211 -0
  302. package/src/internal/differ/contextPatch.ts +232 -0
  303. package/src/internal/differ/hashMapPatch.ts +220 -0
  304. package/src/internal/differ/hashSetPatch.ts +176 -0
  305. package/src/internal/differ/orPatch.ts +311 -0
  306. package/src/internal/differ/readonlyArrayPatch.ts +210 -0
  307. package/src/internal/differ.ts +200 -0
  308. package/src/internal/doNotation.ts +80 -0
  309. package/src/internal/effect/circular.ts +895 -0
  310. package/src/internal/effectable.ts +131 -0
  311. package/src/internal/either.ts +110 -0
  312. package/src/internal/encoding/base64.ts +286 -0
  313. package/src/internal/encoding/base64Url.ts +29 -0
  314. package/src/internal/encoding/common.ts +51 -0
  315. package/src/internal/encoding/hex.ts +315 -0
  316. package/src/internal/errors.ts +7 -0
  317. package/src/internal/executionPlan.ts +114 -0
  318. package/src/internal/executionStrategy.ts +74 -0
  319. package/src/internal/fiber.ts +388 -0
  320. package/src/internal/fiberId.ts +267 -0
  321. package/src/internal/fiberMessage.ts +82 -0
  322. package/src/internal/fiberRefs/patch.ts +144 -0
  323. package/src/internal/fiberRefs.ts +297 -0
  324. package/src/internal/fiberRuntime.ts +3842 -0
  325. package/src/internal/fiberScope.ts +71 -0
  326. package/src/internal/fiberStatus.ts +119 -0
  327. package/src/internal/groupBy.ts +530 -0
  328. package/src/internal/hashMap/array.ts +49 -0
  329. package/src/internal/hashMap/bitwise.ts +32 -0
  330. package/src/internal/hashMap/config.ts +14 -0
  331. package/src/internal/hashMap/keySet.ts +8 -0
  332. package/src/internal/hashMap/node.ts +391 -0
  333. package/src/internal/hashMap.ts +586 -0
  334. package/src/internal/hashSet.ts +323 -0
  335. package/src/internal/keyedPool.ts +244 -0
  336. package/src/internal/layer/circular.ts +214 -0
  337. package/src/internal/layer.ts +1483 -0
  338. package/src/internal/logSpan.ts +20 -0
  339. package/src/internal/logger-circular.ts +24 -0
  340. package/src/internal/logger.ts +522 -0
  341. package/src/internal/mailbox.ts +561 -0
  342. package/src/internal/managedRuntime/circular.ts +6 -0
  343. package/src/internal/managedRuntime.ts +134 -0
  344. package/src/internal/matcher.ts +652 -0
  345. package/src/internal/metric/boundaries.ts +75 -0
  346. package/src/internal/metric/hook.ts +483 -0
  347. package/src/internal/metric/key.ts +167 -0
  348. package/src/internal/metric/keyType.ts +238 -0
  349. package/src/internal/metric/label.ts +41 -0
  350. package/src/internal/metric/pair.ts +48 -0
  351. package/src/internal/metric/polling.ts +149 -0
  352. package/src/internal/metric/registry.ts +187 -0
  353. package/src/internal/metric/state.ts +290 -0
  354. package/src/internal/metric.ts +577 -0
  355. package/src/internal/opCodes/cause.ts +35 -0
  356. package/src/internal/opCodes/channel.ts +83 -0
  357. package/src/internal/opCodes/channelChildExecutorDecision.ts +17 -0
  358. package/src/internal/opCodes/channelMergeDecision.ts +11 -0
  359. package/src/internal/opCodes/channelMergeState.ts +17 -0
  360. package/src/internal/opCodes/channelMergeStrategy.ts +11 -0
  361. package/src/internal/opCodes/channelState.ts +23 -0
  362. package/src/internal/opCodes/channelUpstreamPullRequest.ts +11 -0
  363. package/src/internal/opCodes/channelUpstreamPullStrategy.ts +11 -0
  364. package/src/internal/opCodes/config.ts +65 -0
  365. package/src/internal/opCodes/configError.ts +35 -0
  366. package/src/internal/opCodes/continuation.ts +11 -0
  367. package/src/internal/opCodes/deferred.ts +11 -0
  368. package/src/internal/opCodes/effect.ts +89 -0
  369. package/src/internal/opCodes/layer.ts +59 -0
  370. package/src/internal/opCodes/streamHaltStrategy.ts +23 -0
  371. package/src/internal/option.ts +80 -0
  372. package/src/internal/pool.ts +432 -0
  373. package/src/internal/pubsub.ts +1762 -0
  374. package/src/internal/query.ts +204 -0
  375. package/src/internal/queue.ts +766 -0
  376. package/src/internal/random.ts +161 -0
  377. package/src/internal/rateLimiter.ts +93 -0
  378. package/src/internal/rcMap.ts +285 -0
  379. package/src/internal/rcRef.ts +192 -0
  380. package/src/internal/redBlackTree/iterator.ts +200 -0
  381. package/src/internal/redBlackTree/node.ts +68 -0
  382. package/src/internal/redBlackTree.ts +1245 -0
  383. package/src/internal/redacted.ts +73 -0
  384. package/src/internal/ref.ts +171 -0
  385. package/src/internal/reloadable.ts +140 -0
  386. package/src/internal/request.ts +177 -0
  387. package/src/internal/resource.ts +76 -0
  388. package/src/internal/ringBuffer.ts +68 -0
  389. package/src/internal/runtime.ts +558 -0
  390. package/src/internal/runtimeFlags.ts +178 -0
  391. package/src/internal/runtimeFlagsPatch.ts +103 -0
  392. package/src/internal/schedule/decision.ts +47 -0
  393. package/src/internal/schedule/interval.ts +101 -0
  394. package/src/internal/schedule/intervals.ts +180 -0
  395. package/src/internal/schedule.ts +2199 -0
  396. package/src/internal/schema/errors.ts +191 -0
  397. package/src/internal/schema/schemaId.ts +106 -0
  398. package/src/internal/schema/util.ts +50 -0
  399. package/src/internal/scopedCache.ts +644 -0
  400. package/src/internal/scopedRef.ts +118 -0
  401. package/src/internal/secret.ts +89 -0
  402. package/src/internal/singleShotGen.ts +35 -0
  403. package/src/internal/sink.ts +2120 -0
  404. package/src/internal/stack.ts +10 -0
  405. package/src/internal/stm/core.ts +817 -0
  406. package/src/internal/stm/entry.ts +59 -0
  407. package/src/internal/stm/journal.ts +123 -0
  408. package/src/internal/stm/opCodes/stm.ts +71 -0
  409. package/src/internal/stm/opCodes/stmState.ts +17 -0
  410. package/src/internal/stm/opCodes/strategy.ts +17 -0
  411. package/src/internal/stm/opCodes/tExit.ts +29 -0
  412. package/src/internal/stm/opCodes/tryCommit.ts +11 -0
  413. package/src/internal/stm/stm.ts +1453 -0
  414. package/src/internal/stm/stmState.ts +136 -0
  415. package/src/internal/stm/tArray.ts +550 -0
  416. package/src/internal/stm/tDeferred.ts +81 -0
  417. package/src/internal/stm/tExit.ts +190 -0
  418. package/src/internal/stm/tMap.ts +824 -0
  419. package/src/internal/stm/tPriorityQueue.ts +267 -0
  420. package/src/internal/stm/tPubSub.ts +551 -0
  421. package/src/internal/stm/tQueue.ts +393 -0
  422. package/src/internal/stm/tRandom.ts +140 -0
  423. package/src/internal/stm/tReentrantLock.ts +352 -0
  424. package/src/internal/stm/tRef.ts +195 -0
  425. package/src/internal/stm/tSemaphore.ts +113 -0
  426. package/src/internal/stm/tSet.ts +259 -0
  427. package/src/internal/stm/tSubscriptionRef.ts +286 -0
  428. package/src/internal/stm/tryCommit.ts +34 -0
  429. package/src/internal/stm/txnId.ts +14 -0
  430. package/src/internal/stm/versioned.ts +4 -0
  431. package/src/internal/stream/debounceState.ts +57 -0
  432. package/src/internal/stream/emit.ts +123 -0
  433. package/src/internal/stream/haltStrategy.ts +94 -0
  434. package/src/internal/stream/handoff.ts +187 -0
  435. package/src/internal/stream/handoffSignal.ts +59 -0
  436. package/src/internal/stream/pull.ts +34 -0
  437. package/src/internal/stream/sinkEndReason.ts +30 -0
  438. package/src/internal/stream/zipAllState.ts +88 -0
  439. package/src/internal/stream/zipChunksState.ts +56 -0
  440. package/src/internal/stream.ts +8801 -0
  441. package/src/internal/string-utils.ts +107 -0
  442. package/src/internal/subscriptionRef.ts +138 -0
  443. package/src/internal/supervisor/patch.ts +190 -0
  444. package/src/internal/supervisor.ts +303 -0
  445. package/src/internal/synchronizedRef.ts +114 -0
  446. package/src/internal/take.ts +199 -0
  447. package/src/internal/testing/sleep.ts +27 -0
  448. package/src/internal/testing/suspendedWarningData.ts +85 -0
  449. package/src/internal/testing/warningData.ts +94 -0
  450. package/src/internal/tracer.ts +150 -0
  451. package/src/internal/trie.ts +722 -0
  452. package/src/internal/version.ts +7 -0
package/src/Graph.ts ADDED
@@ -0,0 +1,3732 @@
1
+ /**
2
+ * @experimental
3
+ * @since 3.18.0
4
+ */
5
+
6
+ import * as Data from "./Data.js"
7
+ import * as Equal from "./Equal.js"
8
+ import { dual } from "./Function.js"
9
+ import * as Hash from "./Hash.js"
10
+ import type { Inspectable } from "./Inspectable.js"
11
+ import { format, NodeInspectSymbol } from "./Inspectable.js"
12
+ import * as Option from "./Option.js"
13
+ import type { Pipeable } from "./Pipeable.js"
14
+ import { pipeArguments } from "./Pipeable.js"
15
+ import type { Mutable } from "./Types.js"
16
+
17
+ /**
18
+ * Unique identifier for Graph instances.
19
+ *
20
+ * @since 3.18.0
21
+ * @category symbol
22
+ */
23
+ export const TypeId: "~effect/Graph" = "~effect/Graph" as const
24
+
25
+ /**
26
+ * Type identifier for Graph instances.
27
+ *
28
+ * @since 3.18.0
29
+ * @category symbol
30
+ */
31
+ export type TypeId = typeof TypeId
32
+
33
+ /**
34
+ * Node index for node identification using plain numbers.
35
+ *
36
+ * @since 3.18.0
37
+ * @category models
38
+ */
39
+ export type NodeIndex = number
40
+
41
+ /**
42
+ * Edge index for edge identification using plain numbers.
43
+ *
44
+ * @since 3.18.0
45
+ * @category models
46
+ */
47
+ export type EdgeIndex = number
48
+
49
+ /**
50
+ * Edge data containing source, target, and user data.
51
+ *
52
+ * @since 3.18.0
53
+ * @category models
54
+ */
55
+ export class Edge<E> extends Data.Class<{
56
+ readonly source: NodeIndex
57
+ readonly target: NodeIndex
58
+ readonly data: E
59
+ }> {}
60
+
61
+ /**
62
+ * Graph type for distinguishing directed and undirected graphs.
63
+ *
64
+ * @since 3.18.0
65
+ * @category models
66
+ */
67
+ export type Kind = "directed" | "undirected"
68
+
69
+ /**
70
+ * Graph prototype interface.
71
+ *
72
+ * @since 3.18.0
73
+ * @category models
74
+ */
75
+ export interface Proto<out N, out E> extends Iterable<readonly [NodeIndex, N]>, Equal.Equal, Pipeable, Inspectable {
76
+ readonly [TypeId]: TypeId
77
+ readonly nodes: Map<NodeIndex, N>
78
+ readonly edges: Map<EdgeIndex, Edge<E>>
79
+ readonly adjacency: Map<NodeIndex, Array<EdgeIndex>>
80
+ readonly reverseAdjacency: Map<NodeIndex, Array<EdgeIndex>>
81
+ nextNodeIndex: NodeIndex
82
+ nextEdgeIndex: EdgeIndex
83
+ isAcyclic: Option.Option<boolean>
84
+ }
85
+
86
+ /**
87
+ * Immutable graph interface.
88
+ *
89
+ * @since 3.18.0
90
+ * @category models
91
+ */
92
+ export interface Graph<out N, out E, T extends Kind = "directed"> extends Proto<N, E> {
93
+ readonly type: T
94
+ readonly mutable: false
95
+ }
96
+
97
+ /**
98
+ * Mutable graph interface.
99
+ *
100
+ * @since 3.18.0
101
+ * @category models
102
+ */
103
+ export interface MutableGraph<out N, out E, T extends Kind = "directed"> extends Proto<N, E> {
104
+ readonly type: T
105
+ readonly mutable: true
106
+ }
107
+
108
+ /**
109
+ * Directed graph type alias.
110
+ *
111
+ * @since 3.18.0
112
+ * @category models
113
+ */
114
+ export type DirectedGraph<N, E> = Graph<N, E, "directed">
115
+
116
+ /**
117
+ * Undirected graph type alias.
118
+ *
119
+ * @since 3.18.0
120
+ * @category models
121
+ */
122
+ export type UndirectedGraph<N, E> = Graph<N, E, "undirected">
123
+
124
+ /**
125
+ * Mutable directed graph type alias.
126
+ *
127
+ * @since 3.18.0
128
+ * @category models
129
+ */
130
+ export type MutableDirectedGraph<N, E> = MutableGraph<N, E, "directed">
131
+
132
+ /**
133
+ * Mutable undirected graph type alias.
134
+ *
135
+ * @since 3.18.0
136
+ * @category models
137
+ */
138
+ export type MutableUndirectedGraph<N, E> = MutableGraph<N, E, "undirected">
139
+
140
+ // =============================================================================
141
+ // Proto Objects
142
+ // =============================================================================
143
+
144
+ /** @internal */
145
+ const ProtoGraph = {
146
+ [TypeId]: TypeId,
147
+ [Symbol.iterator](this: Graph<any, any>) {
148
+ return this.nodes[Symbol.iterator]()
149
+ },
150
+ [NodeInspectSymbol](this: Graph<any, any>) {
151
+ return this.toJSON()
152
+ },
153
+ [Equal.symbol](this: Graph<any, any>, that: Equal.Equal): boolean {
154
+ if (isGraph(that)) {
155
+ if (
156
+ this.nodes.size !== that.nodes.size ||
157
+ this.edges.size !== that.edges.size ||
158
+ this.type !== that.type
159
+ ) {
160
+ return false
161
+ }
162
+ // Compare nodes
163
+ for (const [nodeIndex, nodeData] of this.nodes) {
164
+ if (!that.nodes.has(nodeIndex)) {
165
+ return false
166
+ }
167
+ const otherNodeData = that.nodes.get(nodeIndex)!
168
+ if (!Equal.equals(nodeData, otherNodeData)) {
169
+ return false
170
+ }
171
+ }
172
+ // Compare edges
173
+ for (const [edgeIndex, edgeData] of this.edges) {
174
+ if (!that.edges.has(edgeIndex)) {
175
+ return false
176
+ }
177
+ const otherEdge = that.edges.get(edgeIndex)!
178
+ if (!Equal.equals(edgeData, otherEdge)) {
179
+ return false
180
+ }
181
+ }
182
+ return true
183
+ }
184
+ return false
185
+ },
186
+ [Hash.symbol](this: Graph<any, any>): number {
187
+ let hash = Hash.string("Graph")
188
+ hash = hash ^ Hash.string(this.type)
189
+ hash = hash ^ Hash.number(this.nodes.size)
190
+ hash = hash ^ Hash.number(this.edges.size)
191
+ for (const [nodeIndex, nodeData] of this.nodes) {
192
+ hash = hash ^ (Hash.hash(nodeIndex) + Hash.hash(nodeData))
193
+ }
194
+ for (const [edgeIndex, edgeData] of this.edges) {
195
+ hash = hash ^ (Hash.hash(edgeIndex) + Hash.hash(edgeData))
196
+ }
197
+ return hash
198
+ },
199
+ toJSON(this: Graph<any, any>) {
200
+ return {
201
+ _id: "Graph",
202
+ nodeCount: this.nodes.size,
203
+ edgeCount: this.edges.size,
204
+ type: this.type
205
+ }
206
+ },
207
+ toString(this: Graph<any, any>) {
208
+ return format(this)
209
+ },
210
+ pipe() {
211
+ return pipeArguments(this, arguments)
212
+ }
213
+ }
214
+
215
+ // =============================================================================
216
+ // Errors
217
+ // =============================================================================
218
+
219
+ /**
220
+ * Error thrown when a graph operation fails.
221
+ *
222
+ * @since 3.18.0
223
+ * @category errors
224
+ */
225
+ export class GraphError extends Data.TaggedError("GraphError")<{
226
+ readonly message: string
227
+ }> {}
228
+
229
+ /** @internal */
230
+ const missingNode = (node: number) => new GraphError({ message: `Node ${node} does not exist` })
231
+
232
+ // =============================================================================
233
+ // Constructors
234
+ // =============================================================================
235
+
236
+ /** @internal */
237
+ export const isGraph = (u: unknown): u is Graph<unknown, unknown> => typeof u === "object" && u !== null && TypeId in u
238
+
239
+ /**
240
+ * Creates a directed graph, optionally with initial mutations.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * import { Graph } from "effect"
245
+ *
246
+ * // Directed graph with initial nodes and edges
247
+ * const graph = Graph.directed<string, string>((mutable) => {
248
+ * const a = Graph.addNode(mutable, "A")
249
+ * const b = Graph.addNode(mutable, "B")
250
+ * const c = Graph.addNode(mutable, "C")
251
+ * Graph.addEdge(mutable, a, b, "A->B")
252
+ * Graph.addEdge(mutable, b, c, "B->C")
253
+ * })
254
+ * ```
255
+ *
256
+ * @since 3.18.0
257
+ * @category constructors
258
+ */
259
+ export const directed = <N, E>(mutate?: (mutable: MutableDirectedGraph<N, E>) => void): DirectedGraph<N, E> => {
260
+ const graph: Mutable<DirectedGraph<N, E>> = Object.create(ProtoGraph)
261
+ graph.type = "directed"
262
+ graph.nodes = new Map()
263
+ graph.edges = new Map()
264
+ graph.adjacency = new Map()
265
+ graph.reverseAdjacency = new Map()
266
+ graph.nextNodeIndex = 0
267
+ graph.nextEdgeIndex = 0
268
+ graph.isAcyclic = Option.some(true)
269
+ graph.mutable = false
270
+
271
+ if (mutate) {
272
+ const mutable = beginMutation(graph as DirectedGraph<N, E>)
273
+ mutate(mutable as MutableDirectedGraph<N, E>)
274
+ return endMutation(mutable)
275
+ }
276
+
277
+ return graph
278
+ }
279
+
280
+ /**
281
+ * Creates an undirected graph, optionally with initial mutations.
282
+ *
283
+ * @example
284
+ * ```ts
285
+ * import { Graph } from "effect"
286
+ *
287
+ * // Undirected graph with initial nodes and edges
288
+ * const graph = Graph.undirected<string, string>((mutable) => {
289
+ * const a = Graph.addNode(mutable, "A")
290
+ * const b = Graph.addNode(mutable, "B")
291
+ * const c = Graph.addNode(mutable, "C")
292
+ * Graph.addEdge(mutable, a, b, "A-B")
293
+ * Graph.addEdge(mutable, b, c, "B-C")
294
+ * })
295
+ * ```
296
+ *
297
+ * @since 3.18.0
298
+ * @category constructors
299
+ */
300
+ export const undirected = <N, E>(mutate?: (mutable: MutableUndirectedGraph<N, E>) => void): UndirectedGraph<N, E> => {
301
+ const graph: Mutable<UndirectedGraph<N, E>> = Object.create(ProtoGraph)
302
+ graph.type = "undirected"
303
+ graph.nodes = new Map()
304
+ graph.edges = new Map()
305
+ graph.adjacency = new Map()
306
+ graph.reverseAdjacency = new Map()
307
+ graph.nextNodeIndex = 0
308
+ graph.nextEdgeIndex = 0
309
+ graph.isAcyclic = Option.some(true)
310
+ graph.mutable = false
311
+
312
+ if (mutate) {
313
+ const mutable = beginMutation(graph)
314
+ mutate(mutable as MutableUndirectedGraph<N, E>)
315
+ return endMutation(mutable)
316
+ }
317
+
318
+ return graph
319
+ }
320
+
321
+ // =============================================================================
322
+ // Scoped Mutable API
323
+ // =============================================================================
324
+
325
+ /**
326
+ * Creates a mutable scope for safe graph mutations by copying the data structure.
327
+ *
328
+ * @example
329
+ * ```ts
330
+ * import { Graph } from "effect"
331
+ *
332
+ * const graph = Graph.directed<string, number>()
333
+ * const mutable = Graph.beginMutation(graph)
334
+ * // Now mutable can be safely modified without affecting original graph
335
+ * ```
336
+ *
337
+ * @since 3.18.0
338
+ * @category mutations
339
+ */
340
+ export const beginMutation = <N, E, T extends Kind = "directed">(
341
+ graph: Graph<N, E, T>
342
+ ): MutableGraph<N, E, T> => {
343
+ // Copy adjacency maps with deep cloned arrays
344
+ const adjacency = new Map<NodeIndex, Array<EdgeIndex>>()
345
+ const reverseAdjacency = new Map<NodeIndex, Array<EdgeIndex>>()
346
+
347
+ for (const [nodeIndex, edges] of graph.adjacency) {
348
+ adjacency.set(nodeIndex, [...edges])
349
+ }
350
+
351
+ for (const [nodeIndex, edges] of graph.reverseAdjacency) {
352
+ reverseAdjacency.set(nodeIndex, [...edges])
353
+ }
354
+
355
+ const mutable: Mutable<MutableGraph<N, E, T>> = Object.create(ProtoGraph)
356
+ mutable.type = graph.type
357
+ mutable.nodes = new Map(graph.nodes)
358
+ mutable.edges = new Map(graph.edges)
359
+ mutable.adjacency = adjacency
360
+ mutable.reverseAdjacency = reverseAdjacency
361
+ mutable.nextNodeIndex = graph.nextNodeIndex
362
+ mutable.nextEdgeIndex = graph.nextEdgeIndex
363
+ mutable.isAcyclic = graph.isAcyclic
364
+ mutable.mutable = true
365
+
366
+ return mutable
367
+ }
368
+
369
+ /**
370
+ * Converts a mutable graph back to an immutable graph, ending the mutation scope.
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * import { Graph } from "effect"
375
+ *
376
+ * const graph = Graph.directed<string, number>()
377
+ * const mutable = Graph.beginMutation(graph)
378
+ * // ... perform mutations on mutable ...
379
+ * const newGraph = Graph.endMutation(mutable)
380
+ * ```
381
+ *
382
+ * @since 3.18.0
383
+ * @category mutations
384
+ */
385
+ export const endMutation = <N, E, T extends Kind = "directed">(
386
+ mutable: MutableGraph<N, E, T>
387
+ ): Graph<N, E, T> => {
388
+ const graph: Mutable<Graph<N, E, T>> = Object.create(ProtoGraph)
389
+ graph.type = mutable.type
390
+ graph.nodes = new Map(mutable.nodes)
391
+ graph.edges = new Map(mutable.edges)
392
+ graph.adjacency = mutable.adjacency
393
+ graph.reverseAdjacency = mutable.reverseAdjacency
394
+ graph.nextNodeIndex = mutable.nextNodeIndex
395
+ graph.nextEdgeIndex = mutable.nextEdgeIndex
396
+ graph.isAcyclic = mutable.isAcyclic
397
+ graph.mutable = false
398
+
399
+ return graph
400
+ }
401
+
402
+ /**
403
+ * Performs scoped mutations on a graph, automatically managing the mutation lifecycle.
404
+ *
405
+ * @example
406
+ * ```ts
407
+ * import { Graph } from "effect"
408
+ *
409
+ * const graph = Graph.directed<string, number>()
410
+ * const newGraph = Graph.mutate(graph, (mutable) => {
411
+ * // Safe mutations go here
412
+ * // mutable gets automatically converted back to immutable
413
+ * })
414
+ * ```
415
+ *
416
+ * @since 3.18.0
417
+ * @category mutations
418
+ */
419
+ export const mutate: {
420
+ <N, E, T extends Kind = "directed">(
421
+ f: (mutable: MutableGraph<N, E, T>) => void
422
+ ): (graph: Graph<N, E, T>) => Graph<N, E, T>
423
+ <N, E, T extends Kind = "directed">(
424
+ graph: Graph<N, E, T>,
425
+ f: (mutable: MutableGraph<N, E, T>) => void
426
+ ): Graph<N, E, T>
427
+ } = dual(2, <N, E, T extends Kind = "directed">(
428
+ graph: Graph<N, E, T>,
429
+ f: (mutable: MutableGraph<N, E, T>) => void
430
+ ): Graph<N, E, T> => {
431
+ const mutable = beginMutation(graph)
432
+ f(mutable)
433
+ return endMutation(mutable)
434
+ })
435
+
436
+ // =============================================================================
437
+ // Basic Node Operations
438
+ // =============================================================================
439
+
440
+ /**
441
+ * Adds a new node to a mutable graph and returns its index.
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * import { Graph } from "effect"
446
+ *
447
+ * const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
448
+ * const nodeA = Graph.addNode(mutable, "Node A")
449
+ * const nodeB = Graph.addNode(mutable, "Node B")
450
+ * console.log(nodeA) // NodeIndex with value 0
451
+ * console.log(nodeB) // NodeIndex with value 1
452
+ * })
453
+ * ```
454
+ *
455
+ * @since 3.18.0
456
+ * @category mutations
457
+ */
458
+ export const addNode = <N, E, T extends Kind = "directed">(
459
+ mutable: MutableGraph<N, E, T>,
460
+ data: N
461
+ ): NodeIndex => {
462
+ const nodeIndex = mutable.nextNodeIndex
463
+
464
+ // Add node data
465
+ mutable.nodes.set(nodeIndex, data)
466
+
467
+ // Initialize empty adjacency lists
468
+ mutable.adjacency.set(nodeIndex, [])
469
+ mutable.reverseAdjacency.set(nodeIndex, [])
470
+
471
+ // Update graph allocators
472
+ mutable.nextNodeIndex = mutable.nextNodeIndex + 1
473
+
474
+ return nodeIndex
475
+ }
476
+
477
+ /**
478
+ * Gets the data associated with a node index, if it exists.
479
+ *
480
+ * @example
481
+ * ```ts
482
+ * import { Graph, Option } from "effect"
483
+ *
484
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
485
+ * Graph.addNode(mutable, "Node A")
486
+ * })
487
+ *
488
+ * const nodeIndex = 0
489
+ * const nodeData = Graph.getNode(graph, nodeIndex)
490
+ *
491
+ * if (Option.isSome(nodeData)) {
492
+ * console.log(nodeData.value) // "Node A"
493
+ * }
494
+ * ```
495
+ *
496
+ * @since 3.18.0
497
+ * @category getters
498
+ */
499
+ export const getNode = <N, E, T extends Kind = "directed">(
500
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
501
+ nodeIndex: NodeIndex
502
+ ): Option.Option<N> => graph.nodes.has(nodeIndex) ? Option.some(graph.nodes.get(nodeIndex)!) : Option.none()
503
+
504
+ /**
505
+ * Checks if a node with the given index exists in the graph.
506
+ *
507
+ * @example
508
+ * ```ts
509
+ * import { Graph } from "effect"
510
+ *
511
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
512
+ * Graph.addNode(mutable, "Node A")
513
+ * })
514
+ *
515
+ * const nodeIndex = 0
516
+ * const exists = Graph.hasNode(graph, nodeIndex)
517
+ * console.log(exists) // true
518
+ *
519
+ * const nonExistentIndex = 999
520
+ * const notExists = Graph.hasNode(graph, nonExistentIndex)
521
+ * console.log(notExists) // false
522
+ * ```
523
+ *
524
+ * @since 3.18.0
525
+ * @category getters
526
+ */
527
+ export const hasNode = <N, E, T extends Kind = "directed">(
528
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
529
+ nodeIndex: NodeIndex
530
+ ): boolean => graph.nodes.has(nodeIndex)
531
+
532
+ /**
533
+ * Returns the number of nodes in the graph.
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * import { Graph } from "effect"
538
+ *
539
+ * const emptyGraph = Graph.directed<string, number>()
540
+ * console.log(Graph.nodeCount(emptyGraph)) // 0
541
+ *
542
+ * const graphWithNodes = Graph.mutate(emptyGraph, (mutable) => {
543
+ * Graph.addNode(mutable, "Node A")
544
+ * Graph.addNode(mutable, "Node B")
545
+ * Graph.addNode(mutable, "Node C")
546
+ * })
547
+ *
548
+ * console.log(Graph.nodeCount(graphWithNodes)) // 3
549
+ * ```
550
+ *
551
+ * @since 3.18.0
552
+ * @category getters
553
+ */
554
+ export const nodeCount = <N, E, T extends Kind = "directed">(
555
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>
556
+ ): number => graph.nodes.size
557
+
558
+ /**
559
+ * Finds the first node that matches the given predicate.
560
+ *
561
+ * @example
562
+ * ```ts
563
+ * import { Graph, Option } from "effect"
564
+ *
565
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
566
+ * Graph.addNode(mutable, "Node A")
567
+ * Graph.addNode(mutable, "Node B")
568
+ * Graph.addNode(mutable, "Node C")
569
+ * })
570
+ *
571
+ * const result = Graph.findNode(graph, (data) => data.startsWith("Node B"))
572
+ * console.log(result) // Option.some(1)
573
+ *
574
+ * const notFound = Graph.findNode(graph, (data) => data === "Node D")
575
+ * console.log(notFound) // Option.none()
576
+ * ```
577
+ *
578
+ * @since 3.18.0
579
+ * @category getters
580
+ */
581
+ export const findNode = <N, E, T extends Kind = "directed">(
582
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
583
+ predicate: (data: N) => boolean
584
+ ): Option.Option<NodeIndex> => {
585
+ for (const [index, data] of graph.nodes) {
586
+ if (predicate(data)) {
587
+ return Option.some(index)
588
+ }
589
+ }
590
+ return Option.none()
591
+ }
592
+
593
+ /**
594
+ * Finds all nodes that match the given predicate.
595
+ *
596
+ * @example
597
+ * ```ts
598
+ * import { Graph } from "effect"
599
+ *
600
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
601
+ * Graph.addNode(mutable, "Start A")
602
+ * Graph.addNode(mutable, "Node B")
603
+ * Graph.addNode(mutable, "Start C")
604
+ * })
605
+ *
606
+ * const result = Graph.findNodes(graph, (data) => data.startsWith("Start"))
607
+ * console.log(result) // [0, 2]
608
+ *
609
+ * const empty = Graph.findNodes(graph, (data) => data === "Not Found")
610
+ * console.log(empty) // []
611
+ * ```
612
+ *
613
+ * @since 3.18.0
614
+ * @category getters
615
+ */
616
+ export const findNodes = <N, E, T extends Kind = "directed">(
617
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
618
+ predicate: (data: N) => boolean
619
+ ): Array<NodeIndex> => {
620
+ const results: Array<NodeIndex> = []
621
+ for (const [index, data] of graph.nodes) {
622
+ if (predicate(data)) {
623
+ results.push(index)
624
+ }
625
+ }
626
+ return results
627
+ }
628
+
629
+ /**
630
+ * Finds the first edge that matches the given predicate.
631
+ *
632
+ * @example
633
+ * ```ts
634
+ * import { Graph, Option } from "effect"
635
+ *
636
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
637
+ * const nodeA = Graph.addNode(mutable, "Node A")
638
+ * const nodeB = Graph.addNode(mutable, "Node B")
639
+ * const nodeC = Graph.addNode(mutable, "Node C")
640
+ * Graph.addEdge(mutable, nodeA, nodeB, 10)
641
+ * Graph.addEdge(mutable, nodeB, nodeC, 20)
642
+ * })
643
+ *
644
+ * const result = Graph.findEdge(graph, (data) => data > 15)
645
+ * console.log(result) // Option.some(1)
646
+ *
647
+ * const notFound = Graph.findEdge(graph, (data) => data > 100)
648
+ * console.log(notFound) // Option.none()
649
+ * ```
650
+ *
651
+ * @since 3.18.0
652
+ * @category getters
653
+ */
654
+ export const findEdge = <N, E, T extends Kind = "directed">(
655
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
656
+ predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean
657
+ ): Option.Option<EdgeIndex> => {
658
+ for (const [edgeIndex, edgeData] of graph.edges) {
659
+ if (predicate(edgeData.data, edgeData.source, edgeData.target)) {
660
+ return Option.some(edgeIndex)
661
+ }
662
+ }
663
+ return Option.none()
664
+ }
665
+
666
+ /**
667
+ * Finds all edges that match the given predicate.
668
+ *
669
+ * @example
670
+ * ```ts
671
+ * import { Graph } from "effect"
672
+ *
673
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
674
+ * const nodeA = Graph.addNode(mutable, "Node A")
675
+ * const nodeB = Graph.addNode(mutable, "Node B")
676
+ * const nodeC = Graph.addNode(mutable, "Node C")
677
+ * Graph.addEdge(mutable, nodeA, nodeB, 10)
678
+ * Graph.addEdge(mutable, nodeB, nodeC, 20)
679
+ * Graph.addEdge(mutable, nodeC, nodeA, 30)
680
+ * })
681
+ *
682
+ * const result = Graph.findEdges(graph, (data) => data >= 20)
683
+ * console.log(result) // [1, 2]
684
+ *
685
+ * const empty = Graph.findEdges(graph, (data) => data > 100)
686
+ * console.log(empty) // []
687
+ * ```
688
+ *
689
+ * @since 3.18.0
690
+ * @category getters
691
+ */
692
+ export const findEdges = <N, E, T extends Kind = "directed">(
693
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
694
+ predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean
695
+ ): Array<EdgeIndex> => {
696
+ const results: Array<EdgeIndex> = []
697
+ for (const [edgeIndex, edgeData] of graph.edges) {
698
+ if (predicate(edgeData.data, edgeData.source, edgeData.target)) {
699
+ results.push(edgeIndex)
700
+ }
701
+ }
702
+ return results
703
+ }
704
+
705
+ /**
706
+ * Updates a single node's data by applying a transformation function.
707
+ *
708
+ * @example
709
+ * ```ts
710
+ * import { Graph } from "effect"
711
+ *
712
+ * const graph = Graph.directed<string, number>((mutable) => {
713
+ * Graph.addNode(mutable, "Node A")
714
+ * Graph.addNode(mutable, "Node B")
715
+ * Graph.updateNode(mutable, 0, (data) => data.toUpperCase())
716
+ * })
717
+ *
718
+ * const nodeData = Graph.getNode(graph, 0)
719
+ * console.log(nodeData) // Option.some("NODE A")
720
+ * ```
721
+ *
722
+ * @since 3.18.0
723
+ * @category transformations
724
+ */
725
+ export const updateNode = <N, E, T extends Kind = "directed">(
726
+ mutable: MutableGraph<N, E, T>,
727
+ index: NodeIndex,
728
+ f: (data: N) => N
729
+ ): void => {
730
+ if (!mutable.nodes.has(index)) {
731
+ return
732
+ }
733
+
734
+ const currentData = mutable.nodes.get(index)!
735
+ const newData = f(currentData)
736
+ mutable.nodes.set(index, newData)
737
+ }
738
+
739
+ /**
740
+ * Updates a single edge's data by applying a transformation function.
741
+ *
742
+ * @example
743
+ * ```ts
744
+ * import { Graph } from "effect"
745
+ *
746
+ * const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
747
+ * const nodeA = Graph.addNode(mutable, "Node A")
748
+ * const nodeB = Graph.addNode(mutable, "Node B")
749
+ * const edgeIndex = Graph.addEdge(mutable, nodeA, nodeB, 10)
750
+ * Graph.updateEdge(mutable, edgeIndex, (data) => data * 2)
751
+ * })
752
+ *
753
+ * const edgeData = Graph.getEdge(result, 0)
754
+ * console.log(edgeData) // Option.some({ source: 0, target: 1, data: 20 })
755
+ * ```
756
+ *
757
+ * @since 3.18.0
758
+ * @category mutations
759
+ */
760
+ export const updateEdge = <N, E, T extends Kind = "directed">(
761
+ mutable: MutableGraph<N, E, T>,
762
+ edgeIndex: EdgeIndex,
763
+ f: (data: E) => E
764
+ ): void => {
765
+ if (!mutable.edges.has(edgeIndex)) {
766
+ return
767
+ }
768
+
769
+ const currentEdge = mutable.edges.get(edgeIndex)!
770
+ const newData = f(currentEdge.data)
771
+ mutable.edges.set(edgeIndex, {
772
+ ...currentEdge,
773
+ data: newData
774
+ })
775
+ }
776
+
777
+ /**
778
+ * Creates a new graph with transformed node data using the provided mapping function.
779
+ *
780
+ * @example
781
+ * ```ts
782
+ * import { Graph } from "effect"
783
+ *
784
+ * const graph = Graph.directed<string, number>((mutable) => {
785
+ * Graph.addNode(mutable, "node a")
786
+ * Graph.addNode(mutable, "node b")
787
+ * Graph.addNode(mutable, "node c")
788
+ * Graph.mapNodes(mutable, (data) => data.toUpperCase())
789
+ * })
790
+ *
791
+ * const nodeData = Graph.getNode(graph, 0)
792
+ * console.log(nodeData) // Option.some("NODE A")
793
+ * ```
794
+ *
795
+ * @since 3.18.0
796
+ * @category transformations
797
+ */
798
+ export const mapNodes = <N, E, T extends Kind = "directed">(
799
+ mutable: MutableGraph<N, E, T>,
800
+ f: (data: N) => N
801
+ ): void => {
802
+ // Transform existing node data in place
803
+ for (const [index, data] of mutable.nodes) {
804
+ const newData = f(data)
805
+ mutable.nodes.set(index, newData)
806
+ }
807
+ }
808
+
809
+ /**
810
+ * Transforms all edge data in a mutable graph using the provided mapping function.
811
+ *
812
+ * @example
813
+ * ```ts
814
+ * import { Graph } from "effect"
815
+ *
816
+ * const graph = Graph.directed<string, number>((mutable) => {
817
+ * const a = Graph.addNode(mutable, "A")
818
+ * const b = Graph.addNode(mutable, "B")
819
+ * const c = Graph.addNode(mutable, "C")
820
+ * Graph.addEdge(mutable, a, b, 10)
821
+ * Graph.addEdge(mutable, b, c, 20)
822
+ * Graph.mapEdges(mutable, (data) => data * 2)
823
+ * })
824
+ *
825
+ * const edgeData = Graph.getEdge(graph, 0)
826
+ * console.log(edgeData) // Option.some({ source: 0, target: 1, data: 20 })
827
+ * ```
828
+ *
829
+ * @since 3.18.0
830
+ * @category transformations
831
+ */
832
+ export const mapEdges = <N, E, T extends Kind = "directed">(
833
+ mutable: MutableGraph<N, E, T>,
834
+ f: (data: E) => E
835
+ ): void => {
836
+ // Transform existing edge data in place
837
+ for (const [index, edgeData] of mutable.edges) {
838
+ const newData = f(edgeData.data)
839
+ mutable.edges.set(index, {
840
+ ...edgeData,
841
+ data: newData
842
+ })
843
+ }
844
+ }
845
+
846
+ /**
847
+ * Reverses all edge directions in a mutable graph by swapping source and target nodes.
848
+ *
849
+ * @example
850
+ * ```ts
851
+ * import { Graph } from "effect"
852
+ *
853
+ * const graph = Graph.directed<string, number>((mutable) => {
854
+ * const a = Graph.addNode(mutable, "A")
855
+ * const b = Graph.addNode(mutable, "B")
856
+ * const c = Graph.addNode(mutable, "C")
857
+ * Graph.addEdge(mutable, a, b, 1) // A -> B
858
+ * Graph.addEdge(mutable, b, c, 2) // B -> C
859
+ * Graph.reverse(mutable) // Now B -> A, C -> B
860
+ * })
861
+ *
862
+ * const edge0 = Graph.getEdge(graph, 0)
863
+ * console.log(edge0) // Option.some({ source: 1, target: 0, data: 1 }) - B -> A
864
+ * ```
865
+ *
866
+ * @since 3.18.0
867
+ * @category transformations
868
+ */
869
+ export const reverse = <N, E, T extends Kind = "directed">(
870
+ mutable: MutableGraph<N, E, T>
871
+ ): void => {
872
+ // Reverse all edges by swapping source and target
873
+ for (const [index, edgeData] of mutable.edges) {
874
+ mutable.edges.set(index, {
875
+ source: edgeData.target,
876
+ target: edgeData.source,
877
+ data: edgeData.data
878
+ })
879
+ }
880
+
881
+ // Clear and rebuild adjacency lists with reversed directions
882
+ mutable.adjacency.clear()
883
+ mutable.reverseAdjacency.clear()
884
+
885
+ // Rebuild adjacency lists with reversed directions
886
+ for (const [edgeIndex, edgeData] of mutable.edges) {
887
+ // Add to forward adjacency (source -> target)
888
+ const sourceEdges = mutable.adjacency.get(edgeData.source) || []
889
+ sourceEdges.push(edgeIndex)
890
+ mutable.adjacency.set(edgeData.source, sourceEdges)
891
+
892
+ // Add to reverse adjacency (target <- source)
893
+ const targetEdges = mutable.reverseAdjacency.get(edgeData.target) || []
894
+ targetEdges.push(edgeIndex)
895
+ mutable.reverseAdjacency.set(edgeData.target, targetEdges)
896
+ }
897
+
898
+ // Invalidate cycle flag since edge directions changed
899
+ mutable.isAcyclic = Option.none()
900
+ }
901
+
902
+ /**
903
+ * Filters and optionally transforms nodes in a mutable graph using a predicate function.
904
+ * Nodes that return Option.none are removed along with all their connected edges.
905
+ *
906
+ * @example
907
+ * ```ts
908
+ * import { Graph, Option } from "effect"
909
+ *
910
+ * const graph = Graph.directed<string, number>((mutable) => {
911
+ * const a = Graph.addNode(mutable, "active")
912
+ * const b = Graph.addNode(mutable, "inactive")
913
+ * const c = Graph.addNode(mutable, "active")
914
+ * Graph.addEdge(mutable, a, b, 1)
915
+ * Graph.addEdge(mutable, b, c, 2)
916
+ *
917
+ * // Keep only "active" nodes and transform to uppercase
918
+ * Graph.filterMapNodes(mutable, (data) =>
919
+ * data === "active" ? Option.some(data.toUpperCase()) : Option.none()
920
+ * )
921
+ * })
922
+ *
923
+ * console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain)
924
+ * ```
925
+ *
926
+ * @since 3.18.0
927
+ * @category transformations
928
+ */
929
+ export const filterMapNodes = <N, E, T extends Kind = "directed">(
930
+ mutable: MutableGraph<N, E, T>,
931
+ f: (data: N) => Option.Option<N>
932
+ ): void => {
933
+ const nodesToRemove: Array<NodeIndex> = []
934
+
935
+ // First pass: identify nodes to remove and transform data for nodes to keep
936
+ for (const [index, data] of mutable.nodes) {
937
+ const result = f(data)
938
+ if (Option.isSome(result)) {
939
+ // Transform node data
940
+ mutable.nodes.set(index, result.value)
941
+ } else {
942
+ // Mark for removal
943
+ nodesToRemove.push(index)
944
+ }
945
+ }
946
+
947
+ // Second pass: remove filtered out nodes and their edges
948
+ for (const nodeIndex of nodesToRemove) {
949
+ removeNode(mutable, nodeIndex)
950
+ }
951
+ }
952
+
953
+ /**
954
+ * Filters and optionally transforms edges in a mutable graph using a predicate function.
955
+ * Edges that return Option.none are removed from the graph.
956
+ *
957
+ * @example
958
+ * ```ts
959
+ * import { Graph, Option } from "effect"
960
+ *
961
+ * const graph = Graph.directed<string, number>((mutable) => {
962
+ * const a = Graph.addNode(mutable, "A")
963
+ * const b = Graph.addNode(mutable, "B")
964
+ * const c = Graph.addNode(mutable, "C")
965
+ * Graph.addEdge(mutable, a, b, 5)
966
+ * Graph.addEdge(mutable, b, c, 15)
967
+ * Graph.addEdge(mutable, c, a, 25)
968
+ *
969
+ * // Keep only edges with weight >= 10 and double their weight
970
+ * Graph.filterMapEdges(mutable, (data) =>
971
+ * data >= 10 ? Option.some(data * 2) : Option.none()
972
+ * )
973
+ * })
974
+ *
975
+ * console.log(Graph.edgeCount(graph)) // 2 (edges with weight 5 removed)
976
+ * ```
977
+ *
978
+ * @since 3.18.0
979
+ * @category transformations
980
+ */
981
+ export const filterMapEdges = <N, E, T extends Kind = "directed">(
982
+ mutable: MutableGraph<N, E, T>,
983
+ f: (data: E) => Option.Option<E>
984
+ ): void => {
985
+ const edgesToRemove: Array<EdgeIndex> = []
986
+
987
+ // First pass: identify edges to remove and transform data for edges to keep
988
+ for (const [index, edgeData] of mutable.edges) {
989
+ const result = f(edgeData.data)
990
+ if (Option.isSome(result)) {
991
+ // Transform edge data
992
+ mutable.edges.set(index, {
993
+ ...edgeData,
994
+ data: result.value
995
+ })
996
+ } else {
997
+ // Mark for removal
998
+ edgesToRemove.push(index)
999
+ }
1000
+ }
1001
+
1002
+ // Second pass: remove filtered out edges
1003
+ for (const edgeIndex of edgesToRemove) {
1004
+ removeEdge(mutable, edgeIndex)
1005
+ }
1006
+ }
1007
+
1008
+ /**
1009
+ * Filters nodes by removing those that don't match the predicate.
1010
+ * This function modifies the mutable graph in place.
1011
+ *
1012
+ * @example
1013
+ * ```ts
1014
+ * import { Graph } from "effect"
1015
+ *
1016
+ * const graph = Graph.directed<string, number>((mutable) => {
1017
+ * Graph.addNode(mutable, "active")
1018
+ * Graph.addNode(mutable, "inactive")
1019
+ * Graph.addNode(mutable, "pending")
1020
+ * Graph.addNode(mutable, "active")
1021
+ *
1022
+ * // Keep only "active" nodes
1023
+ * Graph.filterNodes(mutable, (data) => data === "active")
1024
+ * })
1025
+ *
1026
+ * console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain)
1027
+ * ```
1028
+ *
1029
+ * @since 3.18.0
1030
+ * @category transformations
1031
+ */
1032
+ export const filterNodes = <N, E, T extends Kind = "directed">(
1033
+ mutable: MutableGraph<N, E, T>,
1034
+ predicate: (data: N) => boolean
1035
+ ): void => {
1036
+ const nodesToRemove: Array<NodeIndex> = []
1037
+
1038
+ // Identify nodes to remove
1039
+ for (const [index, data] of mutable.nodes) {
1040
+ if (!predicate(data)) {
1041
+ nodesToRemove.push(index)
1042
+ }
1043
+ }
1044
+
1045
+ // Remove filtered out nodes (this also removes connected edges)
1046
+ for (const nodeIndex of nodesToRemove) {
1047
+ removeNode(mutable, nodeIndex)
1048
+ }
1049
+ }
1050
+
1051
+ /**
1052
+ * Filters edges by removing those that don't match the predicate.
1053
+ * This function modifies the mutable graph in place.
1054
+ *
1055
+ * @example
1056
+ * ```ts
1057
+ * import { Graph } from "effect"
1058
+ *
1059
+ * const graph = Graph.directed<string, number>((mutable) => {
1060
+ * const a = Graph.addNode(mutable, "A")
1061
+ * const b = Graph.addNode(mutable, "B")
1062
+ * const c = Graph.addNode(mutable, "C")
1063
+ *
1064
+ * Graph.addEdge(mutable, a, b, 5)
1065
+ * Graph.addEdge(mutable, b, c, 15)
1066
+ * Graph.addEdge(mutable, c, a, 25)
1067
+ *
1068
+ * // Keep only edges with weight >= 10
1069
+ * Graph.filterEdges(mutable, (data) => data >= 10)
1070
+ * })
1071
+ *
1072
+ * console.log(Graph.edgeCount(graph)) // 2 (edge with weight 5 removed)
1073
+ * ```
1074
+ *
1075
+ * @since 3.18.0
1076
+ * @category transformations
1077
+ */
1078
+ export const filterEdges = <N, E, T extends Kind = "directed">(
1079
+ mutable: MutableGraph<N, E, T>,
1080
+ predicate: (data: E) => boolean
1081
+ ): void => {
1082
+ const edgesToRemove: Array<EdgeIndex> = []
1083
+
1084
+ // Identify edges to remove
1085
+ for (const [index, edgeData] of mutable.edges) {
1086
+ if (!predicate(edgeData.data)) {
1087
+ edgesToRemove.push(index)
1088
+ }
1089
+ }
1090
+
1091
+ // Remove filtered out edges
1092
+ for (const edgeIndex of edgesToRemove) {
1093
+ removeEdge(mutable, edgeIndex)
1094
+ }
1095
+ }
1096
+
1097
+ // =============================================================================
1098
+ // Cycle Flag Management (Internal)
1099
+ // =============================================================================
1100
+
1101
+ /** @internal */
1102
+ const invalidateCycleFlagOnRemoval = <N, E, T extends Kind = "directed">(
1103
+ mutable: MutableGraph<N, E, T>
1104
+ ): void => {
1105
+ // Only invalidate if the graph had cycles (removing edges/nodes cannot introduce cycles in acyclic graphs)
1106
+ // If already unknown (null) or acyclic (true), no need to change
1107
+ if (Option.isSome(mutable.isAcyclic) && mutable.isAcyclic.value === false) {
1108
+ mutable.isAcyclic = Option.none()
1109
+ }
1110
+ }
1111
+
1112
+ /** @internal */
1113
+ const invalidateCycleFlagOnAddition = <N, E, T extends Kind = "directed">(
1114
+ mutable: MutableGraph<N, E, T>
1115
+ ): void => {
1116
+ // Only invalidate if the graph was acyclic (adding edges cannot remove cycles from cyclic graphs)
1117
+ // If already unknown (null) or cyclic (false), no need to change
1118
+ if (Option.isSome(mutable.isAcyclic) && mutable.isAcyclic.value === true) {
1119
+ mutable.isAcyclic = Option.none()
1120
+ }
1121
+ }
1122
+
1123
+ // =============================================================================
1124
+ // Edge Operations
1125
+ // =============================================================================
1126
+
1127
+ /**
1128
+ * Adds a new edge to a mutable graph and returns its index.
1129
+ *
1130
+ * @example
1131
+ * ```ts
1132
+ * import { Graph } from "effect"
1133
+ *
1134
+ * const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1135
+ * const nodeA = Graph.addNode(mutable, "Node A")
1136
+ * const nodeB = Graph.addNode(mutable, "Node B")
1137
+ * const edge = Graph.addEdge(mutable, nodeA, nodeB, 42)
1138
+ * console.log(edge) // EdgeIndex with value 0
1139
+ * })
1140
+ * ```
1141
+ *
1142
+ * @since 3.18.0
1143
+ * @category mutations
1144
+ */
1145
+ export const addEdge = <N, E, T extends Kind = "directed">(
1146
+ mutable: MutableGraph<N, E, T>,
1147
+ source: NodeIndex,
1148
+ target: NodeIndex,
1149
+ data: E
1150
+ ): EdgeIndex => {
1151
+ // Validate that both nodes exist
1152
+ if (!mutable.nodes.has(source)) {
1153
+ throw missingNode(source)
1154
+ }
1155
+ if (!mutable.nodes.has(target)) {
1156
+ throw missingNode(target)
1157
+ }
1158
+
1159
+ const edgeIndex = mutable.nextEdgeIndex
1160
+
1161
+ // Create edge data
1162
+ const edgeData = new Edge({ source, target, data })
1163
+ mutable.edges.set(edgeIndex, edgeData)
1164
+
1165
+ // Update adjacency lists
1166
+ const sourceAdjacency = mutable.adjacency.get(source)
1167
+ if (sourceAdjacency !== undefined) {
1168
+ sourceAdjacency.push(edgeIndex)
1169
+ }
1170
+
1171
+ const targetReverseAdjacency = mutable.reverseAdjacency.get(target)
1172
+ if (targetReverseAdjacency !== undefined) {
1173
+ targetReverseAdjacency.push(edgeIndex)
1174
+ }
1175
+
1176
+ // For undirected graphs, add reverse connections
1177
+ if (mutable.type === "undirected") {
1178
+ const targetAdjacency = mutable.adjacency.get(target)
1179
+ if (targetAdjacency !== undefined) {
1180
+ targetAdjacency.push(edgeIndex)
1181
+ }
1182
+
1183
+ const sourceReverseAdjacency = mutable.reverseAdjacency.get(source)
1184
+ if (sourceReverseAdjacency !== undefined) {
1185
+ sourceReverseAdjacency.push(edgeIndex)
1186
+ }
1187
+ }
1188
+
1189
+ // Update allocators
1190
+ mutable.nextEdgeIndex = mutable.nextEdgeIndex + 1
1191
+
1192
+ // Only invalidate cycle flag if the graph was acyclic
1193
+ // Adding edges cannot remove cycles from cyclic graphs
1194
+ invalidateCycleFlagOnAddition(mutable)
1195
+
1196
+ return edgeIndex
1197
+ }
1198
+
1199
+ /**
1200
+ * Removes a node and all its incident edges from a mutable graph.
1201
+ *
1202
+ * @example
1203
+ * ```ts
1204
+ * import { Graph } from "effect"
1205
+ *
1206
+ * const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1207
+ * const nodeA = Graph.addNode(mutable, "Node A")
1208
+ * const nodeB = Graph.addNode(mutable, "Node B")
1209
+ * Graph.addEdge(mutable, nodeA, nodeB, 42)
1210
+ *
1211
+ * // Remove nodeA and all edges connected to it
1212
+ * Graph.removeNode(mutable, nodeA)
1213
+ * })
1214
+ * ```
1215
+ *
1216
+ * @since 3.18.0
1217
+ * @category mutations
1218
+ */
1219
+ export const removeNode = <N, E, T extends Kind = "directed">(
1220
+ mutable: MutableGraph<N, E, T>,
1221
+ nodeIndex: NodeIndex
1222
+ ): void => {
1223
+ // Check if node exists
1224
+ if (!mutable.nodes.has(nodeIndex)) {
1225
+ return // Node doesn't exist, nothing to remove
1226
+ }
1227
+
1228
+ // Collect all incident edges for removal
1229
+ const edgesToRemove: Array<EdgeIndex> = []
1230
+
1231
+ // Get outgoing edges
1232
+ const outgoingEdges = mutable.adjacency.get(nodeIndex)
1233
+ if (outgoingEdges !== undefined) {
1234
+ for (const edge of outgoingEdges) {
1235
+ edgesToRemove.push(edge)
1236
+ }
1237
+ }
1238
+
1239
+ // Get incoming edges
1240
+ const incomingEdges = mutable.reverseAdjacency.get(nodeIndex)
1241
+ if (incomingEdges !== undefined) {
1242
+ for (const edge of incomingEdges) {
1243
+ edgesToRemove.push(edge)
1244
+ }
1245
+ }
1246
+
1247
+ // Remove all incident edges
1248
+ for (const edgeIndex of edgesToRemove) {
1249
+ removeEdgeInternal(mutable, edgeIndex)
1250
+ }
1251
+
1252
+ // Remove the node itself
1253
+ mutable.nodes.delete(nodeIndex)
1254
+ mutable.adjacency.delete(nodeIndex)
1255
+ mutable.reverseAdjacency.delete(nodeIndex)
1256
+
1257
+ // Only invalidate cycle flag if the graph wasn't already known to be acyclic
1258
+ // Removing nodes cannot introduce cycles in an acyclic graph
1259
+ invalidateCycleFlagOnRemoval(mutable)
1260
+ }
1261
+
1262
+ /**
1263
+ * Removes an edge from a mutable graph.
1264
+ *
1265
+ * @example
1266
+ * ```ts
1267
+ * import { Graph } from "effect"
1268
+ *
1269
+ * const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1270
+ * const nodeA = Graph.addNode(mutable, "Node A")
1271
+ * const nodeB = Graph.addNode(mutable, "Node B")
1272
+ * const edge = Graph.addEdge(mutable, nodeA, nodeB, 42)
1273
+ *
1274
+ * // Remove the edge
1275
+ * Graph.removeEdge(mutable, edge)
1276
+ * })
1277
+ * ```
1278
+ *
1279
+ * @since 3.18.0
1280
+ * @category mutations
1281
+ */
1282
+ export const removeEdge = <N, E, T extends Kind = "directed">(
1283
+ mutable: MutableGraph<N, E, T>,
1284
+ edgeIndex: EdgeIndex
1285
+ ): void => {
1286
+ const wasRemoved = removeEdgeInternal(mutable, edgeIndex)
1287
+
1288
+ // Only invalidate cycle flag if an edge was actually removed
1289
+ // and only if the graph wasn't already known to be acyclic
1290
+ if (wasRemoved) {
1291
+ invalidateCycleFlagOnRemoval(mutable)
1292
+ }
1293
+ }
1294
+
1295
+ /** @internal */
1296
+ const removeEdgeInternal = <N, E, T extends Kind = "directed">(
1297
+ mutable: MutableGraph<N, E, T>,
1298
+ edgeIndex: EdgeIndex
1299
+ ): boolean => {
1300
+ // Get edge data
1301
+ const edge = mutable.edges.get(edgeIndex)
1302
+ if (edge === undefined) {
1303
+ return false // Edge doesn't exist, no mutation occurred
1304
+ }
1305
+
1306
+ const { source, target } = edge
1307
+
1308
+ // Remove from adjacency lists
1309
+ const sourceAdjacency = mutable.adjacency.get(source)
1310
+ if (sourceAdjacency !== undefined) {
1311
+ const index = sourceAdjacency.indexOf(edgeIndex)
1312
+ if (index !== -1) {
1313
+ sourceAdjacency.splice(index, 1)
1314
+ }
1315
+ }
1316
+
1317
+ const targetReverseAdjacency = mutable.reverseAdjacency.get(target)
1318
+ if (targetReverseAdjacency !== undefined) {
1319
+ const index = targetReverseAdjacency.indexOf(edgeIndex)
1320
+ if (index !== -1) {
1321
+ targetReverseAdjacency.splice(index, 1)
1322
+ }
1323
+ }
1324
+
1325
+ // For undirected graphs, remove reverse connections
1326
+ if (mutable.type === "undirected") {
1327
+ const targetAdjacency = mutable.adjacency.get(target)
1328
+ if (targetAdjacency !== undefined) {
1329
+ const index = targetAdjacency.indexOf(edgeIndex)
1330
+ if (index !== -1) {
1331
+ targetAdjacency.splice(index, 1)
1332
+ }
1333
+ }
1334
+
1335
+ const sourceReverseAdjacency = mutable.reverseAdjacency.get(source)
1336
+ if (sourceReverseAdjacency !== undefined) {
1337
+ const index = sourceReverseAdjacency.indexOf(edgeIndex)
1338
+ if (index !== -1) {
1339
+ sourceReverseAdjacency.splice(index, 1)
1340
+ }
1341
+ }
1342
+ }
1343
+
1344
+ // Remove edge data
1345
+ mutable.edges.delete(edgeIndex)
1346
+
1347
+ return true // Edge was successfully removed
1348
+ }
1349
+
1350
+ // =============================================================================
1351
+ // Edge Query Operations
1352
+ // =============================================================================
1353
+
1354
+ /**
1355
+ * Gets the edge data associated with an edge index, if it exists.
1356
+ *
1357
+ * @example
1358
+ * ```ts
1359
+ * import { Graph, Option } from "effect"
1360
+ *
1361
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1362
+ * const nodeA = Graph.addNode(mutable, "Node A")
1363
+ * const nodeB = Graph.addNode(mutable, "Node B")
1364
+ * Graph.addEdge(mutable, nodeA, nodeB, 42)
1365
+ * })
1366
+ *
1367
+ * const edgeIndex = 0
1368
+ * const edgeData = Graph.getEdge(graph, edgeIndex)
1369
+ *
1370
+ * if (Option.isSome(edgeData)) {
1371
+ * console.log(edgeData.value.data) // 42
1372
+ * console.log(edgeData.value.source) // NodeIndex(0)
1373
+ * console.log(edgeData.value.target) // NodeIndex(1)
1374
+ * }
1375
+ * ```
1376
+ *
1377
+ * @since 3.18.0
1378
+ * @category getters
1379
+ */
1380
+ export const getEdge = <N, E, T extends Kind = "directed">(
1381
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
1382
+ edgeIndex: EdgeIndex
1383
+ ): Option.Option<Edge<E>> => graph.edges.has(edgeIndex) ? Option.some(graph.edges.get(edgeIndex)!) : Option.none()
1384
+
1385
+ /**
1386
+ * Checks if an edge exists between two nodes in the graph.
1387
+ *
1388
+ * @example
1389
+ * ```ts
1390
+ * import { Graph } from "effect"
1391
+ *
1392
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1393
+ * const nodeA = Graph.addNode(mutable, "Node A")
1394
+ * const nodeB = Graph.addNode(mutable, "Node B")
1395
+ * const nodeC = Graph.addNode(mutable, "Node C")
1396
+ * Graph.addEdge(mutable, nodeA, nodeB, 42)
1397
+ * })
1398
+ *
1399
+ * const nodeA = 0
1400
+ * const nodeB = 1
1401
+ * const nodeC = 2
1402
+ *
1403
+ * const hasAB = Graph.hasEdge(graph, nodeA, nodeB)
1404
+ * console.log(hasAB) // true
1405
+ *
1406
+ * const hasAC = Graph.hasEdge(graph, nodeA, nodeC)
1407
+ * console.log(hasAC) // false
1408
+ * ```
1409
+ *
1410
+ * @since 3.18.0
1411
+ * @category getters
1412
+ */
1413
+ export const hasEdge = <N, E, T extends Kind = "directed">(
1414
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
1415
+ source: NodeIndex,
1416
+ target: NodeIndex
1417
+ ): boolean => {
1418
+ const adjacencyList = graph.adjacency.get(source)
1419
+ if (adjacencyList === undefined) {
1420
+ return false
1421
+ }
1422
+
1423
+ // Check if any edge in the adjacency list connects to the target
1424
+ for (const edgeIndex of adjacencyList) {
1425
+ const edge = graph.edges.get(edgeIndex)
1426
+ if (edge !== undefined && edge.target === target) {
1427
+ return true
1428
+ }
1429
+ }
1430
+
1431
+ return false
1432
+ }
1433
+
1434
+ /**
1435
+ * Returns the number of edges in the graph.
1436
+ *
1437
+ * @example
1438
+ * ```ts
1439
+ * import { Graph } from "effect"
1440
+ *
1441
+ * const emptyGraph = Graph.directed<string, number>()
1442
+ * console.log(Graph.edgeCount(emptyGraph)) // 0
1443
+ *
1444
+ * const graphWithEdges = Graph.mutate(emptyGraph, (mutable) => {
1445
+ * const nodeA = Graph.addNode(mutable, "Node A")
1446
+ * const nodeB = Graph.addNode(mutable, "Node B")
1447
+ * const nodeC = Graph.addNode(mutable, "Node C")
1448
+ * Graph.addEdge(mutable, nodeA, nodeB, 1)
1449
+ * Graph.addEdge(mutable, nodeB, nodeC, 2)
1450
+ * Graph.addEdge(mutable, nodeC, nodeA, 3)
1451
+ * })
1452
+ *
1453
+ * console.log(Graph.edgeCount(graphWithEdges)) // 3
1454
+ * ```
1455
+ *
1456
+ * @since 3.18.0
1457
+ * @category getters
1458
+ */
1459
+ export const edgeCount = <N, E, T extends Kind = "directed">(
1460
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>
1461
+ ): number => graph.edges.size
1462
+
1463
+ /**
1464
+ * Returns the neighboring nodes (targets of outgoing edges) for a given node.
1465
+ *
1466
+ * @example
1467
+ * ```ts
1468
+ * import { Graph } from "effect"
1469
+ *
1470
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1471
+ * const nodeA = Graph.addNode(mutable, "Node A")
1472
+ * const nodeB = Graph.addNode(mutable, "Node B")
1473
+ * const nodeC = Graph.addNode(mutable, "Node C")
1474
+ * Graph.addEdge(mutable, nodeA, nodeB, 1)
1475
+ * Graph.addEdge(mutable, nodeA, nodeC, 2)
1476
+ * })
1477
+ *
1478
+ * const nodeA = 0
1479
+ * const nodeB = 1
1480
+ * const nodeC = 2
1481
+ *
1482
+ * const neighborsA = Graph.neighbors(graph, nodeA)
1483
+ * console.log(neighborsA) // [NodeIndex(1), NodeIndex(2)]
1484
+ *
1485
+ * const neighborsB = Graph.neighbors(graph, nodeB)
1486
+ * console.log(neighborsB) // []
1487
+ * ```
1488
+ *
1489
+ * @since 3.18.0
1490
+ * @category getters
1491
+ */
1492
+ export const neighbors = <N, E, T extends Kind = "directed">(
1493
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
1494
+ nodeIndex: NodeIndex
1495
+ ): Array<NodeIndex> => {
1496
+ // For undirected graphs, use the specialized helper that returns the other endpoint
1497
+ if (graph.type === "undirected") {
1498
+ return getUndirectedNeighbors(graph as any, nodeIndex)
1499
+ }
1500
+
1501
+ const adjacencyList = graph.adjacency.get(nodeIndex)
1502
+ if (adjacencyList === undefined) {
1503
+ return []
1504
+ }
1505
+
1506
+ const result: Array<NodeIndex> = []
1507
+ for (const edgeIndex of adjacencyList) {
1508
+ const edge = graph.edges.get(edgeIndex)
1509
+ if (edge !== undefined) {
1510
+ result.push(edge.target)
1511
+ }
1512
+ }
1513
+
1514
+ return result
1515
+ }
1516
+
1517
+ /**
1518
+ * Get neighbors of a node in a specific direction for bidirectional traversal.
1519
+ *
1520
+ * @example
1521
+ * ```ts
1522
+ * import { Graph } from "effect"
1523
+ *
1524
+ * const graph = Graph.directed<string, string>((mutable) => {
1525
+ * const a = Graph.addNode(mutable, "A")
1526
+ * const b = Graph.addNode(mutable, "B")
1527
+ * Graph.addEdge(mutable, a, b, "A->B")
1528
+ * })
1529
+ *
1530
+ * const nodeA = 0
1531
+ * const nodeB = 1
1532
+ *
1533
+ * // Get outgoing neighbors (nodes that nodeA points to)
1534
+ * const outgoing = Graph.neighborsDirected(graph, nodeA, "outgoing")
1535
+ *
1536
+ * // Get incoming neighbors (nodes that point to nodeB)
1537
+ * const incoming = Graph.neighborsDirected(graph, nodeB, "incoming")
1538
+ * ```
1539
+ *
1540
+ * @since 3.18.0
1541
+ * @category queries
1542
+ */
1543
+ export const neighborsDirected = <N, E, T extends Kind = "directed">(
1544
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
1545
+ nodeIndex: NodeIndex,
1546
+ direction: Direction
1547
+ ): Array<NodeIndex> => {
1548
+ const adjacencyMap = direction === "incoming"
1549
+ ? graph.reverseAdjacency
1550
+ : graph.adjacency
1551
+
1552
+ const adjacencyList = adjacencyMap.get(nodeIndex)
1553
+ if (adjacencyList === undefined) {
1554
+ return []
1555
+ }
1556
+
1557
+ const result: Array<NodeIndex> = []
1558
+ for (const edgeIndex of adjacencyList) {
1559
+ const edge = graph.edges.get(edgeIndex)
1560
+ if (edge !== undefined) {
1561
+ // For incoming direction, we want the source node instead of target
1562
+ const neighborNode = direction === "incoming"
1563
+ ? edge.source
1564
+ : edge.target
1565
+ result.push(neighborNode)
1566
+ }
1567
+ }
1568
+
1569
+ return result
1570
+ }
1571
+
1572
+ // =============================================================================
1573
+ // GraphViz Export
1574
+ // =============================================================================
1575
+
1576
+ /**
1577
+ * Configuration options for GraphViz DOT format generation from graphs.
1578
+ *
1579
+ * @since 3.18.0
1580
+ * @category models
1581
+ */
1582
+ export interface GraphVizOptions<N, E> {
1583
+ readonly nodeLabel?: (data: N) => string
1584
+ readonly edgeLabel?: (data: E) => string
1585
+ readonly graphName?: string
1586
+ }
1587
+
1588
+ /**
1589
+ * Exports a graph to GraphViz DOT format for visualization.
1590
+ *
1591
+ * @example
1592
+ * ```ts
1593
+ * import { Graph } from "effect"
1594
+ *
1595
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1596
+ * const nodeA = Graph.addNode(mutable, "Node A")
1597
+ * const nodeB = Graph.addNode(mutable, "Node B")
1598
+ * const nodeC = Graph.addNode(mutable, "Node C")
1599
+ * Graph.addEdge(mutable, nodeA, nodeB, 1)
1600
+ * Graph.addEdge(mutable, nodeB, nodeC, 2)
1601
+ * Graph.addEdge(mutable, nodeC, nodeA, 3)
1602
+ * })
1603
+ *
1604
+ * const dot = Graph.toGraphViz(graph)
1605
+ * console.log(dot)
1606
+ * // digraph G {
1607
+ * // "0" [label="Node A"];
1608
+ * // "1" [label="Node B"];
1609
+ * // "2" [label="Node C"];
1610
+ * // "0" -> "1" [label="1"];
1611
+ * // "1" -> "2" [label="2"];
1612
+ * // "2" -> "0" [label="3"];
1613
+ * // }
1614
+ * ```
1615
+ *
1616
+ * @since 3.18.0
1617
+ * @category utils
1618
+ */
1619
+ export const toGraphViz = <N, E, T extends Kind = "directed">(
1620
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
1621
+ options?: GraphVizOptions<N, E>
1622
+ ): string => {
1623
+ const {
1624
+ edgeLabel = (data: E) => String(data),
1625
+ graphName = "G",
1626
+ nodeLabel = (data: N) => String(data)
1627
+ } = options ?? {}
1628
+
1629
+ const isDirected = graph.type === "directed"
1630
+ const graphType = isDirected ? "digraph" : "graph"
1631
+ const edgeOperator = isDirected ? "->" : "--"
1632
+
1633
+ const lines: Array<string> = []
1634
+ lines.push(`${graphType} ${graphName} {`)
1635
+
1636
+ // Add nodes
1637
+ for (const [nodeIndex, nodeData] of graph.nodes) {
1638
+ const label = nodeLabel(nodeData).replace(/"/g, "\\\"")
1639
+ lines.push(` "${nodeIndex}" [label="${label}"];`)
1640
+ }
1641
+
1642
+ // Add edges
1643
+ for (const [, edgeData] of graph.edges) {
1644
+ const label = edgeLabel(edgeData.data).replace(/"/g, "\\\"")
1645
+ lines.push(` "${edgeData.source}" ${edgeOperator} "${edgeData.target}" [label="${label}"];`)
1646
+ }
1647
+
1648
+ lines.push("}")
1649
+ return lines.join("\n")
1650
+ }
1651
+
1652
+ // =============================================================================
1653
+ // Mermaid Export
1654
+ // =============================================================================
1655
+
1656
+ /**
1657
+ * Mermaid node shape types.
1658
+ *
1659
+ * @since 3.18.0
1660
+ * @category models
1661
+ */
1662
+ export type MermaidNodeShape =
1663
+ | "rectangle"
1664
+ | "rounded"
1665
+ | "circle"
1666
+ | "diamond"
1667
+ | "hexagon"
1668
+ | "stadium"
1669
+ | "subroutine"
1670
+ | "cylindrical"
1671
+
1672
+ /**
1673
+ * Mermaid diagram direction types.
1674
+ *
1675
+ * @since 3.18.0
1676
+ * @category models
1677
+ */
1678
+ export type MermaidDirection = "TB" | "TD" | "BT" | "LR" | "RL"
1679
+
1680
+ /**
1681
+ * Mermaid diagram type.
1682
+ *
1683
+ * @since 3.18.0
1684
+ * @category models
1685
+ */
1686
+ export type MermaidDiagramType = "flowchart" | "graph"
1687
+
1688
+ /**
1689
+ * Configuration options for Mermaid diagram generation.
1690
+ *
1691
+ * @since 3.18.0
1692
+ * @category models
1693
+ */
1694
+ export interface MermaidOptions<N, E> {
1695
+ readonly nodeLabel?: (data: N) => string
1696
+ readonly edgeLabel?: (data: E) => string
1697
+ readonly diagramType?: MermaidDiagramType
1698
+ readonly direction?: MermaidDirection
1699
+ readonly nodeShape?: (data: N) => MermaidNodeShape
1700
+ }
1701
+
1702
+ /** @internal */
1703
+ const escapeMermaidLabel = (label: string): string => {
1704
+ // Escape special characters for Mermaid using HTML entity codes
1705
+ // According to: https://mermaid.js.org/syntax/flowchart.html#special-characters-that-break-syntax
1706
+ return label
1707
+ .replace(/#/g, "#35;")
1708
+ .replace(/"/g, "#quot;")
1709
+ .replace(/</g, "#lt;")
1710
+ .replace(/>/g, "#gt;")
1711
+ .replace(/&/g, "#amp;")
1712
+ .replace(/\[/g, "#91;")
1713
+ .replace(/\]/g, "#93;")
1714
+ .replace(/\{/g, "#123;")
1715
+ .replace(/\}/g, "#125;")
1716
+ .replace(/\(/g, "#40;")
1717
+ .replace(/\)/g, "#41;")
1718
+ .replace(/\|/g, "#124;")
1719
+ .replace(/\\/g, "#92;")
1720
+ .replace(/\n/g, "<br/>")
1721
+ }
1722
+
1723
+ /** @internal */
1724
+ const formatMermaidNode = (nodeId: string, label: string, shape: MermaidNodeShape): string => {
1725
+ switch (shape) {
1726
+ case "rectangle":
1727
+ return `${nodeId}["${label}"]`
1728
+ case "rounded":
1729
+ return `${nodeId}("${label}")`
1730
+ case "circle":
1731
+ return `${nodeId}(("${label}"))`
1732
+ case "diamond":
1733
+ return `${nodeId}{"${label}"}`
1734
+ case "hexagon":
1735
+ return `${nodeId}{{"${label}"}}`
1736
+ case "stadium":
1737
+ return `${nodeId}(["${label}"])`
1738
+ case "subroutine":
1739
+ return `${nodeId}[["${label}"]]`
1740
+ case "cylindrical":
1741
+ return `${nodeId}[("${label}")]`
1742
+ }
1743
+ }
1744
+
1745
+ /**
1746
+ * Exports a graph to Mermaid diagram format for visualization.
1747
+ *
1748
+ * @example
1749
+ * ```ts
1750
+ * import { Graph } from "effect"
1751
+ *
1752
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1753
+ * const app = Graph.addNode(mutable, "App")
1754
+ * const db = Graph.addNode(mutable, "Database")
1755
+ * const cache = Graph.addNode(mutable, "Cache")
1756
+ * Graph.addEdge(mutable, app, db, 1)
1757
+ * Graph.addEdge(mutable, app, cache, 2)
1758
+ * })
1759
+ *
1760
+ * const mermaid = Graph.toMermaid(graph)
1761
+ * console.log(mermaid)
1762
+ * // flowchart TD
1763
+ * // 0["App"]
1764
+ * // 1["Database"]
1765
+ * // 2["Cache"]
1766
+ * // 0 -->|"1"| 1
1767
+ * // 0 -->|"2"| 2
1768
+ * ```
1769
+ *
1770
+ * @since 3.18.0
1771
+ * @category utils
1772
+ */
1773
+ export const toMermaid = <N, E, T extends Kind = "directed">(
1774
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
1775
+ options?: MermaidOptions<N, E>
1776
+ ): string => {
1777
+ // Extract and validate options with defaults
1778
+ const {
1779
+ diagramType,
1780
+ direction = "TD",
1781
+ edgeLabel = (data: E) => String(data),
1782
+ nodeLabel = (data: N) => String(data),
1783
+ nodeShape = () => "rectangle" as const
1784
+ } = options ?? {}
1785
+
1786
+ // Auto-detect diagram type if not specified
1787
+ const finalDiagramType = diagramType ??
1788
+ (graph.type === "directed" ? "flowchart" : "graph")
1789
+
1790
+ // Generate diagram header
1791
+ const lines: Array<string> = []
1792
+ lines.push(`${finalDiagramType} ${direction}`)
1793
+
1794
+ // Add nodes
1795
+ for (const [nodeIndex, nodeData] of graph.nodes) {
1796
+ const nodeId = String(nodeIndex)
1797
+ const label = escapeMermaidLabel(nodeLabel(nodeData))
1798
+ const shape = nodeShape(nodeData)
1799
+ const formattedNode = formatMermaidNode(nodeId, label, shape)
1800
+ lines.push(` ${formattedNode}`)
1801
+ }
1802
+
1803
+ // Add edges
1804
+ const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---"
1805
+ for (const [, edgeData] of graph.edges) {
1806
+ const sourceId = String(edgeData.source)
1807
+ const targetId = String(edgeData.target)
1808
+ const label = escapeMermaidLabel(edgeLabel(edgeData.data))
1809
+
1810
+ if (label) {
1811
+ lines.push(` ${sourceId} ${edgeOperator}|"${label}"| ${targetId}`)
1812
+ } else {
1813
+ lines.push(` ${sourceId} ${edgeOperator} ${targetId}`)
1814
+ }
1815
+ }
1816
+
1817
+ return lines.join("\n")
1818
+ }
1819
+
1820
+ // =============================================================================
1821
+ // Direction Types for Bidirectional Traversal
1822
+ // =============================================================================
1823
+
1824
+ /**
1825
+ * Direction for graph traversal, indicating which edges to follow.
1826
+ *
1827
+ * @example
1828
+ * ```ts
1829
+ * import { Graph } from "effect"
1830
+ *
1831
+ * const graph = Graph.directed<string, string>((mutable) => {
1832
+ * const a = Graph.addNode(mutable, "A")
1833
+ * const b = Graph.addNode(mutable, "B")
1834
+ * Graph.addEdge(mutable, a, b, "A->B")
1835
+ * })
1836
+ *
1837
+ * // Follow outgoing edges (normal direction)
1838
+ * const outgoingNodes = Array.from(Graph.indices(Graph.dfs(graph, { start: [0], direction: "outgoing" })))
1839
+ *
1840
+ * // Follow incoming edges (reverse direction)
1841
+ * const incomingNodes = Array.from(Graph.indices(Graph.dfs(graph, { start: [1], direction: "incoming" })))
1842
+ * ```
1843
+ *
1844
+ * @since 3.18.0
1845
+ * @category models
1846
+ */
1847
+ export type Direction = "outgoing" | "incoming"
1848
+
1849
+ // =============================================================================
1850
+
1851
+ // =============================================================================
1852
+ // Graph Structure Analysis Algorithms (Phase 5A)
1853
+ // =============================================================================
1854
+
1855
+ /**
1856
+ * Checks if the graph is acyclic (contains no cycles).
1857
+ *
1858
+ * Uses depth-first search to detect back edges, which indicate cycles.
1859
+ * For directed graphs, any back edge creates a cycle. For undirected graphs,
1860
+ * a back edge that doesn't go to the immediate parent creates a cycle.
1861
+ *
1862
+ * @example
1863
+ * ```ts
1864
+ * import { Graph } from "effect"
1865
+ *
1866
+ * // Acyclic directed graph (DAG)
1867
+ * const dag = Graph.directed<string, string>((mutable) => {
1868
+ * const a = Graph.addNode(mutable, "A")
1869
+ * const b = Graph.addNode(mutable, "B")
1870
+ * const c = Graph.addNode(mutable, "C")
1871
+ * Graph.addEdge(mutable, a, b, "A->B")
1872
+ * Graph.addEdge(mutable, b, c, "B->C")
1873
+ * })
1874
+ * console.log(Graph.isAcyclic(dag)) // true
1875
+ *
1876
+ * // Cyclic directed graph
1877
+ * const cyclic = Graph.directed<string, string>((mutable) => {
1878
+ * const a = Graph.addNode(mutable, "A")
1879
+ * const b = Graph.addNode(mutable, "B")
1880
+ * Graph.addEdge(mutable, a, b, "A->B")
1881
+ * Graph.addEdge(mutable, b, a, "B->A") // Creates cycle
1882
+ * })
1883
+ * console.log(Graph.isAcyclic(cyclic)) // false
1884
+ * ```
1885
+ *
1886
+ * @since 3.18.0
1887
+ * @category algorithms
1888
+ */
1889
+ export const isAcyclic = <N, E, T extends Kind = "directed">(
1890
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>
1891
+ ): boolean => {
1892
+ // Use existing cycle flag if available
1893
+ if (Option.isSome(graph.isAcyclic)) {
1894
+ return graph.isAcyclic.value
1895
+ }
1896
+
1897
+ // Stack-safe DFS cycle detection using iterative approach
1898
+ const visited = new Set<NodeIndex>()
1899
+ const recursionStack = new Set<NodeIndex>()
1900
+
1901
+ // Stack entry: [node, neighbors, neighborIndex, isFirstVisit]
1902
+ type DfsStackEntry = [NodeIndex, Array<NodeIndex>, number, boolean]
1903
+
1904
+ // Get all nodes to handle disconnected components
1905
+ for (const startNode of graph.nodes.keys()) {
1906
+ if (visited.has(startNode)) {
1907
+ continue // Already processed this component
1908
+ }
1909
+
1910
+ // Iterative DFS with explicit stack
1911
+ const stack: Array<DfsStackEntry> = [[startNode, [], 0, true]]
1912
+
1913
+ while (stack.length > 0) {
1914
+ const [node, neighbors, neighborIndex, isFirstVisit] = stack[stack.length - 1]
1915
+
1916
+ // First visit to this node
1917
+ if (isFirstVisit) {
1918
+ if (recursionStack.has(node)) {
1919
+ // Back edge found - cycle detected
1920
+ graph.isAcyclic = Option.some(false)
1921
+ return false
1922
+ }
1923
+
1924
+ if (visited.has(node)) {
1925
+ stack.pop()
1926
+ continue
1927
+ }
1928
+
1929
+ visited.add(node)
1930
+ recursionStack.add(node)
1931
+
1932
+ // Get neighbors for this node
1933
+ const nodeNeighbors = Array.from(neighborsDirected(graph, node, "outgoing"))
1934
+ stack[stack.length - 1] = [node, nodeNeighbors, 0, false]
1935
+ continue
1936
+ }
1937
+
1938
+ // Process next neighbor
1939
+ if (neighborIndex < neighbors.length) {
1940
+ const neighbor = neighbors[neighborIndex]
1941
+ stack[stack.length - 1] = [node, neighbors, neighborIndex + 1, false]
1942
+
1943
+ if (recursionStack.has(neighbor)) {
1944
+ // Back edge found - cycle detected
1945
+ graph.isAcyclic = Option.some(false)
1946
+ return false
1947
+ }
1948
+
1949
+ if (!visited.has(neighbor)) {
1950
+ stack.push([neighbor, [], 0, true])
1951
+ }
1952
+ } else {
1953
+ // Done with this node - backtrack
1954
+ recursionStack.delete(node)
1955
+ stack.pop()
1956
+ }
1957
+ }
1958
+ }
1959
+
1960
+ // Cache the result
1961
+ graph.isAcyclic = Option.some(true)
1962
+ return true
1963
+ }
1964
+
1965
+ /**
1966
+ * Checks if an undirected graph is bipartite.
1967
+ *
1968
+ * A bipartite graph is one whose vertices can be divided into two disjoint sets
1969
+ * such that no two vertices within the same set are adjacent. Uses BFS coloring
1970
+ * to determine bipartiteness.
1971
+ *
1972
+ * @example
1973
+ * ```ts
1974
+ * import { Graph } from "effect"
1975
+ *
1976
+ * // Bipartite graph (alternating coloring possible)
1977
+ * const bipartite = Graph.undirected<string, string>((mutable) => {
1978
+ * const a = Graph.addNode(mutable, "A")
1979
+ * const b = Graph.addNode(mutable, "B")
1980
+ * const c = Graph.addNode(mutable, "C")
1981
+ * const d = Graph.addNode(mutable, "D")
1982
+ * Graph.addEdge(mutable, a, b, "edge") // Set 1: {A, C}, Set 2: {B, D}
1983
+ * Graph.addEdge(mutable, b, c, "edge")
1984
+ * Graph.addEdge(mutable, c, d, "edge")
1985
+ * })
1986
+ * console.log(Graph.isBipartite(bipartite)) // true
1987
+ *
1988
+ * // Non-bipartite graph (odd cycle)
1989
+ * const triangle = Graph.undirected<string, string>((mutable) => {
1990
+ * const a = Graph.addNode(mutable, "A")
1991
+ * const b = Graph.addNode(mutable, "B")
1992
+ * const c = Graph.addNode(mutable, "C")
1993
+ * Graph.addEdge(mutable, a, b, "edge")
1994
+ * Graph.addEdge(mutable, b, c, "edge")
1995
+ * Graph.addEdge(mutable, c, a, "edge") // Triangle (3-cycle)
1996
+ * })
1997
+ * console.log(Graph.isBipartite(triangle)) // false
1998
+ * ```
1999
+ *
2000
+ * @since 3.18.0
2001
+ * @category algorithms
2002
+ */
2003
+ export const isBipartite = <N, E>(
2004
+ graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">
2005
+ ): boolean => {
2006
+ const coloring = new Map<NodeIndex, 0 | 1>()
2007
+ const discovered = new Set<NodeIndex>()
2008
+ let isBipartiteGraph = true
2009
+
2010
+ // Get all nodes to handle disconnected components
2011
+ for (const startNode of graph.nodes.keys()) {
2012
+ if (!discovered.has(startNode)) {
2013
+ // Start BFS coloring from this component
2014
+ const queue: Array<NodeIndex> = [startNode]
2015
+ coloring.set(startNode, 0) // Color start node with 0
2016
+ discovered.add(startNode)
2017
+
2018
+ while (queue.length > 0 && isBipartiteGraph) {
2019
+ const current = queue.shift()!
2020
+ const currentColor = coloring.get(current)!
2021
+ const neighborColor: 0 | 1 = currentColor === 0 ? 1 : 0
2022
+
2023
+ // Get all neighbors for undirected graph
2024
+ const nodeNeighbors = getUndirectedNeighbors(graph, current)
2025
+ for (const neighbor of nodeNeighbors) {
2026
+ if (!discovered.has(neighbor)) {
2027
+ // Color unvisited neighbor with opposite color
2028
+ coloring.set(neighbor, neighborColor)
2029
+ discovered.add(neighbor)
2030
+ queue.push(neighbor)
2031
+ } else {
2032
+ // Check if neighbor has the same color (conflict)
2033
+ if (coloring.get(neighbor) === currentColor) {
2034
+ isBipartiteGraph = false
2035
+ break
2036
+ }
2037
+ }
2038
+ }
2039
+ }
2040
+
2041
+ // Early exit if not bipartite
2042
+ if (!isBipartiteGraph) {
2043
+ break
2044
+ }
2045
+ }
2046
+ }
2047
+
2048
+ return isBipartiteGraph
2049
+ }
2050
+
2051
+ /**
2052
+ * Get neighbors for undirected graphs by checking both adjacency and reverse adjacency.
2053
+ * For undirected graphs, we need to find the other endpoint of each edge incident to the node.
2054
+ */
2055
+ const getUndirectedNeighbors = <N, E>(
2056
+ graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">,
2057
+ nodeIndex: NodeIndex
2058
+ ): Array<NodeIndex> => {
2059
+ const neighbors = new Set<NodeIndex>()
2060
+
2061
+ // Check edges where this node is the source
2062
+ const adjacencyList = graph.adjacency.get(nodeIndex)
2063
+ if (adjacencyList !== undefined) {
2064
+ for (const edgeIndex of adjacencyList) {
2065
+ const edge = graph.edges.get(edgeIndex)
2066
+ if (edge !== undefined) {
2067
+ // For undirected graphs, the neighbor is the other endpoint
2068
+ const otherNode = edge.source === nodeIndex ? edge.target : edge.source
2069
+ neighbors.add(otherNode)
2070
+ }
2071
+ }
2072
+ }
2073
+
2074
+ return Array.from(neighbors)
2075
+ }
2076
+
2077
+ /**
2078
+ * Find connected components in an undirected graph.
2079
+ * Each component is represented as an array of node indices.
2080
+ *
2081
+ * @example
2082
+ * ```ts
2083
+ * import { Graph } from "effect"
2084
+ *
2085
+ * const graph = Graph.undirected<string, string>((mutable) => {
2086
+ * const a = Graph.addNode(mutable, "A")
2087
+ * const b = Graph.addNode(mutable, "B")
2088
+ * const c = Graph.addNode(mutable, "C")
2089
+ * const d = Graph.addNode(mutable, "D")
2090
+ * Graph.addEdge(mutable, a, b, "edge") // Component 1: A-B
2091
+ * Graph.addEdge(mutable, c, d, "edge") // Component 2: C-D
2092
+ * })
2093
+ *
2094
+ * const components = Graph.connectedComponents(graph)
2095
+ * console.log(components) // [[0, 1], [2, 3]]
2096
+ * ```
2097
+ *
2098
+ * @since 3.18.0
2099
+ * @category algorithms
2100
+ */
2101
+ export const connectedComponents = <N, E>(
2102
+ graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">
2103
+ ): Array<Array<NodeIndex>> => {
2104
+ const visited = new Set<NodeIndex>()
2105
+ const components: Array<Array<NodeIndex>> = []
2106
+ for (const startNode of graph.nodes.keys()) {
2107
+ if (!visited.has(startNode)) {
2108
+ // DFS to find all nodes in this component
2109
+ const component: Array<NodeIndex> = []
2110
+ const stack: Array<NodeIndex> = [startNode]
2111
+
2112
+ while (stack.length > 0) {
2113
+ const current = stack.pop()!
2114
+ if (!visited.has(current)) {
2115
+ visited.add(current)
2116
+ component.push(current)
2117
+
2118
+ // Add all unvisited neighbors to stack
2119
+ const nodeNeighbors = getUndirectedNeighbors(graph, current)
2120
+ for (const neighbor of nodeNeighbors) {
2121
+ if (!visited.has(neighbor)) {
2122
+ stack.push(neighbor)
2123
+ }
2124
+ }
2125
+ }
2126
+ }
2127
+
2128
+ components.push(component)
2129
+ }
2130
+ }
2131
+
2132
+ return components
2133
+ }
2134
+
2135
+ /**
2136
+ * Find strongly connected components in a directed graph using Kosaraju's algorithm.
2137
+ * Each SCC is represented as an array of node indices.
2138
+ *
2139
+ * @example
2140
+ * ```ts
2141
+ * import { Graph } from "effect"
2142
+ *
2143
+ * const graph = Graph.directed<string, string>((mutable) => {
2144
+ * const a = Graph.addNode(mutable, "A")
2145
+ * const b = Graph.addNode(mutable, "B")
2146
+ * const c = Graph.addNode(mutable, "C")
2147
+ * Graph.addEdge(mutable, a, b, "A->B")
2148
+ * Graph.addEdge(mutable, b, c, "B->C")
2149
+ * Graph.addEdge(mutable, c, a, "C->A") // Creates SCC: A-B-C
2150
+ * })
2151
+ *
2152
+ * const sccs = Graph.stronglyConnectedComponents(graph)
2153
+ * console.log(sccs) // [[0, 1, 2]]
2154
+ * ```
2155
+ *
2156
+ * @since 3.18.0
2157
+ * @category algorithms
2158
+ */
2159
+ export const stronglyConnectedComponents = <N, E, T extends Kind = "directed">(
2160
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>
2161
+ ): Array<Array<NodeIndex>> => {
2162
+ const visited = new Set<NodeIndex>()
2163
+ const finishOrder: Array<NodeIndex> = []
2164
+ // Iterate directly over node keys
2165
+
2166
+ // Step 1: Stack-safe DFS on original graph to get finish times
2167
+ // Stack entry: [node, neighbors, neighborIndex, isFirstVisit]
2168
+ type DfsStackEntry = [NodeIndex, Array<NodeIndex>, number, boolean]
2169
+
2170
+ for (const startNode of graph.nodes.keys()) {
2171
+ if (visited.has(startNode)) {
2172
+ continue
2173
+ }
2174
+
2175
+ const stack: Array<DfsStackEntry> = [[startNode, [], 0, true]]
2176
+
2177
+ while (stack.length > 0) {
2178
+ const [node, nodeNeighbors, neighborIndex, isFirstVisit] = stack[stack.length - 1]
2179
+
2180
+ if (isFirstVisit) {
2181
+ if (visited.has(node)) {
2182
+ stack.pop()
2183
+ continue
2184
+ }
2185
+
2186
+ visited.add(node)
2187
+ const nodeNeighborsList = neighbors(graph, node)
2188
+ stack[stack.length - 1] = [node, nodeNeighborsList, 0, false]
2189
+ continue
2190
+ }
2191
+
2192
+ // Process next neighbor
2193
+ if (neighborIndex < nodeNeighbors.length) {
2194
+ const neighbor = nodeNeighbors[neighborIndex]
2195
+ stack[stack.length - 1] = [node, nodeNeighbors, neighborIndex + 1, false]
2196
+
2197
+ if (!visited.has(neighbor)) {
2198
+ stack.push([neighbor, [], 0, true])
2199
+ }
2200
+ } else {
2201
+ // Done with this node - add to finish order (post-order)
2202
+ finishOrder.push(node)
2203
+ stack.pop()
2204
+ }
2205
+ }
2206
+ }
2207
+
2208
+ // Step 2: Stack-safe DFS on transpose graph in reverse finish order
2209
+ visited.clear()
2210
+ const sccs: Array<Array<NodeIndex>> = []
2211
+
2212
+ for (let i = finishOrder.length - 1; i >= 0; i--) {
2213
+ const startNode = finishOrder[i]
2214
+ if (visited.has(startNode)) {
2215
+ continue
2216
+ }
2217
+
2218
+ const scc: Array<NodeIndex> = []
2219
+ const stack: Array<NodeIndex> = [startNode]
2220
+
2221
+ while (stack.length > 0) {
2222
+ const node = stack.pop()!
2223
+
2224
+ if (visited.has(node)) {
2225
+ continue
2226
+ }
2227
+
2228
+ visited.add(node)
2229
+ scc.push(node)
2230
+
2231
+ // Use reverse adjacency (transpose graph)
2232
+ const reverseAdjacency = graph.reverseAdjacency.get(node)
2233
+ if (reverseAdjacency !== undefined) {
2234
+ for (const edgeIndex of reverseAdjacency) {
2235
+ const edge = graph.edges.get(edgeIndex)
2236
+ if (edge !== undefined) {
2237
+ const predecessor = edge.source
2238
+ if (!visited.has(predecessor)) {
2239
+ stack.push(predecessor)
2240
+ }
2241
+ }
2242
+ }
2243
+ }
2244
+ }
2245
+
2246
+ sccs.push(scc)
2247
+ }
2248
+
2249
+ return sccs
2250
+ }
2251
+
2252
+ // =============================================================================
2253
+ // Path Finding Algorithms (Phase 5B)
2254
+ // =============================================================================
2255
+
2256
+ /**
2257
+ * Result of a shortest path computation containing the path and total distance.
2258
+ *
2259
+ * @since 3.18.0
2260
+ * @category models
2261
+ */
2262
+ export interface PathResult<E> {
2263
+ readonly path: Array<NodeIndex>
2264
+ readonly distance: number
2265
+ readonly costs: Array<E>
2266
+ }
2267
+
2268
+ /**
2269
+ * Configuration for Dijkstra's algorithm.
2270
+ *
2271
+ * @since 3.18.0
2272
+ * @category models
2273
+ */
2274
+ export interface DijkstraConfig<E> {
2275
+ source: NodeIndex
2276
+ target: NodeIndex
2277
+ cost: (edgeData: E) => number
2278
+ }
2279
+
2280
+ /**
2281
+ * Configuration for A* algorithm.
2282
+ *
2283
+ * @since 3.18.0
2284
+ * @category models
2285
+ */
2286
+ export interface AstarConfig<E, N> {
2287
+ source: NodeIndex
2288
+ target: NodeIndex
2289
+ cost: (edgeData: E) => number
2290
+ heuristic: (sourceNodeData: N, targetNodeData: N) => number
2291
+ }
2292
+
2293
+ /**
2294
+ * Configuration for Bellman-Ford algorithm.
2295
+ *
2296
+ * @since 3.18.0
2297
+ * @category models
2298
+ */
2299
+ export interface BellmanFordConfig<E> {
2300
+ source: NodeIndex
2301
+ target: NodeIndex
2302
+ cost: (edgeData: E) => number
2303
+ }
2304
+
2305
+ /**
2306
+ * Find the shortest path between two nodes using Dijkstra's algorithm.
2307
+ *
2308
+ * Dijkstra's algorithm works with non-negative edge weights and finds the shortest
2309
+ * path from a source node to a target node in O((V + E) log V) time complexity.
2310
+ *
2311
+ * @example
2312
+ * ```ts
2313
+ * import { Graph, Option } from "effect"
2314
+ *
2315
+ * const graph = Graph.directed<string, number>((mutable) => {
2316
+ * const a = Graph.addNode(mutable, "A")
2317
+ * const b = Graph.addNode(mutable, "B")
2318
+ * const c = Graph.addNode(mutable, "C")
2319
+ * Graph.addEdge(mutable, a, b, 5)
2320
+ * Graph.addEdge(mutable, a, c, 10)
2321
+ * Graph.addEdge(mutable, b, c, 2)
2322
+ * })
2323
+ *
2324
+ * const result = Graph.dijkstra(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
2325
+ * if (Option.isSome(result)) {
2326
+ * console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
2327
+ * console.log(result.value.distance) // 7 - total distance
2328
+ * }
2329
+ * ```
2330
+ *
2331
+ * @since 3.18.0
2332
+ * @category algorithms
2333
+ */
2334
+ export const dijkstra = <N, E, T extends Kind = "directed">(
2335
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
2336
+ config: DijkstraConfig<E>
2337
+ ): Option.Option<PathResult<E>> => {
2338
+ const { cost, source, target } = config
2339
+ // Validate that source and target nodes exist
2340
+ if (!graph.nodes.has(source)) {
2341
+ throw missingNode(source)
2342
+ }
2343
+ if (!graph.nodes.has(target)) {
2344
+ throw missingNode(target)
2345
+ }
2346
+
2347
+ // Early return if source equals target
2348
+ if (source === target) {
2349
+ return Option.some({
2350
+ path: [source],
2351
+ distance: 0,
2352
+ costs: []
2353
+ })
2354
+ }
2355
+
2356
+ // Distance tracking and priority queue simulation
2357
+ const distances = new Map<NodeIndex, number>()
2358
+ const previous = new Map<NodeIndex, { node: NodeIndex; edgeData: E } | null>()
2359
+ const visited = new Set<NodeIndex>()
2360
+
2361
+ // Initialize distances
2362
+ // Iterate directly over node keys
2363
+ for (const node of graph.nodes.keys()) {
2364
+ distances.set(node, node === source ? 0 : Infinity)
2365
+ previous.set(node, null)
2366
+ }
2367
+
2368
+ // Simple priority queue using array (can be optimized with proper heap)
2369
+ const priorityQueue: Array<{ node: NodeIndex; distance: number }> = [
2370
+ { node: source, distance: 0 }
2371
+ ]
2372
+
2373
+ while (priorityQueue.length > 0) {
2374
+ // Find minimum distance node (priority queue extract-min)
2375
+ let minIndex = 0
2376
+ for (let i = 1; i < priorityQueue.length; i++) {
2377
+ if (priorityQueue[i].distance < priorityQueue[minIndex].distance) {
2378
+ minIndex = i
2379
+ }
2380
+ }
2381
+
2382
+ const current = priorityQueue.splice(minIndex, 1)[0]
2383
+ const currentNode = current.node
2384
+
2385
+ // Skip if already visited (can happen with duplicate entries)
2386
+ if (visited.has(currentNode)) {
2387
+ continue
2388
+ }
2389
+
2390
+ visited.add(currentNode)
2391
+
2392
+ // Early termination if we reached the target
2393
+ if (currentNode === target) {
2394
+ break
2395
+ }
2396
+
2397
+ // Get current distance
2398
+ const currentDistance = distances.get(currentNode)!
2399
+
2400
+ // Examine all outgoing edges
2401
+ const adjacencyList = graph.adjacency.get(currentNode)
2402
+ if (adjacencyList !== undefined) {
2403
+ for (const edgeIndex of adjacencyList) {
2404
+ const edge = graph.edges.get(edgeIndex)
2405
+ if (edge !== undefined) {
2406
+ const neighbor = edge.target
2407
+ const weight = cost(edge.data)
2408
+
2409
+ // Validate non-negative weights
2410
+ if (weight < 0) {
2411
+ throw new Error(`Dijkstra's algorithm requires non-negative edge weights, found ${weight}`)
2412
+ }
2413
+
2414
+ const newDistance = currentDistance + weight
2415
+ const neighborDistance = distances.get(neighbor)!
2416
+
2417
+ // Relaxation step
2418
+ if (newDistance < neighborDistance) {
2419
+ distances.set(neighbor, newDistance)
2420
+ previous.set(neighbor, { node: currentNode, edgeData: edge.data })
2421
+
2422
+ // Add to priority queue if not visited
2423
+ if (!visited.has(neighbor)) {
2424
+ priorityQueue.push({ node: neighbor, distance: newDistance })
2425
+ }
2426
+ }
2427
+ }
2428
+ }
2429
+ }
2430
+ }
2431
+
2432
+ // Check if target is reachable
2433
+ const targetDistance = distances.get(target)!
2434
+ if (targetDistance === Infinity) {
2435
+ return Option.none() // No path exists
2436
+ }
2437
+
2438
+ // Reconstruct path
2439
+ const path: Array<NodeIndex> = []
2440
+ const costs: Array<E> = []
2441
+ let currentNode: NodeIndex | null = target
2442
+
2443
+ while (currentNode !== null) {
2444
+ path.unshift(currentNode)
2445
+ const prev: { node: NodeIndex; edgeData: E } | null = previous.get(currentNode)!
2446
+ if (prev !== null) {
2447
+ costs.unshift(prev.edgeData)
2448
+ currentNode = prev.node
2449
+ } else {
2450
+ currentNode = null
2451
+ }
2452
+ }
2453
+
2454
+ return Option.some({
2455
+ path,
2456
+ distance: targetDistance,
2457
+ costs
2458
+ })
2459
+ }
2460
+
2461
+ /**
2462
+ * Result of all-pairs shortest path computation.
2463
+ *
2464
+ * @since 3.18.0
2465
+ * @category models
2466
+ */
2467
+ export interface AllPairsResult<E> {
2468
+ readonly distances: Map<NodeIndex, Map<NodeIndex, number>>
2469
+ readonly paths: Map<NodeIndex, Map<NodeIndex, Array<NodeIndex> | null>>
2470
+ readonly costs: Map<NodeIndex, Map<NodeIndex, Array<E>>>
2471
+ }
2472
+
2473
+ /**
2474
+ * Find shortest paths between all pairs of nodes using Floyd-Warshall algorithm.
2475
+ *
2476
+ * Floyd-Warshall algorithm computes shortest paths between all pairs of nodes in O(V³) time.
2477
+ * It can handle negative edge weights and detect negative cycles.
2478
+ *
2479
+ * @example
2480
+ * ```ts
2481
+ * import { Graph } from "effect"
2482
+ *
2483
+ * const graph = Graph.directed<string, number>((mutable) => {
2484
+ * const a = Graph.addNode(mutable, "A")
2485
+ * const b = Graph.addNode(mutable, "B")
2486
+ * const c = Graph.addNode(mutable, "C")
2487
+ * Graph.addEdge(mutable, a, b, 3)
2488
+ * Graph.addEdge(mutable, b, c, 2)
2489
+ * Graph.addEdge(mutable, a, c, 7)
2490
+ * })
2491
+ *
2492
+ * const result = Graph.floydWarshall(graph, (edgeData) => edgeData)
2493
+ * const distanceAToC = result.distances.get(0)?.get(2) // 5 (A->B->C)
2494
+ * const pathAToC = result.paths.get(0)?.get(2) // [0, 1, 2]
2495
+ * ```
2496
+ *
2497
+ * @since 3.18.0
2498
+ * @category algorithms
2499
+ */
2500
+ export const floydWarshall = <N, E, T extends Kind = "directed">(
2501
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
2502
+ cost: (edgeData: E) => number
2503
+ ): AllPairsResult<E> => {
2504
+ // Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration)
2505
+ const allNodes = Array.from(graph.nodes.keys())
2506
+
2507
+ // Initialize distance matrix
2508
+ const dist = new Map<NodeIndex, Map<NodeIndex, number>>()
2509
+ const next = new Map<NodeIndex, Map<NodeIndex, NodeIndex | null>>()
2510
+ const edgeMatrix = new Map<NodeIndex, Map<NodeIndex, E | null>>()
2511
+
2512
+ // Initialize with infinity for all pairs
2513
+ for (const i of allNodes) {
2514
+ dist.set(i, new Map())
2515
+ next.set(i, new Map())
2516
+ edgeMatrix.set(i, new Map())
2517
+
2518
+ for (const j of allNodes) {
2519
+ dist.get(i)!.set(j, i === j ? 0 : Infinity)
2520
+ next.get(i)!.set(j, null)
2521
+ edgeMatrix.get(i)!.set(j, null)
2522
+ }
2523
+ }
2524
+
2525
+ // Set edge weights
2526
+ for (const [, edgeData] of graph.edges) {
2527
+ const weight = cost(edgeData.data)
2528
+ const i = edgeData.source
2529
+ const j = edgeData.target
2530
+
2531
+ // Use minimum weight if multiple edges exist
2532
+ const currentWeight = dist.get(i)!.get(j)!
2533
+ if (weight < currentWeight) {
2534
+ dist.get(i)!.set(j, weight)
2535
+ next.get(i)!.set(j, j)
2536
+ edgeMatrix.get(i)!.set(j, edgeData.data)
2537
+ }
2538
+ }
2539
+
2540
+ // Floyd-Warshall main loop
2541
+ for (const k of allNodes) {
2542
+ for (const i of allNodes) {
2543
+ for (const j of allNodes) {
2544
+ const distIK = dist.get(i)!.get(k)!
2545
+ const distKJ = dist.get(k)!.get(j)!
2546
+ const distIJ = dist.get(i)!.get(j)!
2547
+
2548
+ if (distIK !== Infinity && distKJ !== Infinity && distIK + distKJ < distIJ) {
2549
+ dist.get(i)!.set(j, distIK + distKJ)
2550
+ next.get(i)!.set(j, next.get(i)!.get(k)!)
2551
+ }
2552
+ }
2553
+ }
2554
+ }
2555
+
2556
+ // Check for negative cycles
2557
+ for (const i of allNodes) {
2558
+ if (dist.get(i)!.get(i)! < 0) {
2559
+ throw new Error(`Negative cycle detected involving node ${i}`)
2560
+ }
2561
+ }
2562
+
2563
+ // Build result paths and edge weights
2564
+ const paths = new Map<NodeIndex, Map<NodeIndex, Array<NodeIndex> | null>>()
2565
+ const resultCosts = new Map<NodeIndex, Map<NodeIndex, Array<E>>>()
2566
+
2567
+ for (const i of allNodes) {
2568
+ paths.set(i, new Map())
2569
+ resultCosts.set(i, new Map())
2570
+
2571
+ for (const j of allNodes) {
2572
+ if (i === j) {
2573
+ paths.get(i)!.set(j, [i])
2574
+ resultCosts.get(i)!.set(j, [])
2575
+ } else if (dist.get(i)!.get(j)! === Infinity) {
2576
+ paths.get(i)!.set(j, null)
2577
+ resultCosts.get(i)!.set(j, [])
2578
+ } else {
2579
+ // Reconstruct path iteratively
2580
+ const path: Array<NodeIndex> = []
2581
+ const weights: Array<E> = []
2582
+ let current = i
2583
+
2584
+ path.push(current)
2585
+ while (current !== j) {
2586
+ const nextNode = next.get(current)!.get(j)!
2587
+ if (nextNode === null) break
2588
+
2589
+ const edgeData = edgeMatrix.get(current)!.get(nextNode)!
2590
+ if (edgeData !== null) {
2591
+ weights.push(edgeData)
2592
+ }
2593
+
2594
+ current = nextNode
2595
+ path.push(current)
2596
+ }
2597
+
2598
+ paths.get(i)!.set(j, path)
2599
+ resultCosts.get(i)!.set(j, weights)
2600
+ }
2601
+ }
2602
+ }
2603
+
2604
+ return {
2605
+ distances: dist,
2606
+ paths,
2607
+ costs: resultCosts
2608
+ }
2609
+ }
2610
+
2611
+ /**
2612
+ * Find the shortest path between two nodes using A* pathfinding algorithm.
2613
+ *
2614
+ * A* is an extension of Dijkstra's algorithm that uses a heuristic function to guide
2615
+ * the search towards the target, potentially finding paths faster than Dijkstra's.
2616
+ * The heuristic must be admissible (never overestimate the actual cost).
2617
+ *
2618
+ * @example
2619
+ * ```ts
2620
+ * import { Graph, Option } from "effect"
2621
+ *
2622
+ * const graph = Graph.directed<{x: number, y: number}, number>((mutable) => {
2623
+ * const a = Graph.addNode(mutable, {x: 0, y: 0})
2624
+ * const b = Graph.addNode(mutable, {x: 1, y: 0})
2625
+ * const c = Graph.addNode(mutable, {x: 2, y: 0})
2626
+ * Graph.addEdge(mutable, a, b, 1)
2627
+ * Graph.addEdge(mutable, b, c, 1)
2628
+ * })
2629
+ *
2630
+ * // Manhattan distance heuristic
2631
+ * const heuristic = (nodeData: {x: number, y: number}, targetData: {x: number, y: number}) =>
2632
+ * Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y)
2633
+ *
2634
+ * const result = Graph.astar(graph, { source: 0, target: 2, cost: (edgeData) => edgeData, heuristic })
2635
+ * if (Option.isSome(result)) {
2636
+ * console.log(result.value.path) // [0, 1, 2] - shortest path
2637
+ * console.log(result.value.distance) // 2 - total distance
2638
+ * }
2639
+ * ```
2640
+ *
2641
+ * @since 3.18.0
2642
+ * @category algorithms
2643
+ */
2644
+ export const astar = <N, E, T extends Kind = "directed">(
2645
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
2646
+ config: AstarConfig<E, N>
2647
+ ): Option.Option<PathResult<E>> => {
2648
+ const { cost, heuristic, source, target } = config
2649
+ // Validate that source and target nodes exist
2650
+ if (!graph.nodes.has(source)) {
2651
+ throw missingNode(source)
2652
+ }
2653
+ if (!graph.nodes.has(target)) {
2654
+ throw missingNode(target)
2655
+ }
2656
+
2657
+ // Early return if source equals target
2658
+ if (source === target) {
2659
+ return Option.some({
2660
+ path: [source],
2661
+ distance: 0,
2662
+ costs: []
2663
+ })
2664
+ }
2665
+
2666
+ // Get target node data for heuristic calculations
2667
+ const targetNodeData = graph.nodes.get(target)
2668
+ if (targetNodeData === undefined) {
2669
+ throw new Error(`Target node ${target} data not found`)
2670
+ }
2671
+
2672
+ // Distance tracking (g-score) and f-score (g + h)
2673
+ const gScore = new Map<NodeIndex, number>()
2674
+ const fScore = new Map<NodeIndex, number>()
2675
+ const previous = new Map<NodeIndex, { node: NodeIndex; edgeData: E } | null>()
2676
+ const visited = new Set<NodeIndex>()
2677
+
2678
+ // Initialize scores
2679
+ // Iterate directly over node keys
2680
+ for (const node of graph.nodes.keys()) {
2681
+ gScore.set(node, node === source ? 0 : Infinity)
2682
+ fScore.set(node, Infinity)
2683
+ previous.set(node, null)
2684
+ }
2685
+
2686
+ // Calculate initial f-score for source
2687
+ const sourceNodeData = graph.nodes.get(source)
2688
+ if (sourceNodeData !== undefined) {
2689
+ const h = heuristic(sourceNodeData, targetNodeData)
2690
+ fScore.set(source, h)
2691
+ }
2692
+
2693
+ // Priority queue using f-score (total estimated cost)
2694
+ const openSet: Array<{ node: NodeIndex; fScore: number }> = [
2695
+ { node: source, fScore: fScore.get(source)! }
2696
+ ]
2697
+
2698
+ while (openSet.length > 0) {
2699
+ // Find node with lowest f-score
2700
+ let minIndex = 0
2701
+ for (let i = 1; i < openSet.length; i++) {
2702
+ if (openSet[i].fScore < openSet[minIndex].fScore) {
2703
+ minIndex = i
2704
+ }
2705
+ }
2706
+
2707
+ const current = openSet.splice(minIndex, 1)[0]
2708
+ const currentNode = current.node
2709
+
2710
+ // Skip if already visited
2711
+ if (visited.has(currentNode)) {
2712
+ continue
2713
+ }
2714
+
2715
+ visited.add(currentNode)
2716
+
2717
+ // Early termination if we reached the target
2718
+ if (currentNode === target) {
2719
+ break
2720
+ }
2721
+
2722
+ // Get current g-score
2723
+ const currentGScore = gScore.get(currentNode)!
2724
+
2725
+ // Examine all outgoing edges
2726
+ const adjacencyList = graph.adjacency.get(currentNode)
2727
+ if (adjacencyList !== undefined) {
2728
+ for (const edgeIndex of adjacencyList) {
2729
+ const edge = graph.edges.get(edgeIndex)
2730
+ if (edge !== undefined) {
2731
+ const neighbor = edge.target
2732
+ const weight = cost(edge.data)
2733
+
2734
+ // Validate non-negative weights
2735
+ if (weight < 0) {
2736
+ throw new Error(`A* algorithm requires non-negative edge weights, found ${weight}`)
2737
+ }
2738
+
2739
+ const tentativeGScore = currentGScore + weight
2740
+ const neighborGScore = gScore.get(neighbor)!
2741
+
2742
+ // If this path to neighbor is better than any previous one
2743
+ if (tentativeGScore < neighborGScore) {
2744
+ // Update g-score and previous
2745
+ gScore.set(neighbor, tentativeGScore)
2746
+ previous.set(neighbor, { node: currentNode, edgeData: edge.data })
2747
+
2748
+ // Calculate f-score using heuristic
2749
+ const neighborNodeData = graph.nodes.get(neighbor)
2750
+ if (neighborNodeData !== undefined) {
2751
+ const h = heuristic(neighborNodeData, targetNodeData)
2752
+ const f = tentativeGScore + h
2753
+ fScore.set(neighbor, f)
2754
+
2755
+ // Add to open set if not visited
2756
+ if (!visited.has(neighbor)) {
2757
+ openSet.push({ node: neighbor, fScore: f })
2758
+ }
2759
+ }
2760
+ }
2761
+ }
2762
+ }
2763
+ }
2764
+ }
2765
+
2766
+ // Check if target is reachable
2767
+ const targetGScore = gScore.get(target)!
2768
+ if (targetGScore === Infinity) {
2769
+ return Option.none() // No path exists
2770
+ }
2771
+
2772
+ // Reconstruct path
2773
+ const path: Array<NodeIndex> = []
2774
+ const costs: Array<E> = []
2775
+ let currentNode: NodeIndex | null = target
2776
+
2777
+ while (currentNode !== null) {
2778
+ path.unshift(currentNode)
2779
+ const prev: { node: NodeIndex; edgeData: E } | null = previous.get(currentNode)!
2780
+ if (prev !== null) {
2781
+ costs.unshift(prev.edgeData)
2782
+ currentNode = prev.node
2783
+ } else {
2784
+ currentNode = null
2785
+ }
2786
+ }
2787
+
2788
+ return Option.some({
2789
+ path,
2790
+ distance: targetGScore,
2791
+ costs
2792
+ })
2793
+ }
2794
+
2795
+ /**
2796
+ * Find the shortest path between two nodes using Bellman-Ford algorithm.
2797
+ *
2798
+ * Bellman-Ford algorithm can handle negative edge weights and detects negative cycles.
2799
+ * It has O(VE) time complexity, slower than Dijkstra's but more versatile.
2800
+ * Returns Option.none() if a negative cycle is detected that affects the path.
2801
+ *
2802
+ * @example
2803
+ * ```ts
2804
+ * import { Graph, Option } from "effect"
2805
+ *
2806
+ * const graph = Graph.directed<string, number>((mutable) => {
2807
+ * const a = Graph.addNode(mutable, "A")
2808
+ * const b = Graph.addNode(mutable, "B")
2809
+ * const c = Graph.addNode(mutable, "C")
2810
+ * Graph.addEdge(mutable, a, b, -1) // Negative weight allowed
2811
+ * Graph.addEdge(mutable, b, c, 3)
2812
+ * Graph.addEdge(mutable, a, c, 5)
2813
+ * })
2814
+ *
2815
+ * const result = Graph.bellmanFord(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
2816
+ * if (Option.isSome(result)) {
2817
+ * console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
2818
+ * console.log(result.value.distance) // 2 - total distance
2819
+ * }
2820
+ * ```
2821
+ *
2822
+ * @since 3.18.0
2823
+ * @category algorithms
2824
+ */
2825
+ export const bellmanFord = <N, E, T extends Kind = "directed">(
2826
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
2827
+ config: BellmanFordConfig<E>
2828
+ ): Option.Option<PathResult<E>> => {
2829
+ const { cost, source, target } = config
2830
+ // Validate that source and target nodes exist
2831
+ if (!graph.nodes.has(source)) {
2832
+ throw missingNode(source)
2833
+ }
2834
+ if (!graph.nodes.has(target)) {
2835
+ throw missingNode(target)
2836
+ }
2837
+
2838
+ // Early return if source equals target
2839
+ if (source === target) {
2840
+ return Option.some({
2841
+ path: [source],
2842
+ distance: 0,
2843
+ costs: []
2844
+ })
2845
+ }
2846
+
2847
+ // Initialize distances and predecessors
2848
+ const distances = new Map<NodeIndex, number>()
2849
+ const previous = new Map<NodeIndex, { node: NodeIndex; edgeData: E } | null>()
2850
+ // Iterate directly over node keys
2851
+
2852
+ for (const node of graph.nodes.keys()) {
2853
+ distances.set(node, node === source ? 0 : Infinity)
2854
+ previous.set(node, null)
2855
+ }
2856
+
2857
+ // Collect all edges for relaxation
2858
+ const edges: Array<{ source: NodeIndex; target: NodeIndex; weight: number; edgeData: E }> = []
2859
+ for (const [, edgeData] of graph.edges) {
2860
+ const weight = cost(edgeData.data)
2861
+ edges.push({
2862
+ source: edgeData.source,
2863
+ target: edgeData.target,
2864
+ weight,
2865
+ edgeData: edgeData.data
2866
+ })
2867
+ }
2868
+
2869
+ // Relax edges up to V-1 times
2870
+ const nodeCount = graph.nodes.size
2871
+ for (let i = 0; i < nodeCount - 1; i++) {
2872
+ let hasUpdate = false
2873
+
2874
+ for (const edge of edges) {
2875
+ const sourceDistance = distances.get(edge.source)!
2876
+ const targetDistance = distances.get(edge.target)!
2877
+
2878
+ // Relaxation step
2879
+ if (sourceDistance !== Infinity && sourceDistance + edge.weight < targetDistance) {
2880
+ distances.set(edge.target, sourceDistance + edge.weight)
2881
+ previous.set(edge.target, { node: edge.source, edgeData: edge.edgeData })
2882
+ hasUpdate = true
2883
+ }
2884
+ }
2885
+
2886
+ // Early termination if no updates
2887
+ if (!hasUpdate) {
2888
+ break
2889
+ }
2890
+ }
2891
+
2892
+ // Check for negative cycles
2893
+ for (const edge of edges) {
2894
+ const sourceDistance = distances.get(edge.source)!
2895
+ const targetDistance = distances.get(edge.target)!
2896
+
2897
+ if (sourceDistance !== Infinity && sourceDistance + edge.weight < targetDistance) {
2898
+ // Negative cycle detected - check if it affects the path to target
2899
+ const affectedNodes = new Set<NodeIndex>()
2900
+ const queue = [edge.target]
2901
+
2902
+ while (queue.length > 0) {
2903
+ const node = queue.shift()!
2904
+ if (affectedNodes.has(node)) continue
2905
+ affectedNodes.add(node)
2906
+
2907
+ // Add all nodes reachable from this node
2908
+ const adjacencyList = graph.adjacency.get(node)
2909
+ if (adjacencyList !== undefined) {
2910
+ for (const edgeIndex of adjacencyList) {
2911
+ const edge = graph.edges.get(edgeIndex)
2912
+ if (edge !== undefined) {
2913
+ queue.push(edge.target)
2914
+ }
2915
+ }
2916
+ }
2917
+ }
2918
+
2919
+ // If target is affected by negative cycle, return null
2920
+ if (affectedNodes.has(target)) {
2921
+ return Option.none()
2922
+ }
2923
+ }
2924
+ }
2925
+
2926
+ // Check if target is reachable
2927
+ const targetDistance = distances.get(target)!
2928
+ if (targetDistance === Infinity) {
2929
+ return Option.none() // No path exists
2930
+ }
2931
+
2932
+ // Reconstruct path
2933
+ const path: Array<NodeIndex> = []
2934
+ const costs: Array<E> = []
2935
+ let currentNode: NodeIndex | null = target
2936
+
2937
+ while (currentNode !== null) {
2938
+ path.unshift(currentNode)
2939
+ const prev: { node: NodeIndex; edgeData: E } | null = previous.get(currentNode)!
2940
+ if (prev !== null) {
2941
+ costs.unshift(prev.edgeData)
2942
+ currentNode = prev.node
2943
+ } else {
2944
+ currentNode = null
2945
+ }
2946
+ }
2947
+
2948
+ return Option.some({
2949
+ path,
2950
+ distance: targetDistance,
2951
+ costs
2952
+ })
2953
+ }
2954
+
2955
+ /**
2956
+ * Concrete class for iterables that produce [NodeIndex, NodeData] tuples.
2957
+ *
2958
+ * This class provides a common abstraction for all iterables that return node data,
2959
+ * including traversal iterators (DFS, BFS, etc.) and element iterators (nodes, externals).
2960
+ * It uses a mapEntry function pattern for flexible iteration and transformation.
2961
+ *
2962
+ * @example
2963
+ * ```ts
2964
+ * import { Graph } from "effect"
2965
+ *
2966
+ * const graph = Graph.directed<string, number>((mutable) => {
2967
+ * const a = Graph.addNode(mutable, "A")
2968
+ * const b = Graph.addNode(mutable, "B")
2969
+ * Graph.addEdge(mutable, a, b, 1)
2970
+ * })
2971
+ *
2972
+ * // Both traversal and element iterators return NodeWalker
2973
+ * const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })
2974
+ * const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
2975
+ *
2976
+ * // Common interface for working with node iterables
2977
+ * function processNodes<N>(nodeIterable: Graph.NodeWalker<N>): Array<number> {
2978
+ * return Array.from(Graph.indices(nodeIterable))
2979
+ * }
2980
+ *
2981
+ * // Access node data using values() or entries()
2982
+ * const nodeData = Array.from(Graph.values(dfsNodes)) // ["A", "B"]
2983
+ * const nodeEntries = Array.from(Graph.entries(allNodes)) // [[0, "A"], [1, "B"]]
2984
+ * ```
2985
+ *
2986
+ * @since 3.18.0
2987
+ * @category models
2988
+ */
2989
+ export class Walker<T, N> implements Iterable<[T, N]> {
2990
+ // @ts-ignore
2991
+ readonly [Symbol.iterator]: () => Iterator<[T, N]>
2992
+
2993
+ /**
2994
+ * Visits each element and maps it to a value using the provided function.
2995
+ *
2996
+ * Takes a function that receives the index and data,
2997
+ * and returns an iterable of the mapped values. Skips elements that
2998
+ * no longer exist in the graph.
2999
+ *
3000
+ * @example
3001
+ * ```ts
3002
+ * import { Graph } from "effect"
3003
+ *
3004
+ * const graph = Graph.directed<string, number>((mutable) => {
3005
+ * const a = Graph.addNode(mutable, "A")
3006
+ * const b = Graph.addNode(mutable, "B")
3007
+ * Graph.addEdge(mutable, a, b, 1)
3008
+ * })
3009
+ *
3010
+ * const dfs = Graph.dfs(graph, { start: [0] })
3011
+ *
3012
+ * // Map to just the node data
3013
+ * const values = Array.from(dfs.visit((index, data) => data))
3014
+ * console.log(values) // ["A", "B"]
3015
+ *
3016
+ * // Map to custom objects
3017
+ * const custom = Array.from(dfs.visit((index, data) => ({ id: index, name: data })))
3018
+ * console.log(custom) // [{ id: 0, name: "A" }, { id: 1, name: "B" }]
3019
+ * ```
3020
+ *
3021
+ * @since 3.18.0
3022
+ * @category iterators
3023
+ */
3024
+ readonly visit: <U>(f: (index: T, data: N) => U) => Iterable<U>
3025
+
3026
+ constructor(
3027
+ /**
3028
+ * Visits each element and maps it to a value using the provided function.
3029
+ *
3030
+ * Takes a function that receives the index and data,
3031
+ * and returns an iterable of the mapped values. Skips elements that
3032
+ * no longer exist in the graph.
3033
+ *
3034
+ * @example
3035
+ * ```ts
3036
+ * import { Graph } from "effect"
3037
+ *
3038
+ * const graph = Graph.directed<string, number>((mutable) => {
3039
+ * const a = Graph.addNode(mutable, "A")
3040
+ * const b = Graph.addNode(mutable, "B")
3041
+ * Graph.addEdge(mutable, a, b, 1)
3042
+ * })
3043
+ *
3044
+ * const dfs = Graph.dfs(graph, { start: [0] })
3045
+ *
3046
+ * // Map to just the node data
3047
+ * const values = Array.from(dfs.visit((index, data) => data))
3048
+ * console.log(values) // ["A", "B"]
3049
+ *
3050
+ * // Map to custom objects
3051
+ * const custom = Array.from(dfs.visit((index, data) => ({ id: index, name: data })))
3052
+ * console.log(custom) // [{ id: 0, name: "A" }, { id: 1, name: "B" }]
3053
+ * ```
3054
+ *
3055
+ * @since 3.18.0
3056
+ * @category iterators
3057
+ */
3058
+ visit: <U>(f: (index: T, data: N) => U) => Iterable<U>
3059
+ ) {
3060
+ this.visit = visit
3061
+ this[Symbol.iterator] = visit((index, data) => [index, data] as [T, N])[Symbol.iterator]
3062
+ }
3063
+ }
3064
+
3065
+ /**
3066
+ * Type alias for node iteration using Walker.
3067
+ * NodeWalker is represented as Walker<NodeIndex, N>.
3068
+ *
3069
+ * @since 3.18.0
3070
+ * @category models
3071
+ */
3072
+ export type NodeWalker<N> = Walker<NodeIndex, N>
3073
+
3074
+ /**
3075
+ * Type alias for edge iteration using Walker.
3076
+ * EdgeWalker is represented as Walker<EdgeIndex, Edge<E>>.
3077
+ *
3078
+ * @since 3.18.0
3079
+ * @category models
3080
+ */
3081
+ export type EdgeWalker<E> = Walker<EdgeIndex, Edge<E>>
3082
+
3083
+ /**
3084
+ * Returns an iterator over the indices in the walker.
3085
+ *
3086
+ * @example
3087
+ * ```ts
3088
+ * import { Graph } from "effect"
3089
+ *
3090
+ * const graph = Graph.directed<string, number>((mutable) => {
3091
+ * const a = Graph.addNode(mutable, "A")
3092
+ * const b = Graph.addNode(mutable, "B")
3093
+ * Graph.addEdge(mutable, a, b, 1)
3094
+ * })
3095
+ *
3096
+ * const dfs = Graph.dfs(graph, { start: [0] })
3097
+ * const indices = Array.from(Graph.indices(dfs))
3098
+ * console.log(indices) // [0, 1]
3099
+ * ```
3100
+ *
3101
+ * @since 3.18.0
3102
+ * @category utilities
3103
+ */
3104
+ export const indices = <T, N>(walker: Walker<T, N>): Iterable<T> => walker.visit((index, _) => index)
3105
+
3106
+ /**
3107
+ * Returns an iterator over the values (data) in the walker.
3108
+ *
3109
+ * @example
3110
+ * ```ts
3111
+ * import { Graph } from "effect"
3112
+ *
3113
+ * const graph = Graph.directed<string, number>((mutable) => {
3114
+ * const a = Graph.addNode(mutable, "A")
3115
+ * const b = Graph.addNode(mutable, "B")
3116
+ * Graph.addEdge(mutable, a, b, 1)
3117
+ * })
3118
+ *
3119
+ * const dfs = Graph.dfs(graph, { start: [0] })
3120
+ * const values = Array.from(Graph.values(dfs))
3121
+ * console.log(values) // ["A", "B"]
3122
+ * ```
3123
+ *
3124
+ * @since 3.18.0
3125
+ * @category utilities
3126
+ */
3127
+ export const values = <T, N>(walker: Walker<T, N>): Iterable<N> => walker.visit((_, data) => data)
3128
+
3129
+ /**
3130
+ * Returns an iterator over [index, data] entries in the walker.
3131
+ *
3132
+ * @example
3133
+ * ```ts
3134
+ * import { Graph } from "effect"
3135
+ *
3136
+ * const graph = Graph.directed<string, number>((mutable) => {
3137
+ * const a = Graph.addNode(mutable, "A")
3138
+ * const b = Graph.addNode(mutable, "B")
3139
+ * Graph.addEdge(mutable, a, b, 1)
3140
+ * })
3141
+ *
3142
+ * const dfs = Graph.dfs(graph, { start: [0] })
3143
+ * const entries = Array.from(Graph.entries(dfs))
3144
+ * console.log(entries) // [[0, "A"], [1, "B"]]
3145
+ * ```
3146
+ *
3147
+ * @since 3.18.0
3148
+ * @category utilities
3149
+ */
3150
+ export const entries = <T, N>(walker: Walker<T, N>): Iterable<[T, N]> =>
3151
+ walker.visit((index, data) => [index, data] as [T, N])
3152
+
3153
+ /**
3154
+ * Configuration for graph search iterators.
3155
+ *
3156
+ * @since 3.18.0
3157
+ * @category models
3158
+ */
3159
+ export interface SearchConfig {
3160
+ readonly start?: Array<NodeIndex>
3161
+ readonly direction?: Direction
3162
+ }
3163
+
3164
+ /**
3165
+ * Creates a new DFS iterator with optional configuration.
3166
+ *
3167
+ * The iterator maintains a stack of nodes to visit and tracks discovered nodes.
3168
+ * It provides lazy evaluation of the depth-first search.
3169
+ *
3170
+ * @example
3171
+ * ```ts
3172
+ * import { Graph } from "effect"
3173
+ *
3174
+ * const graph = Graph.directed<string, number>((mutable) => {
3175
+ * const a = Graph.addNode(mutable, "A")
3176
+ * const b = Graph.addNode(mutable, "B")
3177
+ * const c = Graph.addNode(mutable, "C")
3178
+ * Graph.addEdge(mutable, a, b, 1)
3179
+ * Graph.addEdge(mutable, b, c, 1)
3180
+ * })
3181
+ *
3182
+ * // Start from a specific node
3183
+ * const dfs1 = Graph.dfs(graph, { start: [0] })
3184
+ * for (const nodeIndex of Graph.indices(dfs1)) {
3185
+ * console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2
3186
+ * }
3187
+ *
3188
+ * // Empty iterator (no starting nodes)
3189
+ * const dfs2 = Graph.dfs(graph)
3190
+ * // Can be used programmatically
3191
+ * ```
3192
+ *
3193
+ * @since 3.18.0
3194
+ * @category iterators
3195
+ */
3196
+ export const dfs = <N, E, T extends Kind = "directed">(
3197
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
3198
+ config: SearchConfig = {}
3199
+ ): NodeWalker<N> => {
3200
+ const start = config.start ?? []
3201
+ const direction = config.direction ?? "outgoing"
3202
+
3203
+ // Validate that all start nodes exist
3204
+ for (const nodeIndex of start) {
3205
+ if (!hasNode(graph, nodeIndex)) {
3206
+ throw missingNode(nodeIndex)
3207
+ }
3208
+ }
3209
+
3210
+ return new Walker((f) => ({
3211
+ [Symbol.iterator]: () => {
3212
+ const stack = [...start]
3213
+ const discovered = new Set<NodeIndex>()
3214
+
3215
+ const nextMapped = () => {
3216
+ while (stack.length > 0) {
3217
+ const current = stack.pop()!
3218
+
3219
+ if (discovered.has(current)) {
3220
+ continue
3221
+ }
3222
+
3223
+ discovered.add(current)
3224
+
3225
+ const nodeDataOption = graph.nodes.get(current)
3226
+ if (nodeDataOption === undefined) {
3227
+ continue
3228
+ }
3229
+
3230
+ const neighbors = neighborsDirected(graph, current, direction)
3231
+ for (let i = neighbors.length - 1; i >= 0; i--) {
3232
+ const neighbor = neighbors[i]
3233
+ if (!discovered.has(neighbor)) {
3234
+ stack.push(neighbor)
3235
+ }
3236
+ }
3237
+
3238
+ return { done: false, value: f(current, nodeDataOption) }
3239
+ }
3240
+
3241
+ return { done: true, value: undefined } as const
3242
+ }
3243
+
3244
+ return { next: nextMapped }
3245
+ }
3246
+ }))
3247
+ }
3248
+
3249
+ /**
3250
+ * Creates a new BFS iterator with optional configuration.
3251
+ *
3252
+ * The iterator maintains a queue of nodes to visit and tracks discovered nodes.
3253
+ * It provides lazy evaluation of the breadth-first search.
3254
+ *
3255
+ * @example
3256
+ * ```ts
3257
+ * import { Graph } from "effect"
3258
+ *
3259
+ * const graph = Graph.directed<string, number>((mutable) => {
3260
+ * const a = Graph.addNode(mutable, "A")
3261
+ * const b = Graph.addNode(mutable, "B")
3262
+ * const c = Graph.addNode(mutable, "C")
3263
+ * Graph.addEdge(mutable, a, b, 1)
3264
+ * Graph.addEdge(mutable, b, c, 1)
3265
+ * })
3266
+ *
3267
+ * // Start from a specific node
3268
+ * const bfs1 = Graph.bfs(graph, { start: [0] })
3269
+ * for (const nodeIndex of Graph.indices(bfs1)) {
3270
+ * console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2
3271
+ * }
3272
+ *
3273
+ * // Empty iterator (no starting nodes)
3274
+ * const bfs2 = Graph.bfs(graph)
3275
+ * // Can be used programmatically
3276
+ * ```
3277
+ *
3278
+ * @since 3.18.0
3279
+ * @category iterators
3280
+ */
3281
+ export const bfs = <N, E, T extends Kind = "directed">(
3282
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
3283
+ config: SearchConfig = {}
3284
+ ): NodeWalker<N> => {
3285
+ const start = config.start ?? []
3286
+ const direction = config.direction ?? "outgoing"
3287
+
3288
+ // Validate that all start nodes exist
3289
+ for (const nodeIndex of start) {
3290
+ if (!hasNode(graph, nodeIndex)) {
3291
+ throw missingNode(nodeIndex)
3292
+ }
3293
+ }
3294
+
3295
+ return new Walker((f) => ({
3296
+ [Symbol.iterator]: () => {
3297
+ const queue = [...start]
3298
+ const discovered = new Set<NodeIndex>()
3299
+
3300
+ const nextMapped = () => {
3301
+ while (queue.length > 0) {
3302
+ const current = queue.shift()!
3303
+
3304
+ if (!discovered.has(current)) {
3305
+ discovered.add(current)
3306
+
3307
+ const neighbors = neighborsDirected(graph, current, direction)
3308
+ for (const neighbor of neighbors) {
3309
+ if (!discovered.has(neighbor)) {
3310
+ queue.push(neighbor)
3311
+ }
3312
+ }
3313
+
3314
+ const nodeData = getNode(graph, current)
3315
+ if (Option.isSome(nodeData)) {
3316
+ return { done: false, value: f(current, nodeData.value) }
3317
+ }
3318
+ return nextMapped()
3319
+ }
3320
+ }
3321
+
3322
+ return { done: true, value: undefined } as const
3323
+ }
3324
+
3325
+ return { next: nextMapped }
3326
+ }
3327
+ }))
3328
+ }
3329
+
3330
+ /**
3331
+ * Configuration options for topological sort iterator.
3332
+ *
3333
+ * @since 3.18.0
3334
+ * @category models
3335
+ */
3336
+ export interface TopoConfig {
3337
+ readonly initials?: Array<NodeIndex>
3338
+ }
3339
+
3340
+ /**
3341
+ * Creates a new topological sort iterator with optional configuration.
3342
+ *
3343
+ * The iterator uses Kahn's algorithm to lazily produce nodes in topological order.
3344
+ * Throws an error if the graph contains cycles.
3345
+ *
3346
+ * @example
3347
+ * ```ts
3348
+ * import { Graph } from "effect"
3349
+ *
3350
+ * const graph = Graph.directed<string, number>((mutable) => {
3351
+ * const a = Graph.addNode(mutable, "A")
3352
+ * const b = Graph.addNode(mutable, "B")
3353
+ * const c = Graph.addNode(mutable, "C")
3354
+ * Graph.addEdge(mutable, a, b, 1)
3355
+ * Graph.addEdge(mutable, b, c, 1)
3356
+ * })
3357
+ *
3358
+ * // Standard topological sort
3359
+ * const topo1 = Graph.topo(graph)
3360
+ * for (const nodeIndex of Graph.indices(topo1)) {
3361
+ * console.log(nodeIndex) // 0, 1, 2 (topological order)
3362
+ * }
3363
+ *
3364
+ * // With initial nodes
3365
+ * const topo2 = Graph.topo(graph, { initials: [0] })
3366
+ *
3367
+ * // Throws error for cyclic graph
3368
+ * const cyclicGraph = Graph.directed<string, number>((mutable) => {
3369
+ * const a = Graph.addNode(mutable, "A")
3370
+ * const b = Graph.addNode(mutable, "B")
3371
+ * Graph.addEdge(mutable, a, b, 1)
3372
+ * Graph.addEdge(mutable, b, a, 2) // Creates cycle
3373
+ * })
3374
+ *
3375
+ * try {
3376
+ * Graph.topo(cyclicGraph) // Throws: "Cannot perform topological sort on cyclic graph"
3377
+ * } catch (error) {
3378
+ * console.log((error as Error).message)
3379
+ * }
3380
+ * ```
3381
+ *
3382
+ * @since 3.18.0
3383
+ * @category iterators
3384
+ */
3385
+ export const topo = <N, E, T extends Kind = "directed">(
3386
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
3387
+ config: TopoConfig = {}
3388
+ ): NodeWalker<N> => {
3389
+ // Check if graph is acyclic first
3390
+ if (!isAcyclic(graph)) {
3391
+ throw new Error("Cannot perform topological sort on cyclic graph")
3392
+ }
3393
+
3394
+ const initials = config.initials ?? []
3395
+
3396
+ // Validate that all initial nodes exist
3397
+ for (const nodeIndex of initials) {
3398
+ if (!hasNode(graph, nodeIndex)) {
3399
+ throw missingNode(nodeIndex)
3400
+ }
3401
+ }
3402
+
3403
+ return new Walker((f) => ({
3404
+ [Symbol.iterator]: () => {
3405
+ const inDegree = new Map<NodeIndex, number>()
3406
+ const remaining = new Set<NodeIndex>()
3407
+ const queue = [...initials]
3408
+
3409
+ // Initialize in-degree counts
3410
+ for (const [nodeIndex] of graph.nodes) {
3411
+ inDegree.set(nodeIndex, 0)
3412
+ remaining.add(nodeIndex)
3413
+ }
3414
+
3415
+ // Calculate in-degrees
3416
+ for (const [, edgeData] of graph.edges) {
3417
+ const currentInDegree = inDegree.get(edgeData.target) || 0
3418
+ inDegree.set(edgeData.target, currentInDegree + 1)
3419
+ }
3420
+
3421
+ // Add nodes with zero in-degree to queue if no initials provided
3422
+ if (initials.length === 0) {
3423
+ for (const [nodeIndex, degree] of inDegree) {
3424
+ if (degree === 0) {
3425
+ queue.push(nodeIndex)
3426
+ }
3427
+ }
3428
+ }
3429
+
3430
+ const nextMapped = () => {
3431
+ while (queue.length > 0) {
3432
+ const current = queue.shift()!
3433
+
3434
+ if (remaining.has(current)) {
3435
+ remaining.delete(current)
3436
+
3437
+ // Process outgoing edges, reducing in-degree of targets
3438
+ const neighbors = neighborsDirected(graph, current, "outgoing")
3439
+ for (const neighbor of neighbors) {
3440
+ if (remaining.has(neighbor)) {
3441
+ const currentInDegree = inDegree.get(neighbor) || 0
3442
+ const newInDegree = currentInDegree - 1
3443
+ inDegree.set(neighbor, newInDegree)
3444
+
3445
+ // If in-degree becomes 0, add to queue
3446
+ if (newInDegree === 0) {
3447
+ queue.push(neighbor)
3448
+ }
3449
+ }
3450
+ }
3451
+
3452
+ const nodeData = getNode(graph, current)
3453
+ if (Option.isSome(nodeData)) {
3454
+ return { done: false, value: f(current, nodeData.value) }
3455
+ }
3456
+ return nextMapped()
3457
+ }
3458
+ }
3459
+
3460
+ return { done: true, value: undefined } as const
3461
+ }
3462
+
3463
+ return { next: nextMapped }
3464
+ }
3465
+ }))
3466
+ }
3467
+
3468
+ /**
3469
+ * Creates a new DFS postorder iterator with optional configuration.
3470
+ *
3471
+ * The iterator maintains a stack with visit state tracking and emits nodes
3472
+ * in postorder (after all descendants have been processed). Essential for
3473
+ * dependency resolution and tree destruction algorithms.
3474
+ *
3475
+ * @example
3476
+ * ```ts
3477
+ * import { Graph } from "effect"
3478
+ *
3479
+ * const graph = Graph.directed<string, number>((mutable) => {
3480
+ * const root = Graph.addNode(mutable, "root")
3481
+ * const child1 = Graph.addNode(mutable, "child1")
3482
+ * const child2 = Graph.addNode(mutable, "child2")
3483
+ * Graph.addEdge(mutable, root, child1, 1)
3484
+ * Graph.addEdge(mutable, root, child2, 1)
3485
+ * })
3486
+ *
3487
+ * // Postorder: children before parents
3488
+ * const postOrder = Graph.dfsPostOrder(graph, { start: [0] })
3489
+ * for (const node of postOrder) {
3490
+ * console.log(node) // 1, 2, 0
3491
+ * }
3492
+ * ```
3493
+ *
3494
+ * @since 3.18.0
3495
+ * @category iterators
3496
+ */
3497
+ export const dfsPostOrder = <N, E, T extends Kind = "directed">(
3498
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
3499
+ config: SearchConfig = {}
3500
+ ): NodeWalker<N> => {
3501
+ const start = config.start ?? []
3502
+ const direction = config.direction ?? "outgoing"
3503
+
3504
+ // Validate that all start nodes exist
3505
+ for (const nodeIndex of start) {
3506
+ if (!hasNode(graph, nodeIndex)) {
3507
+ throw missingNode(nodeIndex)
3508
+ }
3509
+ }
3510
+
3511
+ return new Walker((f) => ({
3512
+ [Symbol.iterator]: () => {
3513
+ const stack: Array<{ node: NodeIndex; visitedChildren: boolean }> = []
3514
+ const discovered = new Set<NodeIndex>()
3515
+ const finished = new Set<NodeIndex>()
3516
+
3517
+ // Initialize stack with start nodes
3518
+ for (let i = start.length - 1; i >= 0; i--) {
3519
+ stack.push({ node: start[i], visitedChildren: false })
3520
+ }
3521
+
3522
+ const nextMapped = () => {
3523
+ while (stack.length > 0) {
3524
+ const current = stack[stack.length - 1]
3525
+
3526
+ if (!discovered.has(current.node)) {
3527
+ discovered.add(current.node)
3528
+ current.visitedChildren = false
3529
+ }
3530
+
3531
+ if (!current.visitedChildren) {
3532
+ current.visitedChildren = true
3533
+ const neighbors = neighborsDirected(graph, current.node, direction)
3534
+
3535
+ for (let i = neighbors.length - 1; i >= 0; i--) {
3536
+ const neighbor = neighbors[i]
3537
+ if (!discovered.has(neighbor) && !finished.has(neighbor)) {
3538
+ stack.push({ node: neighbor, visitedChildren: false })
3539
+ }
3540
+ }
3541
+ } else {
3542
+ const nodeToEmit = stack.pop()!.node
3543
+
3544
+ if (!finished.has(nodeToEmit)) {
3545
+ finished.add(nodeToEmit)
3546
+
3547
+ const nodeData = getNode(graph, nodeToEmit)
3548
+ if (Option.isSome(nodeData)) {
3549
+ return { done: false, value: f(nodeToEmit, nodeData.value) }
3550
+ }
3551
+ return nextMapped()
3552
+ }
3553
+ }
3554
+ }
3555
+
3556
+ return { done: true, value: undefined } as const
3557
+ }
3558
+
3559
+ return { next: nextMapped }
3560
+ }
3561
+ }))
3562
+ }
3563
+
3564
+ /**
3565
+ * Creates an iterator over all node indices in the graph.
3566
+ *
3567
+ * The iterator produces node indices in the order they were added to the graph.
3568
+ * This provides access to all nodes regardless of connectivity.
3569
+ *
3570
+ * @example
3571
+ * ```ts
3572
+ * import { Graph } from "effect"
3573
+ *
3574
+ * const graph = Graph.directed<string, number>((mutable) => {
3575
+ * const a = Graph.addNode(mutable, "A")
3576
+ * const b = Graph.addNode(mutable, "B")
3577
+ * const c = Graph.addNode(mutable, "C")
3578
+ * Graph.addEdge(mutable, a, b, 1)
3579
+ * })
3580
+ *
3581
+ * const indices = Array.from(Graph.indices(Graph.nodes(graph)))
3582
+ * console.log(indices) // [0, 1, 2]
3583
+ * ```
3584
+ *
3585
+ * @since 3.18.0
3586
+ * @category iterators
3587
+ */
3588
+ export const nodes = <N, E, T extends Kind = "directed">(
3589
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>
3590
+ ): NodeWalker<N> =>
3591
+ new Walker((f) => ({
3592
+ [Symbol.iterator]() {
3593
+ const nodeMap = graph.nodes
3594
+ const iterator = nodeMap.entries()
3595
+
3596
+ return {
3597
+ next() {
3598
+ const result = iterator.next()
3599
+ if (result.done) {
3600
+ return { done: true, value: undefined }
3601
+ }
3602
+ const [nodeIndex, nodeData] = result.value
3603
+ return { done: false, value: f(nodeIndex, nodeData) }
3604
+ }
3605
+ }
3606
+ }
3607
+ }))
3608
+
3609
+ /**
3610
+ * Creates an iterator over all edge indices in the graph.
3611
+ *
3612
+ * The iterator produces edge indices in the order they were added to the graph.
3613
+ * This provides access to all edges regardless of connectivity.
3614
+ *
3615
+ * @example
3616
+ * ```ts
3617
+ * import { Graph } from "effect"
3618
+ *
3619
+ * const graph = Graph.directed<string, number>((mutable) => {
3620
+ * const a = Graph.addNode(mutable, "A")
3621
+ * const b = Graph.addNode(mutable, "B")
3622
+ * const c = Graph.addNode(mutable, "C")
3623
+ * Graph.addEdge(mutable, a, b, 1)
3624
+ * Graph.addEdge(mutable, b, c, 2)
3625
+ * })
3626
+ *
3627
+ * const indices = Array.from(Graph.indices(Graph.edges(graph)))
3628
+ * console.log(indices) // [0, 1]
3629
+ * ```
3630
+ *
3631
+ * @since 3.18.0
3632
+ * @category iterators
3633
+ */
3634
+ export const edges = <N, E, T extends Kind = "directed">(
3635
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>
3636
+ ): EdgeWalker<E> =>
3637
+ new Walker((f) => ({
3638
+ [Symbol.iterator]() {
3639
+ const edgeMap = graph.edges
3640
+ const iterator = edgeMap.entries()
3641
+
3642
+ return {
3643
+ next() {
3644
+ const result = iterator.next()
3645
+ if (result.done) {
3646
+ return { done: true, value: undefined }
3647
+ }
3648
+ const [edgeIndex, edgeData] = result.value
3649
+ return { done: false, value: f(edgeIndex, edgeData) }
3650
+ }
3651
+ }
3652
+ }
3653
+ }))
3654
+
3655
+ /**
3656
+ * Configuration for externals iterator.
3657
+ *
3658
+ * @since 3.18.0
3659
+ * @category models
3660
+ */
3661
+ export interface ExternalsConfig {
3662
+ readonly direction?: Direction
3663
+ }
3664
+
3665
+ /**
3666
+ * Creates an iterator over external nodes (nodes without edges in specified direction).
3667
+ *
3668
+ * External nodes are nodes that have no outgoing edges (direction="outgoing") or
3669
+ * no incoming edges (direction="incoming"). These are useful for finding
3670
+ * sources, sinks, or isolated nodes.
3671
+ *
3672
+ * @example
3673
+ * ```ts
3674
+ * import { Graph } from "effect"
3675
+ *
3676
+ * const graph = Graph.directed<string, number>((mutable) => {
3677
+ * const source = Graph.addNode(mutable, "source") // 0 - no incoming
3678
+ * const middle = Graph.addNode(mutable, "middle") // 1 - has both
3679
+ * const sink = Graph.addNode(mutable, "sink") // 2 - no outgoing
3680
+ * const isolated = Graph.addNode(mutable, "isolated") // 3 - no edges
3681
+ *
3682
+ * Graph.addEdge(mutable, source, middle, 1)
3683
+ * Graph.addEdge(mutable, middle, sink, 2)
3684
+ * })
3685
+ *
3686
+ * // Nodes with no outgoing edges (sinks + isolated)
3687
+ * const sinks = Array.from(Graph.indices(Graph.externals(graph, { direction: "outgoing" })))
3688
+ * console.log(sinks) // [2, 3]
3689
+ *
3690
+ * // Nodes with no incoming edges (sources + isolated)
3691
+ * const sources = Array.from(Graph.indices(Graph.externals(graph, { direction: "incoming" })))
3692
+ * console.log(sources) // [0, 3]
3693
+ * ```
3694
+ *
3695
+ * @since 3.18.0
3696
+ * @category iterators
3697
+ */
3698
+ export const externals = <N, E, T extends Kind = "directed">(
3699
+ graph: Graph<N, E, T> | MutableGraph<N, E, T>,
3700
+ config: ExternalsConfig = {}
3701
+ ): NodeWalker<N> => {
3702
+ const direction = config.direction ?? "outgoing"
3703
+
3704
+ return new Walker((f) => ({
3705
+ [Symbol.iterator]: () => {
3706
+ const nodeMap = graph.nodes
3707
+ const adjacencyMap = direction === "incoming"
3708
+ ? graph.reverseAdjacency
3709
+ : graph.adjacency
3710
+
3711
+ const nodeIterator = nodeMap.entries()
3712
+
3713
+ const nextMapped = () => {
3714
+ let current = nodeIterator.next()
3715
+ while (!current.done) {
3716
+ const [nodeIndex, nodeData] = current.value
3717
+ const adjacencyList = adjacencyMap.get(nodeIndex)
3718
+
3719
+ // Node is external if it has no edges in the specified direction
3720
+ if (adjacencyList === undefined || adjacencyList.length === 0) {
3721
+ return { done: false, value: f(nodeIndex, nodeData) }
3722
+ }
3723
+ current = nodeIterator.next()
3724
+ }
3725
+
3726
+ return { done: true, value: undefined } as const
3727
+ }
3728
+
3729
+ return { next: nextMapped }
3730
+ }
3731
+ }))
3732
+ }