prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  NPM sync is broken
  https://gratipay.slack.com/archives/C36LJJF9V/p1494580201702422
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `gratipay/sync_npm.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 from __future__ import absolute_import, division, print_function, unicode_literals
  3 
  4 import requests
  5 from couchdb import Database
  6 
  7 from gratipay.models.package import NPM, Package
  8 
  9 
  10 REGISTRY_URL = 'https://replicate.npmjs.com/'
  11 
  12 
  13 def get_last_seq(db):
  14     return db.one('SELECT npm_last_seq FROM worker_coordination')
  15 
  16 
  17 def production_change_stream(seq):
  18     """Given a sequence number in the npm registry change stream, start
  19     streaming from there!
  20     """
  21     return Database(REGISTRY_URL).changes(feed='continuous', include_docs=True, since=seq)
  22 
  23 
  24 def process_doc(doc):
  25     """Return a smoothed-out doc, or None if it's not a package doc, meaning
  26     there's no name key and it's probably a design doc, per:
  27 
  28         https://github.com/npm/registry/blob/aef8a275/docs/follower.md#clean-up
  29 
  30     """
  31     if 'name' not in doc:
  32         return None
  33     name = doc['name']
  34     description = doc.get('description', '')
  35     emails = [e for e in [m.get('email') for m in doc.get('maintainers', [])] if e.strip()]
  36     return {'name': name, 'description': description, 'emails': sorted(set(emails))}
  37 
  38 
  39 def consume_change_stream(stream, db):
  40     """Given an iterable of CouchDB change notifications and a
  41     :py:class:`~GratipayDB`, read from the stream and write to the db.
  42 
  43     The npm registry is a CouchDB app, which means we get a change stream from
  44     it that allows us to follow registry updates in near-realtime. Our strategy
  45     here is to maintain open connections to both the registry and our own
  46     database, and write as we read.
  47 
  48     """
  49     with db.get_connection() as connection:
  50         for change in stream:
  51 
  52             # Decide what to do.
  53             if change.get('deleted'):
  54                 package = Package.from_names(NPM, change['id'])
  55                 assert package is not None  # right?
  56                 op, kw = package.delete, {}
  57             else:
  58                 op = Package.upsert
  59                 kw = process_doc(change['doc'])
  60                 if not kw:
  61                     continue
  62                 kw['package_manager'] = NPM
  63 
  64             # Do it.
  65             cursor = connection.cursor()
  66             kw['cursor'] = cursor
  67             op(**kw)
  68             cursor.run('UPDATE worker_coordination SET npm_last_seq=%(seq)s', change)
  69             connection.commit()
  70 
  71 
  72 def check(db, _print=print):
  73     ours = db.one('SELECT npm_last_seq FROM worker_coordination')
  74     theirs = int(requests.get(REGISTRY_URL).json()['update_seq'])
  75     _print("count#npm-sync-lag={}".format(theirs - ours))
  76
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/gratipay/sync_npm.py b/gratipay/sync_npm.py
  --- a/gratipay/sync_npm.py
  +++ b/gratipay/sync_npm.py
  @@ -52,7 +52,11 @@
               # Decide what to do.
               if change.get('deleted'):
                   package = Package.from_names(NPM, change['id'])
  -                assert package is not None  # right?
  +                if not package:
  +                    # As a result of CouchDB's compaction algorithm, we might
  +                    # receive 'deleted' events for docs even if we haven't seen
  +                    # the corresponding events for when the doc was created
  +                    continue
                   op, kw = package.delete, {}
               else:
                   op = Package.upsert
  
verification_info: {"golden_diff": "diff --git a/gratipay/sync_npm.py b/gratipay/sync_npm.py\n--- a/gratipay/sync_npm.py\n+++ b/gratipay/sync_npm.py\n@@ -52,7 +52,11 @@\n             # Decide what to do.\n             if change.get('deleted'):\n                 package = Package.from_names(NPM, change['id'])\n-                assert package is not None  # right?\n+                if not package:\n+                    # As a result of CouchDB's compaction algorithm, we might\n+                    # receive 'deleted' events for docs even if we haven't seen\n+                    # the corresponding events for when the doc was created\n+                    continue\n                 op, kw = package.delete, {}\n             else:\n                 op = Package.upsert\n", "issue": "NPM sync is broken\nhttps://gratipay.slack.com/archives/C36LJJF9V/p1494580201702422\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport requests\nfrom couchdb import Database\n\nfrom gratipay.models.package import NPM, Package\n\n\nREGISTRY_URL = 'https://replicate.npmjs.com/'\n\n\ndef get_last_seq(db):\n    return db.one('SELECT npm_last_seq FROM worker_coordination')\n\n\ndef production_change_stream(seq):\n    \"\"\"Given a sequence number in the npm registry change stream, start\n    streaming from there!\n    \"\"\"\n    return Database(REGISTRY_URL).changes(feed='continuous', include_docs=True, since=seq)\n\n\ndef process_doc(doc):\n    \"\"\"Return a smoothed-out doc, or None if it's not a package doc, meaning\n    there's no name key and it's probably a design doc, per:\n\n        https://github.com/npm/registry/blob/aef8a275/docs/follower.md#clean-up\n\n    \"\"\"\n    if 'name' not in doc:\n        return None\n    name = doc['name']\n    description = doc.get('description', '')\n    emails = [e for e in [m.get('email') for m in doc.get('maintainers', [])] if e.strip()]\n    return {'name': name, 'description': description, 'emails': sorted(set(emails))}\n\n\ndef consume_change_stream(stream, db):\n    \"\"\"Given an iterable of CouchDB change notifications and a\n    :py:class:`~GratipayDB`, read from the stream and write to the db.\n\n    The npm registry is a CouchDB app, which means we get a change stream from\n    it that allows us to follow registry updates in near-realtime. Our strategy\n    here is to maintain open connections to both the registry and our own\n    database, and write as we read.\n\n    \"\"\"\n    with db.get_connection() as connection:\n        for change in stream:\n\n            # Decide what to do.\n            if change.get('deleted'):\n                package = Package.from_names(NPM, change['id'])\n                assert package is not None  # right?\n                op, kw = package.delete, {}\n            else:\n                op = Package.upsert\n                kw = process_doc(change['doc'])\n                if not kw:\n                    continue\n                kw['package_manager'] = NPM\n\n            # Do it.\n            cursor = connection.cursor()\n            kw['cursor'] = cursor\n            op(**kw)\n            cursor.run('UPDATE worker_coordination SET npm_last_seq=%(seq)s', change)\n            connection.commit()\n\n\ndef check(db, _print=print):\n    ours = db.one('SELECT npm_last_seq FROM worker_coordination')\n    theirs = int(requests.get(REGISTRY_URL).json()['update_seq'])\n    _print(\"count#npm-sync-lag={}\".format(theirs - ours))\n", "path": "gratipay/sync_npm.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport requests\nfrom couchdb import Database\n\nfrom gratipay.models.package import NPM, Package\n\n\nREGISTRY_URL = 'https://replicate.npmjs.com/'\n\n\ndef get_last_seq(db):\n    return db.one('SELECT npm_last_seq FROM worker_coordination')\n\n\ndef production_change_stream(seq):\n    \"\"\"Given a sequence number in the npm registry change stream, start\n    streaming from there!\n    \"\"\"\n    return Database(REGISTRY_URL).changes(feed='continuous', include_docs=True, since=seq)\n\n\ndef process_doc(doc):\n    \"\"\"Return a smoothed-out doc, or None if it's not a package doc, meaning\n    there's no name key and it's probably a design doc, per:\n\n        https://github.com/npm/registry/blob/aef8a275/docs/follower.md#clean-up\n\n    \"\"\"\n    if 'name' not in doc:\n        return None\n    name = doc['name']\n    description = doc.get('description', '')\n    emails = [e for e in [m.get('email') for m in doc.get('maintainers', [])] if e.strip()]\n    return {'name': name, 'description': description, 'emails': sorted(set(emails))}\n\n\ndef consume_change_stream(stream, db):\n    \"\"\"Given an iterable of CouchDB change notifications and a\n    :py:class:`~GratipayDB`, read from the stream and write to the db.\n\n    The npm registry is a CouchDB app, which means we get a change stream from\n    it that allows us to follow registry updates in near-realtime. Our strategy\n    here is to maintain open connections to both the registry and our own\n    database, and write as we read.\n\n    \"\"\"\n    with db.get_connection() as connection:\n        for change in stream:\n\n            # Decide what to do.\n            if change.get('deleted'):\n                package = Package.from_names(NPM, change['id'])\n                if not package:\n                    # As a result of CouchDB's compaction algorithm, we might\n                    # receive 'deleted' events for docs even if we haven't seen\n                    # the corresponding events for when the doc was created\n                    continue\n                op, kw = package.delete, {}\n            else:\n                op = Package.upsert\n                kw = process_doc(change['doc'])\n                if not kw:\n                    continue\n                kw['package_manager'] = NPM\n\n            # Do it.\n            cursor = connection.cursor()\n            kw['cursor'] = cursor\n            op(**kw)\n            cursor.run('UPDATE worker_coordination SET npm_last_seq=%(seq)s', change)\n            connection.commit()\n\n\ndef check(db, _print=print):\n    ours = db.one('SELECT npm_last_seq FROM worker_coordination')\n    theirs = int(requests.get(REGISTRY_URL).json()['update_seq'])\n    _print(\"count#npm-sync-lag={}\".format(theirs - ours))\n", "path": "gratipay/sync_npm.py"}]}