{"id":16578013,"url":"https://github.com/glynnbird/changesreader","last_synced_at":"2025-10-29T05:30:33.554Z","repository":{"id":33022744,"uuid":"149630188","full_name":"glynnbird/changesreader","owner":"glynnbird","description":"CouchDB changes reader","archived":false,"fork":false,"pushed_at":"2023-01-07T22:58:53.000Z","size":319,"stargazers_count":6,"open_issues_count":3,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-01T22:23:30.178Z","etag":null,"topics":["changes","couchdb","nodejs"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/glynnbird.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-20T15:23:12.000Z","updated_at":"2024-07-25T01:58:48.000Z","dependencies_parsed_at":"2023-01-14T23:05:25.223Z","dependency_job_id":null,"html_url":"https://github.com/glynnbird/changesreader","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glynnbird%2Fchangesreader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glynnbird%2Fchangesreader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glynnbird%2Fchangesreader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glynnbird%2Fchangesreader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/glynnbird","download_url":"https://codeload.github.com/glynnbird/changesreader/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238776988,"owners_count":19528812,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["changes","couchdb","nodejs"],"created_at":"2024-10-11T22:12:52.767Z","updated_at":"2025-10-29T05:30:28.041Z","avatar_url":"https://github.com/glynnbird.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ChangesReader\n\nThe *ChangesReader* object allows a CouchDB databases's changes feed to be consumed across one or more HTTP requests. Once started, the *ChangesReader* will continuously poll the server for changes, handle network errors \u0026 retries and feed you with database changes as and when they arrive. The *ChangesReader* library has three modes of operation - start/get/spool:\n\n1. `start()` - to listen to changes indefinitely by repeated \"long poll\" requests. This mode continues to poll for changes forever.\n2. `get()` - to listen to changes until the end of the changes feed is reached, by repeated \"long poll\" requests. Once a response with zero changes is received, the 'end' event will indicate the end of the changes and polling will stop.\n3. `spool()` - listen to changes in one long HTTP request. (as opposed to repeated round trips) - spool is faster but less reliable.\n\n\u003e Note: you may also call `stop()` during start/get modes to prematurely end the polling sequence.\n\nThe `ChangesReader` library hides the myriad of options that the CouchDB changes API offers and exposes only the features you need to build a resilient, resumable change listener.\n\n## Listening to a changes feed indefinitely\n\nInitialise the *ChangesReader* with the name of the database and URL of your CouchDB service (including credentials, if required) - then call its `start` method to monitor the changes feed indefinitely:\n\n```js\nconst ChangesReader = require('changesreader')\nconst cr = new ChangesReader('mydatabase', 'http://admin:admin@localhost:5984')\n```\n\nThe object returned from `start()` emits events when a change occurs:\n\n```js\ncr.start()\n  .on('change', (c) =\u003e {\n    console.log('change', c);\n  }).on('batch', (b) =\u003e {\n    console.log('a batch of', b.length, 'changes has arrived');\n  }).on('seq', (s) =\u003e {\n    console.log('sequence token', s);\n  }).on('error', (e) =\u003e {\n    console.error('error', e);\n  });\n```\n\n\u003e Note: you probably want to monitor *either* the `change` or `batch` event, not both.\n\n## Listening to the changes feed until you have caught up\n\nAlternatively the `get()` method is available to monitor the changes feed until there are no more changes to consume, at which point an `end` event is emitted.\n\n```js\ncr.get()\n  .on('change', (c) =\u003e {\n    console.log('change', c);\n  }).on('batch', (b) =\u003e {\n    console.log('a batch of', b.length, 'changes has arrived');\n  }).on('seq', (s) =\u003e {\n    console.log('sequence token', s);\n  }).on('error', (e) =\u003e {\n    console.error('error', e);\n  }).on('end', (count) =\u003e {\n    console.log('changes feed monitoring has stopped', count);\n  });\n```\n\n## Listening for changes with `wait=true`\n\nBy supplying `wait:true` in the options to `get`/`start`, then your code can rate-limit the rate of changes feed polling. Your code decides when the next request is fired by calling the `on('batch')` callback when ready.\n\n```js\nchangesReader.get({wait: true})\n  .on('change', (c) =\u003e {\n    console.log('change', c);\n  }).on('batch', (b, callback) =\u003e {\n    console.log('a batch of', b.length, 'changes has arrived');\n    // call \"callback\" when you are ready to poll for new changes\n    callback()\n  }).on('seq', (s) =\u003e {\n    console.log('sequence token', s);\n  }).on('error', (e) =\u003e {\n    console.error('error', e);\n  }).on('end', (count) =\u003e {\n    console.log('changes feed monitoring has stopped', count);\n  });\n```\n\nThis allows you to process some asynchronous work safely without building up a back-log of unprocessed data.\n\n## Listening to the changes feed in one HTTP call\n\nAnother option is `spool()` which churns through the changes feed in one go. It only emits `batch` events and an `end` event when it finishes.\n\n```js\nct.spool({ since: '0'})\n  .on('batch', (b) =\u003e {\n    console.log('a batch of', b.length, 'changes has arrived');\n  }).on('end', (count) =\u003e {\n    console.log('changes feed monitoring has stopped', count);\n  });\n```\n\n## Options\n\n| Parameter | Description                                                                                                                                                                             | Default value | e.g.                            |   |\n|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|---------------------------------|---|\n| batchSize | The maximum number of changes to ask CouchDB for per HTTP request. This is the maximum number of changes you will receive in a `batch` event. | 100           | 500                             |   |\n| since     | The position in the changes feed to start from where `0` means the beginning of time, `now` means the current position or a string token indicates a fixed position in the changes feed | now           | 390768-g1AAAAGveJzLYWBgYMlgTmGQ |   |\n| includeDocs | Whether to include document bodies or not | false | e.g. true |\n| wait | Got `get`/`start` mode, only processes requests the next batch of changes when the calling code indicates it's ready with a callback  | false | e.g. true |\n| fastChanges | Adds a seq_interval parameter to fetch changes more quickly | false           | true                             |   |\n| selector | Filters the changes feed with the supplied Mango selector | {\"name\":\"fred}           | null                             |   |\n| timeout | The number of milliseconds a changes feed request waits for data| 60000         | 10000                             |   |\n\nTo consume the changes feed of a large database from the beginning, you may want to increase the `batchSize` e.g. `{ batchSize: 10000, since: 0}`. \n\n## Events\n\nThe objects returned by `changesReader.start()` and `changesReader.get()` emit the following events:\n\n| Event  | Description                                                                                                                                                               | Data                       |   |\n|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|---|\n| change | Each detected change is emitted individually. Only available in `get`/`start` modes.                                                                                                                          | A change object            |   |\n| batch  | Each batch of changes is emitted in bulk in quantities up to `batchSize`.                                                                                                                              | An array of change objects |   |\n| seq    | Each new sequence token (per HTTP request). This token can be passed into `ChangesReader` as the `since` parameter to resume changes feed consumption from a known point. Only available in `get`/`start` modes. | String                     |   |\n| error  | On a fatal error, a descriptive object is returned and change consumption stops.                                                                                         | Error object               |   |\n| end    | Emitted when the end of the changes feed is reached. `ChangesReader.get()` mode only,                                                                                     | Nothing                    |   |\n\nThe *ChangesReader* library will handle many temporal errors such as network connectivity, service capacity limits and malformed data but it will emit an `error` event and exit when fed incorrect authentication credentials or an invalid `since` token.\n\n## What does a change object look like?\n\nThe `change` event delivers a change object that looks like this:\n\n```js\n{\n\t\"seq\": \"8-g1AAAAYIeJyt1M9NwzAUBnALKiFOdAO4gpRix3X\",\n\t\"id\": \"2451be085772a9e588c26fb668e1cc52\",\n\t\"changes\": [{\n\t\t\"rev\": \"4-061b768b6c0b6efe1bad425067986587\"\n\t}],\n\t\"doc\": {\n\t\t\"_id\": \"2451be085772a9e588c26fb668e1cc52\",\n\t\t\"_rev\": \"4-061b768b6c0b6efe1bad425067986587\",\n\t\t\"a\": 3\n\t}\n}\n```\n\nN.B\n\n- `doc` is only present if `includeDocs:true` is supplied\n- `seq` is not present for every change\n\nThe `id` is the unique identifier of the document that changed and the `changes` array contains the document revision tokens that were written to the database.\n\nThe `batch` event delivers an array of change objects.\n\n## Building a resumable changes feed listener\n\nThe `ChangesReader` object gives you the building blocks to construct code that can listen to the changes feed, resuming from where it left off. To do this you will need to\n\n- listen to the `seq` event and store the value it delivers to you. This is the sequence token of the latest change recieved.\n- when starting up the *ChangesReader*, pass your last known `seq` value as the `since` parameter\n\n## TypeScript\n\nThe `ChangesReader` class can be used in TypeScript code too:\n\n```ts\nimport ChangesReader from 'changesreader'\n// or\n// import ChangesReader = require('changesreader')\n\nconst cr = new ChangesReader('mydb', process.env.COUCH_URL)\ncr.start({since:'0'})\n  .on('batch',(data) =\u003e { \n    console.log('batch', data)\n  })  \n```\n\n\u003e Note: if you choose to use the `import ChangesReader from 'changesreader'` form, you will need to supply the `--esModuleInterop` compile-time option to `tsc`. See [tsc compiler options](https://www.typescriptlang.org/docs/handbook/compiler-options.html).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglynnbird%2Fchangesreader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglynnbird%2Fchangesreader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglynnbird%2Fchangesreader/lists"}