├── project ├── build.properties └── plugins.sbt ├── .travis.yml ├── src ├── main │ └── scala │ │ └── rxscalajs │ │ ├── subjects │ │ ├── AsyncSubject.scala │ │ ├── BehaviorSubject.scala │ │ └── ReplaySubject.scala │ │ ├── ConnectableObservable.scala │ │ ├── facade │ │ ├── BehaviorSubjectFacade.scala │ │ ├── ReplaySubjectFacade.scala │ │ ├── AsyncSubjectFacade.scala │ │ ├── ConnectableObservableFacade.scala │ │ ├── SubjectFacade.scala │ │ └── ObservableFacade.scala │ │ ├── subscription │ │ ├── InnerSubscriber.scala │ │ ├── SubjectSubscription.scala │ │ ├── OuterSubscriber.scala │ │ ├── Subscription.scala │ │ ├── Subscriber.scala │ │ └── ObserverFacade.scala │ │ ├── scheduler │ │ └── Action.scala │ │ ├── CombineObservable.scala │ │ ├── Observer.scala │ │ ├── Subject.scala │ │ ├── dom │ │ ├── Ajax.scala │ │ └── AjaxObservableFacade.scala │ │ ├── Scheduler.scala │ │ └── Notification.scala └── test │ └── scala │ └── rxscalajs │ ├── LawTests.scala │ └── ObservableTest.scala ├── .gitattributes ├── .gitignore └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.16 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | jdk: 3 | - oraclejdk8 4 | scala: 5 | - 2.12.0 6 | - 2.11.8 7 | - 2.10.6 8 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += "jgit-repo" at "http://download.eclipse.org/jgit/maven" 2 | 3 | addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2") 4 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.20") 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.0.0") 6 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") 7 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") 8 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subjects/AsyncSubject.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subjects 2 | 3 | 4 | import rxscalajs.Subject 5 | import rxscalajs.facade.AsyncSubjectFacade 6 | 7 | 8 | class AsyncSubject[T] protected(inner: AsyncSubjectFacade[T]) extends Subject[T](inner) 9 | 10 | object AsyncSubject { 11 | def apply[T](): AsyncSubject[T] = new AsyncSubject(new AsyncSubjectFacade()) 12 | } -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/ConnectableObservable.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import rxscalajs.facade.ConnectableObservableFacade 4 | import rxscalajs.subscription.Subscription 5 | 6 | class ConnectableObservable[+T] protected[rxscalajs](val inner: ConnectableObservableFacade[T]) { 7 | def connect: Subscription = inner.connect 8 | def refCount: Observable[T] = new Observable[T](inner.refCount) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subjects/BehaviorSubject.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subjects 2 | 3 | import rxscalajs.Subject 4 | import rxscalajs.facade.BehaviorSubjectFacade 5 | 6 | 7 | class BehaviorSubject[T] protected(inner: BehaviorSubjectFacade[T]) extends Subject[T](inner) 8 | 9 | 10 | object BehaviorSubject { 11 | def apply[T](defaultValue: T): BehaviorSubject[T] = new BehaviorSubject(new BehaviorSubjectFacade(defaultValue)) 12 | } -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/BehaviorSubjectFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import scala.scalajs.js 3 | import scala.scalajs.js.annotation.JSImport 4 | 5 | 6 | @js.native 7 | @JSImport("rxjs/Rx", "BehaviorSubject", globalFallback = "Rx.BehaviorSubject") 8 | class BehaviorSubjectFacade[T] protected() extends SubjectFacade[T] { 9 | def this(_value: T) = this() 10 | def getValue(): T = js.native 11 | var value: T = js.native 12 | } 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/InnerSubscriber.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | 7 | @js.native 8 | @JSImport("rxjs/Rx", "InnerSubscription", globalFallback = "Rx.InnerSubscription") 9 | class InnerSubscriber[T, R] protected() extends Subscriber[R] { 10 | def this(parent: OuterSubscriber[T, R], outerValue: T, outerIndex: Double) = this() 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/ReplaySubjectFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import rxscalajs.Scheduler 3 | 4 | import scala.scalajs.js.annotation.JSImport 5 | import scala.scalajs.js 6 | 7 | 8 | @js.native 9 | @JSImport("rxjs/Rx", "ReplaySubject", globalFallback = "Rx.ReplaySubject") 10 | class ReplaySubjectFacade[T] protected() extends SubjectFacade[T] { 11 | def this(bufferSize: Int = ???, windowTime: Int = ???, scheduler: Scheduler = ???) = this() 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/SubjectSubscription.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import rxscalajs.facade.SubjectFacade 4 | 5 | import scala.scalajs.js.annotation.JSImport 6 | import scala.scalajs.js 7 | 8 | @js.native 9 | @JSImport("rxjs/Rx", "SubjectSubscription", globalFallback = "Rx.SubjectSubscription") 10 | class SubjectSubscription protected () extends Subscription { 11 | def this(subject: SubjectFacade[js.Any], observer: ObserverFacade[js.Any]) = this() 12 | var subject: SubjectFacade[js.Any] = js.native 13 | var observer: ObserverFacade[js.Any] = js.native 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/AsyncSubjectFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import rxscalajs.subscription.{AnonymousSubscription, Subscriber} 3 | 4 | import scala.scalajs.js 5 | import scala.scalajs.js.annotation.JSImport 6 | 7 | 8 | @js.native 9 | @JSImport("rxjs/Rx", "AsyncSubject", globalFallback = "Rx.AsyncSubject") 10 | class AsyncSubjectFacade[T] extends SubjectFacade[T] { 11 | var value: T = js.native 12 | var hasNext: Boolean = js.native 13 | def _subscribe(subscriber: Subscriber[js.Any]): AnonymousSubscription = js.native 14 | def _next(value: T): Unit = js.native 15 | def _complete(): Unit = js.native 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/ConnectableObservableFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | 3 | import rxscalajs.subscription.Subscription 4 | 5 | import scala.scalajs.js.annotation.JSImport 6 | import scala.scalajs.js 7 | 8 | 9 | 10 | 11 | 12 | @js.native 13 | @JSImport("rxjs/Rx", "ConnectableObservable", globalFallback = "Rx.ConnectableObservable") 14 | class ConnectableObservableFacade[+T] protected() extends ObservableFacade[T] { 15 | def this(source: ObservableFacade[T], subjectFactory: js.Function0[SubjectFacade[T]]) = this() 16 | def connect(): Subscription = js.native 17 | def refCount(): ObservableFacade[T] = js.native 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/OuterSubscriber.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | 7 | @js.native 8 | @JSImport("rxjs/Rx", "OuterSubscriber", globalFallback = "Rx.OuterSubscriber") 9 | class OuterSubscriber[T, R] extends Subscriber[T] { 10 | def notifyNext(outerValue: T, innerValue: R, outerIndex: Double, innerIndex: Double, innerSub: InnerSubscriber[T, R]): Unit = js.native 11 | def notifyError(error: js.Any, innerSub: InnerSubscriber[T, R]): Unit = js.native 12 | def notifyComplete(innerSub: InnerSubscriber[T, R]): Unit = js.native 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/scheduler/Action.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.scheduler 2 | 3 | import rxscalajs.subscription.Subscription 4 | 5 | import scala.scalajs.js 6 | import js.annotation._ 7 | import js.| 8 | 9 | 10 | 11 | import rxscalajs.Scheduler 12 | 13 | @js.native 14 | trait Action[T] extends Subscription { 15 | var work: js.Function1[T, Unit] | Subscription = js.native 16 | var state: T = js.native 17 | var delay: Double = js.native 18 | def schedule(state: T = ???, delay: Double = ???): Unit = js.native 19 | def execute(): Unit = js.native 20 | var scheduler: Scheduler = js.native 21 | var error: js.Any = js.native 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/CombineObservable.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import cats._ 4 | 5 | class CombineObservable[A](val value: Observable[A]) extends AnyVal 6 | object CombineObservable { 7 | implicit def combineObservableApplicative: Applicative[CombineObservable] = new Applicative[CombineObservable] { 8 | def pure[A](x: A): CombineObservable[A] = new CombineObservable(Observable.just(x)) 9 | 10 | def ap[A, B](ff: CombineObservable[(A) => B])(fa: CombineObservable[A]) = new CombineObservable( 11 | ff.value.combineLatestWith(fa.value)((f, a) => f(a)) 12 | ) 13 | 14 | override def map[A, B](fa: CombineObservable[A])(f: A => B): CombineObservable[B] = 15 | new CombineObservable(fa.value.map(f)) 16 | 17 | override def product[A, B](fa: CombineObservable[A], fb: CombineObservable[B]): CombineObservable[(A, B)] = 18 | new CombineObservable(fa.value.combineLatest(fb.value)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subjects/ReplaySubject.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subjects 2 | 3 | import rxscalajs.{Scheduler, Subject} 4 | import rxscalajs.facade.ReplaySubjectFacade 5 | 6 | import scala.concurrent.duration.FiniteDuration 7 | 8 | 9 | class ReplaySubject[T] protected(inner: ReplaySubjectFacade[T]) extends Subject[T](inner) 10 | 11 | 12 | object ReplaySubject { 13 | def apply[T](): ReplaySubject[T] = new ReplaySubject(new ReplaySubjectFacade()) 14 | def withSize[T](bufferSize: Int): ReplaySubject[T] = new ReplaySubject(new ReplaySubjectFacade(bufferSize)) 15 | def withTime[T](time: FiniteDuration, scheduler: Scheduler): ReplaySubject[T] = new ReplaySubject(new ReplaySubjectFacade(windowTime = time.toMillis.toInt, scheduler = scheduler)) 16 | def withTimeAndSize[T](time: FiniteDuration, size: Int, scheduler: Scheduler): ReplaySubject[T] = new ReplaySubject(new ReplaySubjectFacade(size, time.toMillis.toInt,scheduler)) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/SubjectFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import rxscalajs.subscription.{AnonymousSubscription, ObserverFacade} 3 | 4 | import scala.scalajs.js 5 | import scala.scalajs.js.annotation._ 6 | 7 | 8 | 9 | @js.native 10 | @JSImport("rxjs/Rx", "Subject", globalFallback = "Rx.Subject") 11 | class SubjectFacade[T] protected() extends ObservableFacade[T] with AnonymousSubscription with ObserverFacade[T] { 12 | def this(destination: ObserverFacade[T] = ???, source: ObservableFacade[T] = ???) = this() 13 | 14 | override def next(value: T): Unit = js.native 15 | override def error(err: js.Any = ???): Unit = js.native 16 | override def complete(): Unit = js.native 17 | def asObservable(): ObservableFacade[T] = js.native 18 | def throwIfUnsubscribed(): js.Dynamic = js.native 19 | } 20 | 21 | @js.native 22 | @JSImport("rxjs/Rx", "Subject", globalFallback = "Rx.Subject") 23 | object SubjectFacade extends js.Object { 24 | var create: js.Function = js.native 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache 6 | .history 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | .idea/ 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | # Windows 50 | # ========================= 51 | 52 | # Windows image file caches 53 | Thumbs.db 54 | ehthumbs.db 55 | 56 | # Folder config file 57 | Desktop.ini 58 | 59 | # Recycle Bin used on file shares 60 | $RECYCLE.BIN/ 61 | 62 | # Windows Installer files 63 | *.cab 64 | *.msi 65 | *.msm 66 | *.msp 67 | 68 | # Windows shortcuts 69 | *.lnk 70 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/Observer.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import scala.scalajs.js 4 | 5 | /** 6 | * Provides a mechanism for receiving push-based notifications. 7 | * 8 | * After an Observer calls an [[rxscalajs.Observable]]'s `subscribe` method, the Observable 9 | * calls the Observer's `onNext` method to provide notifications. A well-behaved Observable will 10 | * call an Observer's `onCompleted` or `onError` methods exactly once. 11 | */ 12 | trait Observer[-T] { 13 | /** 14 | * Provides the Observer with new data. 15 | * 16 | * The [[rxscalajs.Observable]] calls this closure 0 or more times. 17 | * 18 | * The [[rxscalajs.Observable]] will not call this method again after it calls either `completed` or `error`. 19 | */ 20 | def next(t: T): Unit 21 | /** 22 | * Notifies the Observer that the [[rxscalajs.Observable]] has experienced an error condition. 23 | * 24 | * If the [[rxscalajs.Observable]] calls this method, it will not thereafter call `next` or `completed`. 25 | */ 26 | def error(err: js.Any): Unit 27 | /** 28 | * Notifies the Observer that the [[rxscalajs.Observable]] has finished sending push-based notifications. 29 | * 30 | * The [[rxscalajs.Observable]] will not call this method if it calls `error`. 31 | */ 32 | def complete(): Unit 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/Subscription.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.{JSImport, JSName} 5 | import scala.scalajs.js.| 6 | 7 | @js.native 8 | trait AnonymousSubscription extends js.Object { 9 | /** 10 | * Call this method to stop receiving notifications on the Observer that was registered when 11 | * this Subscription was received. 12 | */ 13 | def unsubscribe(): Unit = js.native 14 | } 15 | 16 | 17 | @js.native 18 | @JSImport("rxjs/Rx", "Subscription", globalFallback = "Rx.Subscription") 19 | /** 20 | * Subscriptions are returned from all `Observable.subscribe` methods to allow unsubscribing. 21 | * 22 | * This interface is the equivalent of `IDisposable` in the .NET Rx implementation. 23 | */ 24 | class Subscription protected () extends AnonymousSubscription { 25 | def add(teardown: Subscription | js.Function0[Unit]): Subscription = js.native 26 | def remove(sub: Subscription): Unit = js.native 27 | def this(unsubscribe: js.Function0[Unit] = js.native) = this() 28 | 29 | def closed: Boolean = js.native 30 | 31 | @JSName("closed") 32 | def isUnsubscribed: Boolean = js.native 33 | } 34 | 35 | @js.native 36 | @JSImport("rxjs/Rx", "Subscription", globalFallback = "Rx.Subscription") 37 | object Subscription extends js.Object { 38 | var EMPTY: Subscription = js.native 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/Subscriber.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.| 5 | import scala.scalajs.js.annotation.JSImport 6 | 7 | 8 | @js.native 9 | @JSImport("rxjs/Rx", "Subscriber", globalFallback = "Rx.Subscriber") 10 | /** 11 | * An extension of the [[ObserverFacade]] trait which adds subscription handling 12 | * (unsubscribe, isUnsubscribed, and `add` methods) and backpressure handling 13 | * (onStart and request methods). 14 | * 15 | * After a [[Subscriber]] calls an [[rxscalajs.Observable]]'s `subscribe` method, the 16 | * [[rxscalajs.Observable]] calls the [[Subscriber]]'s onNext method to emit items. A well-behaved 17 | * [[rxscalajs.Observable]] will call a [[Subscriber]]'s onCompleted method exactly once or the [[Subscriber]]'s 18 | * onError method exactly once. 19 | * 20 | * 21 | */ 22 | class Subscriber[T] () extends Subscription { 23 | def this(destinationOrNext: ObserverFacade[js.Any] | js.Function1[T, Unit] = ???, error: js.Function1[js.Any, Unit] = ???, complete: js.Function0[Unit] = ???) = this() 24 | 25 | def next(value: T = ???): Unit = js.native 26 | def error(err: js.Any = ???): Unit = js.native 27 | } 28 | 29 | @js.native 30 | @JSImport("rxjs/Rx", "Subscriber", globalFallback = "Rx.Subscriber") 31 | object Subscriber extends js.Object { 32 | def create[T](next: js.Function1[T, Unit] = ???, error: js.Function1[js.Any, Unit] = ???, complete: js.Function0[Unit] = ???): Subscriber[T] = js.native 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/Subject.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import rxscalajs.facade.SubjectFacade 4 | 5 | import scala.scalajs.js 6 | 7 | /** 8 | * A Subject is an Observable and an Observer at the same time. 9 | * 10 | */ 11 | class Subject[T] protected(inner: SubjectFacade[T]) extends Observable[T](inner) with Observer[T] { 12 | 13 | def next(value: T): Unit = inner.next(value) 14 | def error(err: js.Any): Unit = inner.error(err) 15 | def complete(): Unit = inner.complete() 16 | def asObservable(): Observable[T] = this 17 | def unsubscribe(): Unit = inner.unsubscribe() 18 | 19 | 20 | } 21 | /** 22 | * Subject that, once an `Observer` has subscribed, emits all subsequently observed items to the 23 | * subscriber. 24 | *
25 | *
26 | *
27 | *
28 | * @example
29 | {{{
30 | val subject = Subject[String]()
31 | // observer1 will receive all onNext and onCompleted events
32 | subject.subscribe(observer1)
33 | subject.onNext("one")
34 | subject.onNext("two")
35 | // observer2 will only receive "three" and onCompleted
36 | subject.subscribe(observer2)
37 | subject.onNext("three")
38 | subject.onCompleted()
39 | }}}
40 | */
41 | object Subject {
42 | /**
43 | * Creates and returns a new `Subject`.
44 | *
45 | * @return the new `Subject`
46 | */
47 | def apply[T](): Subject[T] = new Subject(new SubjectFacade())
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/src/main/scala/rxscalajs/dom/Ajax.scala:
--------------------------------------------------------------------------------
1 | package rxscalajs.dom
2 |
3 | import org.scalajs.dom.XMLHttpRequest
4 |
5 | import scala.scalajs.js
6 | import scala.scalajs.js.JSON
7 |
8 |
9 | final case class Request(url: String,
10 | data: String = "",
11 | timeout: Int = 0,
12 | headers: Map[String, String] = Map.empty,
13 | crossDomain: Boolean = false,
14 | responseType: String = "",
15 | method: String = "GET")
16 |
17 | final case class Response(body: String, status: Int, responseType: String, xhr: XMLHttpRequest, response: js.Dynamic)
18 |
19 | object Ajax {
20 | import scala.scalajs.js.JSConverters._
21 | def toJsRequest(request: Request): AjaxRequest = {
22 | js.Dynamic.literal(
23 | url = request.url,
24 | body = request.data,
25 | timeout = request.timeout,
26 | headers = request.headers.toJSDictionary,
27 | crossDomain = request.crossDomain,
28 | responseType = request.responseType,
29 | method = request.method
30 | ).asInstanceOf[AjaxRequest]
31 | }
32 |
33 | def fromJsResponse(response: AjaxResponse): Response = {
34 | val body = response.responseText.getOrElse{
35 | if (response.responseType == "json") JSON.stringify(response.response)
36 | else response.response.toString
37 | }
38 | Response(
39 | body,
40 | response.status.toInt,
41 | response.responseType,
42 | response.xhr,
43 | response.response.asInstanceOf[js.Dynamic]
44 | )
45 | }
46 | }
--------------------------------------------------------------------------------
/src/test/scala/rxscalajs/LawTests.scala:
--------------------------------------------------------------------------------
1 | package rxscalajs
2 |
3 | import cats.Eq
4 | import cats.tests.CatsSuite
5 | import org.scalacheck.{Arbitrary, Gen}
6 | import cats.laws.discipline._
7 | import cats.kernel.laws.discipline._
8 |
9 | class LawTests extends CatsSuite {
10 |
11 | implicit def arbObservable[A: Arbitrary]: Arbitrary[Observable[A]] =
12 | Arbitrary(Gen.oneOf((for {
13 | e <- Arbitrary.arbitrary[A]
14 | e2 <- Arbitrary.arbitrary[A]
15 | } yield Observable.of(e, e2)
16 | ), (for {
17 | e <- Arbitrary.arbitrary[A]
18 | } yield Observable.of(e))))
19 |
20 | implicit def eqObservable[A: Eq]: Eq[Observable[A]] = new Eq[Observable[A]] {
21 | def eqv(x: Observable[A], y: Observable[A]) = {
22 | var a: Seq[A] = Seq()
23 | var b: Seq[A] = Seq()
24 | x.toSeq.subscribe(xs => a = xs)
25 | y.toSeq.subscribe(ys => b = ys)
26 |
27 | a.toList === b.toList
28 | }
29 | }
30 |
31 | implicit def eqCombineObservable[A: Eq]: Eq[CombineObservable[A]] = Eq.by(_.value)
32 | implicit def arbCombineObservable[A: Arbitrary]: Arbitrary[CombineObservable[A]] =
33 | Arbitrary(arbObservable[A].arbitrary.map(a => new CombineObservable(a)))
34 |
35 | checkAll("Observable.MonadLaws", MonadTests[Observable].monad[Int, Int, String])
36 | checkAll("Observable.MonoidKLaws", MonoidKTests[Observable].monoidK[Int])
37 | checkAll("CombineObservable.ApplicativeLaws", ApplicativeTests[CombineObservable].apply[Int, Int, String])
38 | checkAll("Observable.ParallelLaws", ParallelTests[Observable, CombineObservable].parallel[Int, String])
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/scala/rxscalajs/Scheduler.scala:
--------------------------------------------------------------------------------
1 | package rxscalajs
2 |
3 | import rxscalajs.subscription.Subscription
4 |
5 | import scala.scalajs.js
6 | import js.annotation._
7 | import js.|
8 |
9 |
10 |
11 | @js.native
12 | /**
13 | * Represents an object that schedules units of work.
14 | */
15 | trait Scheduler extends js.Object {
16 | /**
17 | * @return the scheduler's notion of current absolute time in milliseconds.
18 | */
19 | def now(): Double = js.native
20 | /**
21 | * Schedules an Action for execution.
22 | *
23 | * @param work the Action to schedule
24 | * @return a subscription to be able to unsubscribe the action (unschedule it if not executed)
25 | */
26 | def schedule[T](work: js.Function1[T, Subscription | Unit], delay: Double = ???, state: T = ???): Subscription = js.native
27 | def flush(): Unit = js.native
28 | }
29 |
30 | @js.native
31 | @JSImport("rxjs/Rx", "Scheduler", globalFallback = "Rx.Scheduler")
32 | /**
33 | * Represents an object that schedules units of work.
34 | */
35 | object Scheduler extends js.Object{
36 | /**
37 | * Schedules on a queue in the current event frame (trampoline scheduler).
38 | * Use this for iteration operations.
39 | */
40 | val queue: Scheduler = js.native
41 | /**
42 | * Schedules on the micro task queue, which uses the fastest transport mechanism available,
43 | * either Node.js'`process.nextTick()` or Web Worker MessageChannel or setTimeout or others.
44 | * Use this for asynchronous conversions.
45 | */
46 | val asap: Scheduler = js.native
47 | /**
48 | * Schedules work with `setInterval`. Use this for time-based operations.
49 | */
50 | val async: Scheduler = js.native
51 | }
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/main/scala/rxscalajs/subscription/ObserverFacade.scala:
--------------------------------------------------------------------------------
1 | package rxscalajs.subscription
2 |
3 | import scala.scalajs.js
4 |
5 |
6 |
7 | @js.native
8 | trait NextObserver[T] extends js.Object {
9 | /**
10 | * Provides the Observer with new data.
11 | *
12 | * The [[rxscalajs.Observable]] calls this closure 0 or more times.
13 | *
14 | * The [[rxscalajs.Observable]] will not call this method again after it calls either `onCompleted` or `onError`.
15 | */
16 | def next(t: T): Unit = js.native
17 | }
18 |
19 | @js.native
20 | trait ErrorObserver[T] extends js.Object {
21 | /**
22 | * Notifies the Observer that the [[rxscalajs.Observable]] has experienced an error condition.
23 | *
24 | * If the [[rxscalajs.Observable]] calls this method, it will not thereafter call `onNext` or `onCompleted`.
25 | */
26 | def error(err :js.Any): Unit = js.native
27 | }
28 |
29 | @js.native
30 | trait CompletionObserver[T] extends js.Object {
31 | /**
32 | * Notifies the Observer that the [[rxscalajs.Observable]] has finished sending push-based notifications.
33 | *
34 | * The [[rxscalajs.Observable]] will not call this method if it calls `onError`.
35 | */
36 | def complete(): Unit = js.native
37 | }
38 | /**
39 | Provides a mechanism for receiving push-based notifications.
40 | *
41 | * After an Observer calls an [[rxscalajs.Observable]]'s `subscribe` method, the Observable
42 | * calls the Observer's `onNext` method to provide notifications. A well-behaved Observable will
43 | * call an Observer's `onCompleted` or `onError` methods exactly once.
44 | */
45 | @js.native
46 | trait ObserverFacade[T] extends NextObserver[T] with ErrorObserver[T] with CompletionObserver[T]
47 |
48 |
--------------------------------------------------------------------------------
/src/main/scala/rxscalajs/Notification.scala:
--------------------------------------------------------------------------------
1 | package rxscalajs
2 |
3 | import rxscalajs.subscription.ObserverFacade
4 |
5 | import scala.scalajs.js
6 | import js.annotation._
7 | import js.|
8 |
9 |
10 |
11 | import rxscalajs.facade.ObservableFacade
12 |
13 | @js.native
14 | @JSImport("rxjs/Rx", "Notification", globalFallback = "Rx.Notification")
15 | /**
16 | * Emitted by Observables returned by [[rxscalajs.Observable.materialize]].
17 | */
18 | class Notification[T] protected () extends js.Object {
19 | def this(kind: String, value: T = ???, exception: js.Any = ???) = this()
20 | var kind: String = js.native
21 | var value: T = js.native
22 | var exception: js.Any = js.native
23 | var hasValue: Boolean = js.native
24 | def observe(observer: ObserverFacade[T]): js.Dynamic = js.native
25 | def `do`(next: js.Function1[T, Unit], error: js.Function1[js.Any, Unit] = ???, complete: js.Function0[Unit] = ???): js.Dynamic = js.native
26 | /**
27 | * Invokes the function corresponding to the notification.
28 | *
29 | * @param onNext
30 | * The function to invoke for an [[rxscalajs.Notification]] notification.
31 | * @param onError
32 | * The function to invoke for an [[rxscalajs.Notification]] notification.
33 | * @param onCompleted
34 | * The function to invoke for an [[rxscalajs.Notification]] notification.
35 | */
36 | def accept(onNext: ObserverFacade[T] | js.Function1[T, Unit], onError: js.Function1[js.Any, Unit] = ???, onCompleted: js.Function0[Unit] = ???): js.Dynamic = js.native
37 | def toObservable(): ObservableFacade[T] = js.native
38 | }
39 |
40 | @js.native
41 | @JSImport("rxjs/Rx", "Notification", globalFallback = "Rx.Notification")
42 | object Notification extends js.Object {
43 | def createNext[T](value: T): Notification[T] = js.native
44 | def createError[T](err: js.Any = js.native): Notification[T] = js.native
45 | def createComplete(): Notification[js.Any] = js.native
46 | }
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/main/scala/rxscalajs/dom/AjaxObservableFacade.scala:
--------------------------------------------------------------------------------
1 | package rxscalajs.dom
2 |
3 | import rxscalajs.subscription.Subscriber
4 |
5 | import scala.scalajs.js
6 | import scala.scalajs.js.annotation.JSImport
7 | import js.|
8 | import org.scalajs.dom._
9 | import rxscalajs.facade.ObservableFacade
10 |
11 | @js.native
12 | trait AjaxRequest extends js.Object {
13 | var url: String = js.native
14 | var body: js.Any = js.native
15 | var user: String = js.native
16 | var async: Boolean = js.native
17 | var method: String = js.native
18 | var headers: Object = js.native
19 | var timeout: Double = js.native
20 | var password: String = js.native
21 | var hasContent: Boolean = js.native
22 | var crossDomain: Boolean = js.native
23 | var createXHR: js.Function0[XMLHttpRequest] = js.native
24 | var progressSubscriber: Subscriber[js.Any] = js.native
25 | var resultSelector: js.Function = js.native
26 | var responseType: String = js.native
27 | }
28 |
29 | @js.native
30 | trait AjaxCreationMethod extends js.Object {
31 | def apply[T](urlOrRequest: String | AjaxRequest): ObservableFacade[T] = js.native
32 | def get[T](url: String, resultSelector: js.Function1[AjaxResponse, T] = ???, headers: Object = ???): ObservableFacade[T] = js.native
33 | def post[T](url: String, body: js.Any = ???, headers: Object = ???): ObservableFacade[T] = js.native
34 | def put[T](url: String, body: js.Any = ???, headers: Object = ???): ObservableFacade[T] = js.native
35 | def delete[T](url: String, headers: Object = ???): ObservableFacade[T] = js.native
36 | def getJSON[T, R](url: String, resultSelector: js.Function1[T, R] = ???, headers: Object = ???): ObservableFacade[R] = js.native
37 | }
38 |
39 | @js.native
40 | @JSImport("rxjs/Rx", "AjaxObservable", globalFallback = "Rx.AjaxObservable")
41 | class AjaxObservableFacade[T] protected() extends ObservableFacade[T] {
42 | def this(urlOrRequest: String | AjaxRequest) = this()
43 | }
44 |
45 | @js.native
46 | @JSImport("rxjs/Rx", "AjaxObservable", globalFallback = "Rx.AjaxObservable")
47 | object AjaxObservableFacade extends js.Object {
48 | var create: AjaxCreationMethod = js.native
49 | }
50 |
51 | @js.native
52 | @JSImport("rxjs/Rx", "AjaxSubscriber", globalFallback = "Rx.AjaxSubscriber")
53 | class AjaxSubscriber[T] protected () extends Subscriber[Event] {
54 | def this(destination: Subscriber[T], request: AjaxRequest) = this()
55 | var request: AjaxRequest = js.native
56 | }
57 |
58 | @js.native
59 | @JSImport("rxjs/Rx", "AjaxResponse", globalFallback = "Rx.AjaxResponse")
60 | class AjaxResponse protected () extends js.Object {
61 | def this(originalEvent: Event, xhr: XMLHttpRequest, request: AjaxRequest) = this()
62 | var originalEvent: Event = js.native
63 | var xhr: XMLHttpRequest = js.native
64 | var request: AjaxRequest = js.native
65 | var status: Double = js.native
66 | var response: js.Any = js.native
67 | var responseText: js.UndefOr[String] = js.native
68 | var responseType: String = js.native
69 | }
70 |
71 | @js.native
72 | @JSImport("rxjs/Rx", "Error", globalFallback = "Rx.Error")
73 | class Error protected() extends js.Object
74 |
75 |
76 | @js.native
77 | @JSImport("rxjs/Rx", "AjaxError", globalFallback = "Rx.AjaxError")
78 | class AjaxError protected () extends Error {
79 | def this(message: String, xhr: XMLHttpRequest, request: AjaxRequest) = this()
80 | var xhr: XMLHttpRequest = js.native
81 | var request: AjaxRequest = js.native
82 | var status: Double = js.native
83 | }
84 |
85 | @js.native
86 | @JSImport("rxjs/Rx", "AjaxTimeoutError", globalFallback = "Rx.AjaxTimeoutError")
87 | class AjaxTimeoutError protected () extends AjaxError {
88 | def this(xhr: XMLHttpRequest, request: AjaxRequest) = this()
89 | }
90 |
91 |
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RxScala.js [](https://travis-ci.org/LukaJCB/rxscala-js) [](https://www.scala-js.org) [](https://gitter.im/rxscala-js/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://maven-badges.herokuapp.com/maven-central/com.github.lukajcb/rxscala-js_sjs0.6_2.12)
2 | This is a Scala adapter to [RxJs](http://github.com/ReactiveX/RxJs).
3 |
4 | Example usage:
5 |
6 | ```scala
7 | val o = Observable.interval(200).take(5)
8 | o.subscribe(n => println("n = " + n))
9 | Observable.just(1, 2, 3, 4).reduce(_ + _)
10 | ```
11 |
12 | Example usage in Browser:
13 |
14 | ```scala
15 | Observable.fromEvent(document.getElementById("btn"),"click")
16 | .mapTo(1)
17 | .scan(0)(_ + _)
18 | .subscribe(n => println(s"Clicked $n times"))
19 | ```
20 |
21 | Getting Started
22 | -----
23 |
24 | Add the following to your sbt build definition:
25 |
26 |
27 | libraryDependencies += "com.github.lukajcb" %%% "rxscala-js" % "0.15.0"
28 |
29 |
30 | then import the types from the package `rxscalajs`.
31 |
32 | ### Javascript Dependencies
33 |
34 | RxScala.js doesn't actually come bundled with the underlying `rx.js` file, so you'll need to either add them manually or specify them as `jsDependencies`:
35 |
36 | jsDependencies += "org.webjars.npm" % "rxjs" % "5.4.0" / "bundles/Rx.min.js" commonJSName "Rx"
37 |
38 | ## Differences from RxJS
39 |
40 | Similary to RxScala, this wrapper attempts to expose an API which is as Scala-idiomatic as possible. Some examples:
41 |
42 | ```scala
43 | // instead of concat:
44 | def ++[U >: T](that: Observable[U]): Observable[U]
45 |
46 | // curried in Scala collections, so curry fold also here:
47 | def foldLeft[R](seed: R)(accumulator: (R, T) => R): Observable[R]
48 |
49 | // called skip in RxJS, but drop in Scala
50 | def drop(n: Int): Observable[T]
51 |
52 | // like in the collection API
53 | def zipWithIndex: Observable[(T, Int)]
54 |
55 | // the implicit evidence argument ensures that switch can only be called on Observables of Observables:
56 | def switch[U](implicit evidence: Observable[T] <:< Observable[Observable[U]]): Observable[U]
57 |
58 | ```
59 |
60 |
61 | ## Documentation
62 |
63 | RxScala.js:
64 |
65 | - The API documentation can be found [here](http://lukajcb.github.io/rxscala-js/latest/api/rxscalajs/Observable.html).
66 |
67 |
68 | RxJs:
69 |
70 | - [API Documentation](http://reactivex.io/rxjs)
71 |
72 |
73 | If you're new to Rx, I suggest starting with [this interactive tutorial.](http://reactivex.io/learnrx/)
74 |
75 | ## Samples
76 |
77 | - [The basics](https://github.com/LukaJCB/RxScalaJsSamples/blob/master/src/main/scala/samples/main/Samples.scala) - How to use some of the most important operations in RxScala.js
78 | - [Spaceship Reactive](https://lukajcb.github.io/RxScalaJsSamples/) - A port of Spaceship Reactive found in Sergi Mansillas awesome book [Reactive Programming with RxJS](https://pragprog.com/book/smreactjs/reactive-programming-with-rxjs). Code can be found [here](https://github.com/LukaJCB/RxScalaJsSamples).
79 | - [RxScala.js as a state store](https://github.com/LukaJCB/RxScalaJsSamples/blob/master/src/main/scala/samples/main/StateStore.scala) - A basic example on how to write a simple state store with RxScala.js. (Find the working example [here](https://lukajcb.github.io/RxScalaJsSamples/assets/state-store.html))
80 |
81 |
82 |
83 |
84 | ## Bugs and Feedback
85 |
86 | For bugs, questions and discussions please use the [Github Issues](https://github.com/LukaJCB/rxscala-js/issues).
87 |
88 | ## LICENSE
89 |
90 | Licensed under the Apache License, Version 2.0 (the "License");
91 | you may not use this file except in compliance with the License.
92 | You may obtain a copy of the License at
93 |
94 |