{"id":13495678,"url":"https://github.com/cartant/rxjs-spy","last_synced_at":"2025-05-15T18:00:20.353Z","repository":{"id":41308847,"uuid":"91206422","full_name":"cartant/rxjs-spy","owner":"cartant","description":"A debugging library for RxJS","archived":false,"fork":false,"pushed_at":"2023-04-18T12:15:01.000Z","size":3573,"stargazers_count":704,"open_issues_count":14,"forks_count":22,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-04-07T22:11:12.889Z","etag":null,"topics":["debugging","logging","rxjs"],"latest_commit_sha":null,"homepage":"https://cartant.github.io/rxjs-spy/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cartant.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":["cartant"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2017-05-13T22:30:40.000Z","updated_at":"2025-03-27T06:35:23.000Z","dependencies_parsed_at":"2024-01-16T09:53:53.086Z","dependency_job_id":"331bcf7e-d793-40e5-9726-5a35e9bf619e","html_url":"https://github.com/cartant/rxjs-spy","commit_stats":{"total_commits":807,"total_committers":5,"mean_commits":161.4,"dds":"0.012391573729863659","last_synced_commit":"f5f459170a977701d0cecb52ac2679853d6050df"},"previous_names":[],"tags_count":51,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartant%2Frxjs-spy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartant%2Frxjs-spy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartant%2Frxjs-spy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartant%2Frxjs-spy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cartant","download_url":"https://codeload.github.com/cartant/rxjs-spy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254394718,"owners_count":22063984,"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":["debugging","logging","rxjs"],"created_at":"2024-07-31T19:01:37.044Z","updated_at":"2025-05-15T18:00:20.124Z","avatar_url":"https://github.com/cartant.png","language":"TypeScript","funding_links":["https://github.com/sponsors/cartant"],"categories":["TypeScript","Debugging"],"sub_categories":["Angular-Specific Utilities"],"readme":"# rxjs-spy\n\n[![GitHub License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/cartant/rxjs-spy/blob/master/LICENSE)\n[![NPM version](https://img.shields.io/npm/v/rxjs-spy.svg)](https://www.npmjs.com/package/rxjs-spy)\n[![Downloads](http://img.shields.io/npm/dm/rxjs-spy.svg)](https://npmjs.org/package/rxjs-spy)\n[![Build status](https://img.shields.io/travis/cartant/rxjs-spy.svg)](http://travis-ci.org/cartant/rxjs-spy)\n[![dependency status](https://img.shields.io/david/cartant/rxjs-spy.svg)](https://david-dm.org/cartant/rxjs-spy)\n[![devDependency Status](https://img.shields.io/david/dev/cartant/rxjs-spy.svg)](https://david-dm.org/cartant/rxjs-spy#info=devDependencies)\n[![peerDependency Status](https://img.shields.io/david/peer/cartant/rxjs-spy.svg)](https://david-dm.org/cartant/rxjs-spy#info=peerDependencies)\n[![Greenkeeper badge](https://badges.greenkeeper.io/cartant/rxjs-spy.svg)](https://greenkeeper.io/)\n\n### What is it?\n\n`rxjs-spy` is a debugging library for RxJS.\n\n### Why might you need it?\n\nThe usual approach to debugging RxJS-based code involves sprinkling `do` operators and logging throughout composed observables. That's something that I find tedious, so I wrote this library and implemented an unobtrusive mechanism for identifying observables and logging and inspecting observable subscriptions.\n\nIf you, too, are looking for a less painful RxJS debugging experience, you might find this library useful. The engineers at Slack have adopted `rxjs-spy` and have [this to say](https://slack.engineering/growing-pains-migrating-slacks-desktop-app-to-browserview-2759690d9c7b) about it:\n\n\u003e You might be like, \"[...] but aren't Observables impossible to debug?\" And you'd have been mostly right less than a year ago. But this is JavaScript and in JavaScript, the only `const` is change. `rxjs-spy` makes debugging (i.e. logging and visualizing) streams as simple as adding a `tag`. A tagged stream can be monitored, paused, and replayed, right from the console.\n\nFor more detail regarding how the library works and what it can do, you can have a look at:\n\n* [Debugging RxJS, Part 1: Tooling](https://ncjamieson.com/debugging-rxjs-part-1-tooling/).\n* [Debugging RxJS, Part 2: Logging](https://ncjamieson.com/debugging-rxjs-part-2-logging/).\n* There is an online example in this repo's [GitHub pages](https://cartant.github.io/rxjs-spy/).\n\n## Install\n\nInstall the package using NPM:\n\n```\nnpm install rxjs-spy --save-dev\n```\n\nAnd import the functions for use with TypeScript or ES2015:\n\n```js\nimport { create } from \"rxjs-spy\";\nconst spy = create();\n```\n\nOr `require` the module for use with Node or a CommonJS bundler:\n\n```js\nconst { create } = require(\"rxjs-spy\");\nconst spy = create();\n```\n\nOr include the UMD bundle for use as a `script`:\n\n```html\n\u003cscript src=\"https://unpkg.com/rxjs@6/bundles/rxjs.umd.min.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://unpkg.com/rxjs-spy@7/bundles/rxjs-spy.min.umd.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\nconst { create } = rxjsSpy;\ncreate();\n\u003c/script\u003e\n```\n\n## Core concepts\n\n`rxjs-spy` introduces a `tag` operator that can be used to identify observables. It attaches a string tag to an observable; it performs no additional processing and does not alter the observable's behaviour or value in any way.\n\nThe `tag` operator can be used with `pipe`:\n\n```js\nimport { tag } from \"rxjs-spy/operators/tag\";\nconst source = Observable.of(\"some-value\").pipe(tag(\"some-tag\"));\n```\n\nThe API's methods are tag-based and tags can be matched using explicit literals, regular expressions or function predicates. For example, logging for the above tag could be enabled like this:\n\n```js\nimport { create } from \"rxjs-spy\";\nconst spy = create();\nspy.log(\"some-tag\");\n\n// Or like this:\nspy.log(/^some-tag$/);\n\n// Or like this:\nspy.log(tag =\u003e tag === \"some-tag\");\n```\n\n`rxjs-spy` exposes a module API intended to be called from code and a console API - via the `spy` global - intended for interactive use via the browser's console.\n\n## Module API\n\nThe methods in the module API are callable via imports, requires or the UMD `rxjsSpy` global. Most methods return a teardown function that will undo the API method's action when called.\n\n* [`create`](#module-create)\n* [`Spy.show`](#module-show)\n* [`Spy.log`](#module-log)\n* [`Spy.pause`](#module-pause)\n* [`Spy.let`](#module-let)\n* [`Spy.debug`](#module-debug)\n* [`Spy.flush`](#module-flush)\n* [`Spy.plug`](#module-plug)\n* [`Spy.unplug`](#module-unplug)\n* [`Spy.find`](#module-find)\n* [`Spy.findAll`](#module-findAll)\n* [`Spy.stats`](#module-stats)\n* [`Spy.teardown`](#module-teardown)\n* [`detect`](#module-detect)\n\n\u003ca name=\"module-create\"\u003e\u003c/a\u003e\n\n### create\n\n```ts\nfunction create(options: {\n    [key: string]: any,\n    audit?: number,\n    defaultLogger?: PartialLogger,\n    defaultPlugins?: boolean,\n    warning?: boolean\n} = {}): Teardown\n```\n\nCalling `create` attaches the spy to `Observable.prototype.subscribe` and returns the following interface:\n\n```ts\ninterface Spy {\n  readonly tick: number;\n  debug(match: Match, ...notifications: Notification[]): Teardown;\n  find\u003cT extends Plugin\u003e(ctor: Ctor\u003cT\u003e): T | undefined;\n  findAll\u003cT extends Plugin\u003e(ctor: Ctor\u003cT\u003e): T[];\n  findAll(): Plugin[];\n  flush(): void;\n  let(match: Match, select: (source: Observable\u003cany\u003e) =\u003e Observable\u003cany\u003e, options?: Options): Teardown;\n  log(match: Match, partialLogger?: PartialLogger): Teardown;\n  log(partialLogger?: PartialLogger): Teardown;\n  pause(match: Match): Deck;\n  plug(...plugins: Plugin[]): Teardown;\n  show(match: Match, partialLogger?: PartialLogger): void;\n  show(partialLogger?: PartialLogger): void;\n  stats(partialLogger?: PartialLogger): void;\n  teardown(): void;\n  unplug(...plugins: Plugin[]): void;\n}\n\n```\n\nBy default, `create` will wire up the snapshotting plugin and numerous others. However, if the `defaultPlugins` option is specified as `false`, no plugins will be wired up and the caller can wire up plugins individually using the `plug` method. For example:\n\n```ts\nimport { create } from \"rxjs-spy\";\nimport { GraphPlugin, SnapshotPlugin } from \"rxjs-spy/plugin\";\nconst spy = create({ defaultPlugins: false });\nspy.plug(\n  new GraphPlugin({ keptDuration: -1 }),\n  new SnapshotPlugin(spy, { keptValues: 1 })\n);\n```\n\nIf the `audit` option is specified, the logging of notifications will be audited within the specified (milliseconds) duration. Each notification source will only be logged once in each duration and the number of ignored notifications (if any) will be included in the console output. This can be useful for logging high-frequency observables. `audit` defaults to zero - i.e. no auditing.\n\nOptions passed to `create` are forwarded to the plugins, so the following can be specified:\n\n| Option | Type | Description | Default |\n| --- | --- | --- | --- |\n| `keptDuration` | `number` | The number of milliseconds for which the subscription graph and snapshots should be kept after unsubscription occurs. | 30000 |\n| `keptValues` | `number` | The maximum number of values that should be kept in a snapshot. | 4 |\n| `sourceMaps` | `boolean` | Whether or not the `StackTracePlugin` should use source maps. | `false` |\n\nThis method returns a teardown function.\n\n\u003ca name=\"module-show\"\u003e\u003c/a\u003e\n\n### show\n\n```ts\ninterface Spy {\n  show(\n    match: string | RegExp | MatchPredicate | Observable\u003cany\u003e,\n    partialLogger: PartialLogger = console\n  ): void;\n  show(\n    partialLogger: PartialLogger = console\n  ): void;\n}\n```\n\n`show` will log information regarding the matching observables to the console or to the specified logger. If no `match` is specified, all tagged observables will be logged.\n\nThe logged information is retrieved from the most recent snapshot, so if snapshotting is not enabled, an error will be thrown.\n\n\u003ca name=\"module-log\"\u003e\u003c/a\u003e\n\n### log\n\n```ts\ninterface Spy {\n  log(\n    match: string | RegExp | MatchPredicate | Observable\u003cany\u003e,\n    partialLogger: PartialLogger = console\n  ): Teardown;\n  log(\n    partialLogger: PartialLogger = console\n  ): Teardown;\n}\n```\n\nWires up an instance of the log plugin for matching observables. If no `match` is specified, all tagged observables will be logged.\n\nAll `subscribe`, `next`, `complete`, `error` and `unsubscribe` notifications will be logged to the console or to the specified logger.\n\nThis method returns a teardown function.\n\n\u003ca name=\"module-pause\"\u003e\u003c/a\u003e\n\n### pause\n\n```ts\ninterface Spy {\n  pause(\n    match: string | RegExp | MatchPredicate | Observable\u003cany\u003e\n  ): Deck;\n}\n```\n\nWires up an instance of the pause plugin for matching observables.\n\nAll subscriptions to matching observables will be placed into a paused state and notifications that would otherwise be emitted will be buffered inside the plugin.\n\nThis method returns a `Deck` instance that can be used to `resume` and `pause` the observables.\n\n```ts\ninterface Deck {\n  readonly paused: boolean;\n  clear(): void;\n  log(partialLogger: PartialLogger = console): void;\n  pause(): void;\n  resume(): void;\n  skip(): void;\n  step(): void;\n  teardown(): void;\n}\n```\n\nCalling `step` will release a single paused notification. The other methods to what their names suggest. Calling `resume` will play all buffered notifications before resuming.\n\n\u003ca name=\"module-let\"\u003e\u003c/a\u003e\n\n### let\n\n```ts\ninterface Spy {\n  let(\n    match: string | RegExp | MatchPredicate | Observable\u003cany\u003e,\n    select: (source: Observable\u003cany\u003e) =\u003e Observable\u003cany\u003e,\n    options?: Options\n  ): Teardown;\n}\n```\n\nWires up an instance of the let plugin for matching observables.\n\nThis is equivalent to the `let` operator. All subscriptions to matching observables will instead be made to the observable returned by the specified `select` function.\n\nIf `complete` option is `false`, completion notifications received from the selected observable will not be forwarded to subscribers.\n\nThis method returns a teardown function.\n\n\u003ca name=\"module-debug\"\u003e\u003c/a\u003e\n\n### debug\n\n```ts\ninterface Spy {\n  debug(\n    match: string | RegExp | MatchPredicate | Observable\u003cany\u003e,\n    ...notifications: (\"complete\" | \"error\" | \"next\" | \"subscribe\" | \"unsubscribe\")[]\n  ): Teardown;\n}\n```\n\nWires up an instance of the debug plugin for matching observables.\n\nWhenever one of the specified notifications occurs, a `debugger` statement in the plugin will pause execution. If no notifications are specified in the call, execution will be paused when any of the notifications occurs.\n\nThis method returns a teardown function.\n\n\u003ca name=\"module-flush\"\u003e\u003c/a\u003e\n\n### flush\n\n```ts\ninterface Spy {\n  flush(): void;\n}\n```\n\nCalling `flush` will see `flush` called on each plugin.\n\nIf snapshotting is enabled, calling `flush` will release excess values and completed or errored obervables from within snapshots.\n\n\u003ca name=\"module-plug\"\u003e\u003c/a\u003e\n\n### plug\n\n```ts\ninterface Spy {\n  plug(...plugins: Plugin[]): Teardown;\n}\n```\n\nWires up the specified plugin(s) and returns a teardown function.\n\n\u003ca name=\"module-unplug\"\u003e\u003c/a\u003e\n\n### unplug\n\n```ts\ninterface Spy {\n  unplug(...plugins: Plugin[]): void;\n}\n```\n\nRemoves the specified plugin(s).\n\n\u003ca name=\"module-find\"\u003e\u003c/a\u003e\n\n### find\n\n```ts\ninterface Spy {\n  find\u003cT extends Plugin\u003e(constructor: { new (...args: any[]): T }): T | undefined;\n}\n```\n\nReturns the first plugin matching the specified constructor/class.\n\n\u003ca name=\"module-findAll\"\u003e\u003c/a\u003e\n\n### findAll\n\n```ts\ninterface Spy {\n  findAll\u003cT extends Plugin\u003e(constructor: { new (...args: any[]): T }): T[];\n  findAll(): T[];\n}\n```\n\nReturns all plugins matching the specified constructor/class. Or all plugins of no constructor is specified.\n\n\u003ca name=\"module-stats\"\u003e\u003c/a\u003e\n\n### stats\n\n```ts\ninterface Spy {\n  stats(partialLogger: PartialLogger = console): void;\n}\n```\n\nWrites, to the console, counts of the number of notifications, etc.\n\n\u003ca name=\"module-teardown\"\u003e\u003c/a\u003e\n\n### teardown\n\n```ts\ninterface Spy {\n  teardown(): void;\n}\n```\n\nTears down the spy.\n\n\u003ca name=\"module-detect\"\u003e\u003c/a\u003e\n\n### detect\n\n```ts\nfunction detect(id: string): void;\n```\n\nWrites, to the console, any subscriptions and unsubscriptions that have occurred since the previous `detect` call with the specified `id`.\n\nThe `detect` method is implemented so that it can be imported and called regardless of whether or not the spy is configured. That is, calls can be left in production code, as they become no-ops. It should be imported like this:\n\n```ts\nimport { detect } from \"rxjs-spy/detect\";\n```\n\n## Console API\n\nThe methods in the console API are callable via the `spy` global and are intended to be used interactively in the browser's console.\n\nThey are identical to the methods in the spy instances created using the module API except for the fact that they do not return teardown functions. Instead, calls can be undone using the `undo` API method.\n\n* [`spy.undo`](#console-undo)\n* [`spy.deck`](#console-deck)\n\n\u003ca name=\"console-undo\"\u003e\u003c/a\u003e\n\n### undo\n\n```ts\nfunction undo(...calls: number[]): void\n```\n\nWhen called without arguments, the `undo` method will display in the console a list of the `rxjs-spy` calls that can be undone.\n\nCalls are listed against a call number and one or more of those numbers can be passed to `undo` to undo specific calls.\n\nUndoing a `spy` call will undo all calls.\n\n\u003ca name=\"console-deck\"\u003e\u003c/a\u003e\n\n### deck\n\n```ts\nfunction deck(call?: number): Deck | undefined\n```\n\nIn the console, it's easy to forget to use a variable to capture the `Deck` returned by a call to `pause`. In those situations, you can call the `deck` method without an argument to see a list of numbered `pause` calls. Calling `deck` again, passing a call number, will return the `Deck` associated with the specified `pause` call.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcartant%2Frxjs-spy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcartant%2Frxjs-spy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcartant%2Frxjs-spy/lists"}