npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

feathers-reactive

v0.11.0

Published

Reactive API extensions for Feathers services

Downloads

6,783

Readme

feathers-reactive

CI Download Status

Reactive API extensions for Feathers

About

feathers-reactive adds a watch() method to services. The returned object implements all service methods as RxJS v7 observables that automatically update on real-time events.

Options

The following options are supported:

  • idField (mandatory): The id property field of your services. Depends on your service/database. Usually 'id' (SQL, Rethinkdb, …) or '_id' (MongoDB, NeDB, … ).
  • dataField (default: data): The data property field in paginated responses
  • listStrategy (default: smart): The strategy to use for streaming the data. Can be smart, always or never. Avoid using always whenever possible.
  • sorter (function(query, options) {}): A function that returns a sorting function for the given query and option including pagination and limiting. Does not need to be customized unless there is a sorting mechanism other than Feathers standard in place.
  • matcher (function(query)): A function that returns a function which returns whether an item matches the original query or not.
  • pipe (operator | operator[]) One or multiple rxjs operators of the form function(observable) => observable like you would pass them to an Observable's .pipe method. The supplied operators are applied to any Observable created by feathers-reactive. options.pipe: tap(data => console.log(data)) would log every emitted value to the console.

Application level

import feathers from '@feathersjs/feathers';
import { rx } from 'feathers-reactive';

const app = feathers().configure(rx(options));

Service level

With feathers-reactive configured on the application individual options can be set at the service level with service.rx:

// Set a different id field
app.service('todos').rx({
  idField: '_id'
});

Method call level

Each method call can also pass its own options via params.rx:

// Never update data for this method call
app.service('todos').watch({ listStrategy: 'never' }).find();

List strategies

List strategies are used to determine how a data stream behaves. Currently there are three strategies:

  • never - Returns a stream from the service promise that only emits the method call data and never updates after that
  • smart (default) - Returns a stream that smartly emits updated list data based on the services real-time events. It does not re-query any new data (but does not cover some cases in which the always strategy can be used). When using smart list strategy, an additional method reset is available to get fresh data from the server.
  • always - Re-runs the original query to always get fresh data from the server on any matching real-time event. Avoid this list strategy if possible since it will put a higher load on the server than necessary.

Usage

import {feathers} from '@feathersjs/feathers';
import memory from 'feathers-memory';
import { rx } from 'feathers-reactive';

const app = feathers()
  .configure(rx({
    idField: 'id'
  }))
  .use('/messages', memory());

const messages = app.service('messages');

messages.create({
  text: 'A test message'
}).then(() => {
  // Get a specific message with id 0. Emit the message data once it resolves
  // and every time it changes e.g. through an updated or patched event
  messages.watch().get(0).subscribe(message => console.log('My message', message));

  // Find all messages and emit a new list every time anything changes
  messages.watch().find().subscribe(messages => console.log('Message list', messages));

  setTimeout(() => {
    messages.create({ text: 'Another message' }).then(() =>
      setTimeout(() => messages.patch(0, { text: 'Updated message' }), 1000)
    );
  }, 1000);
});

Will output:

My message { text: 'A test message', id: 0 }
Message list [ { text: 'A test message', id: 0 } ]
Message list [ { text: 'A test message', id: 0 },
  { text: 'Another message', id: 1 } ]
My message { text: 'Updated message', id: 0 }
Message list [ { text: 'Updated message', id: 0 },
  { text: 'Another message', id: 1 } ]

Frameworks

Let's assume a simple Feathers Socket.io server in app.js like this:

npm install @feathersjs/feathers @feathersjs/socketio feathers-memory

import {feathers} from '@feathersjs/feathers';
import socketio from '@feathersjs/socketio';
import memory from 'feathers-memory';

const app = feathers()
  .configure(socketio())
  .use('/todos', memory());

app.on('connection', connection => app.channel('everybody').join(connection));
app.publish(() => app.channel('everybody'));

app.listen(3030).on('listening', () =>
  console.log('Feathers Socket.io server running on localhost:3030')
);

Usage

For an ES5 compatible version on the client (e.g. when using create-react-app) you can import feathers-reactive/dist/feathers-reactive. In client.js:

import io from 'socket.io-client';
import feathers from '@feathersjs/client';
import rx from 'feathers-reactive/dist/feathers-reactive';

const socket = io('http://localhost:3030');
const app = feathers()
  .configure(feathers.socketio(socket))
  .configure(rx({
    idField: 'id'
  }));

export default app;

React

A real-time ReactJS Todo application (with Bootstrap styles) can look like this (see the examples/react-todos folder for a working example);

import React, { Component } from 'react';
import client from './client';

class App extends Component {
  constructor (props) {
    super(props);
    this.state = {
      todos: [],
      text: ''
    };
  }

  componentDidMount () {
    this.todos = client.service('todos').watch()
      .find().subscribe(todos => this.setState(todos));
  }

  componentWillUnmount () {
    this.todos.unsubscribe();
  }

  updateText (ev) {
    this.setState({ text: ev.target.value });
  }

  createTodo (ev) {
    client.service('todos').create({
      text: this.state.text,
      complete: false
    });
    this.setState({ text: '' });
    ev.preventDefault();
  }

  updateTodo (todo, ev) {
    todo.complete = ev.target.checked;
    client.service('todos').patch(todo.id, todo);
  }

  deleteTodo (todo) {
    client.service('todos').remove(todo.id);
  }

  render () {
    const renderTodo = todo =>
      <li key={todo.id} className={`page-header checkbox ${todo.complete ? 'done' : ''}`}>
        <label>
          <input type='checkbox' onChange={this.updateTodo.bind(this, todo)}
            checked={todo.complete} />
          {todo.text}
        </label>
        <a href='javascript://' className='pull-right delete'
          onClick={this.deleteTodo.bind(this, todo)}>
          <span className='glyphicon glyphicon-remove' />
        </a>
      </li>;

    return <div className='container' id='todos'>
      <h1>Feathers real-time Todos</h1>

      <ul className='todos list-unstyled'>{this.state.todos.map(renderTodo)}</ul>
      <form role='form' className='create-todo' onSubmit={this.createTodo.bind(this)}>
        <div className='form-group'>
          <input type='text' className='form-control' name='description'
            placeholder='Add a new Todo' onChange={this.updateText.bind(this)}
            value={this.state.text} />
        </div>
        <button type='submit' className='btn btn-info col-md-12'>
          Add Todo
        </button>
      </form>
    </div>;
  }
}

export default App;

License

Copyright (c) 2023

Licensed under the MIT license.