Repository: sveltejs/svelte (JavaScript)
Date: 2020-01-21 18:15:12+00:00
Task type: Bug Fix

Issue:
svelte/store `derived` doesn't handle RxJS observables
**Describe the bug**
The `derived` implementation doesn't handle RxJS Observables like other autosubscription stuff does.

**Logs**
```
TypeError: "fn is not a function"
```

**To Reproduce**
```svelte
<script>
	import { of } from 'rxjs';
	import { derived } from 'svelte/store';
	const store1 = of('foo');
	const store2 = derived(store1, _ => _);
	store2.subscribe()();
</script>
```

**Expected behavior**
Subscribing to and unsubscribing from a store derived from an observable should work. In particular, this means the 'look to see whether there's an `unsubscribe` method to call instead' logic we use elsewhere should also be use in the `derived` implementation.

**Information about your Svelte project:**
- Svelte 3.17.2
- REPL

**Severity**
Probably worth the bytes it would take to fix.

**Additional context**
A question came up in chat about using Observables in Svelte. I don't know whether the gap this issue describes is part of what was wrong, but the question did lead me to look into this.

We're already shipping a few extra bytes to everyone for RxJS support whether they're using it or not. It would be nice to be able to use the same helper in `derived` as we're already using in every Svelte project that uses autosubscription, but that might not be possible because of the two-argument `subscribe()` calls for diamond dependency stuff.


Issue discussion:

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## Unreleased
 
 * Fix updating a `<slot>` inside an `{#if}` or other block ([#4292](https://github.com/sveltejs/svelte/issues/4292))
+* Fix using RxJS observables in `derived` stores ([#4298](https://github.com/sveltejs/svelte/issues/4298))
 
 ## 3.17.2
 
diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts
--- a/src/runtime/internal/utils.ts
+++ b/src/runtime/internal/utils.ts
@@ -48,8 +48,8 @@ export function validate_store(store, name) {
 	}
 }
 
-export function subscribe(store, callback) {
-	const unsub = store.subscribe(callback);
+export function subscribe(store, ...callbacks) {
+	const unsub = store.subscribe(...callbacks);
 	return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
 }
 
diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts
--- a/src/runtime/store/index.ts
+++ b/src/runtime/store/index.ts
@@ -1,4 +1,4 @@
-import { run_all, noop, safe_not_equal, is_function, get_store_value } from 'svelte/internal';
+import { run_all, subscribe, noop, safe_not_equal, is_function, get_store_value } from 'svelte/internal';
 
 /** Callback to inform of a value updates. */
 type Subscriber<T> = (value: T) => void;
@@ -173,7 +173,8 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
 			}
 		};
 
-		const unsubscribers = stores_array.map((store, i) => store.subscribe(
+		const unsubscribers = stores_array.map((store, i) => subscribe(
+			store,
 			(value) => {
 				values[i] = value;
 				pending &= ~(1 << i);


Test patch:
diff --git a/test/store/index.js b/test/store/index.js
--- a/test/store/index.js
+++ b/test/store/index.js
@@ -116,6 +116,15 @@ describe('store', () => {
 		});
 	});
 
+	const fake_observable = {
+		subscribe(fn) {
+			fn(42);
+			return {
+				unsubscribe: () => {}
+			};
+		}
+	};
+
 	describe('derived', () => {
 		it('maps a single store', () => {
 			const a = writable(1);
@@ -346,6 +355,11 @@ describe('store', () => {
 			b.set(2);
 			assert.deepEqual(get(c), 'two 2');
 		});
+
+		it('works with RxJS-style observables', () => {
+			const d = derived(fake_observable, _ => _);
+			assert.equal(get(d), 42);
+		});
 	});
 
 	describe('get', () => {
@@ -355,16 +369,7 @@ describe('store', () => {
 		});
 
 		it('works with RxJS-style observables', () => {
-			const observable = {
-				subscribe(fn) {
-					fn(42);
-					return {
-						unsubscribe: () => {}
-					};
-				}
-			};
-
-			assert.equal(get(observable), 42);
+			assert.equal(get(fake_observable), 42);
 		});
 	});
 });