New js DDP client (SimpleDDP)

Motivation.
I am working in a startup. At first I was the only dev in my company and the best choice in speed and quality was Meteor. We also needed few mobile apps and we choosed Ionic. Working with naked DDP was a hell of the thing especially to non-experienced Meteor developer like me. Projects like Asteroid didn’t provide the proper functionality and documentation for DDP outside the Meteor world and not being maintained for few years. Our team started to grow and frontend developers not familiar with Meteor faced certain issues like learning new framework and build tools (Meteor), connecting their frontend framework with Meteor, pushing updates to UI in production without building the whole project in pipelines etc (and we started to think about client-less Meteor). So I decided to make a simple DDP library that would be something like a starter pack ‘everything you need to start working on a Meteor client outside the Meteor and be productive no matter what js frameworks or libs you use’. That’s how simpleDDP was born.

I built simpleDDP on top of Mondora’s ddp.js lib which I used in first versions of the apps.

SimpleDDP has

  • Full documentation
  • Examples for Node.js and Ionic 3 (looking forward to any PR to add more examples)
  • Plugin system (for now only one plugin for authorization, PR are welcome)
  • Lots of promises, so you can use async/await with ease.
  • Collections storage (actual state of all data you have got from your subscriptions)
  • Built-in change listener for collections and objects inside the collections

SimpleDDP is battle tested in production, is maintained because our team uses it and it will continue to improve. We are going to add more examples and frontend recipes sharing our experience of working with Meteor.

Take a look! https://github.com/Gregivy/simpleddp

39 Likes

This is awesome! Besides the actual state, do you use any clientside storage mechanism to query stuff like minimongo?

Thanks, cloudspider!

I am using techniques that are included in simpleDDP .
For example you can fetch filtered objects from your collection and get plain js array like so

let myItems = server.collection('somecoll').filter(el=>el.cat=='supercat').fetch();

If you want some storage variable to be reactive you can use onChange

let myStorage;
let a = server.collection('somecoll').filter(el=>el.cat=='supercat');
myStorage = a.fetch();
a.onChange(()=>{
  myStorage = a.fetch();
})

There are a lot of things you can do with simpleDDP, checkout readme, examples and docs :slight_smile:

P.S. I have updated simpleDDP to v1.1.8 so there will be no error if you use techniques I described above and there is no data inside the collections (or no collection at all).

1 Like

simpleDDP 1.1.9 is out

Several bugs were fixed, new test added and most important changes are:

  • Shallow copying was replaced with deep cloning js objects
  • ddpFilter.onChange() triggers even when a next state of an object successfully passes the filter. If prev and next are both not false you can check which passes the filter with new argument predicatePassed . It is an array of two booleans , predicatePassed[0] is for prev and predicatePassed[1] is for next.

@cloudspider I was also inspired by your question so I decided to implement reactive collection fetching and introduce few more important things! It will be in 1.2.0 version which comes out soon.

4 Likes

This sounds awesome. I’m going to check this out. Nice work man! I might want to build a Vue plugin for this

1 Like

Awsome! As someone maintaining an Electron app that needs to interact with Meteor, I might switch to this solution because we had problems with every other implementation of this.

2 Likes

@cloudspider @francisbou The would be cool, the more environments we use the better! I will write here when 1.2.0 come out (a few days, I am on finish line) and would appreciate any help in bug detecting, usage examples etc.

Crucial bug fix with subscriptions in simpleDDP 1.1.10

After testing out mobile app we have found two bugs in simpleDDP 1.1.9. First one - subscription readiness (both promise and callback) was always true and other bug was with mutating EventEmitter’s message in the first listener, so other listeners on the same event received modified message.

P.S. @cloudspider thank for PR :slight_smile:

And I am continuing to test version 1.2.0 which will come out soon.

3 Likes

@gregivy I’ve finished the initial steps for the Vue integration package. Its now possible to connect a Vue or Nuxt project easily to Meteor’s DDP system by simply specify a plugin. The plugin options are passed down to the SimpleDDP package and it uses isomorphic-ws by default to simplify the setup :slight_smile:

I have plans to implement a Vuex version aswell. This should automatically do the subscription handling, support SSR and makes uses of simple getter functions along the lines of:

{
  created() {
    this.$store.dispatch('articles/subscribe', {
      // parameters
    });
  },

  computed: {
    articles() {
      return this.$store.getters['articles/recent']();
    }
  }
}
6 Likes

@cloudspider Nice work, I’m glad you have started it! It would be super cool if we have such easy solution for vue!
I saw in source that you you such construction:

Vue.prototype.$subscribe = async (publicationName, ...args) => {
      return api.sub(publicationName, ...args).ready();
};

But it won’t work as you expected, because simpleDDP.sub take a subscription name as a first argument and an array of parameters (optional) for the subscription as a second argument.

And in your example you use:

await this.$subscribe('articles').catch(console.error);
this.articles = await this.find('articles');

The last statement uses:

Vue.prototype.find = function(collectionName) {
      const collection = api.collection(collectionName);

      return collection.fetch();
};

However collection.fetch will return you not a promise but a raw js array of what you have in your collection or [ ] (if there is none). So you can’t await it.

As for the example code:

await this.$subscribe('articles').catch(console.error);

This line will guarantee you that you have something inside the collection, and when you fetch it using

this.find('articles');

You will receive a copy of what you have in your collection at the moment of fetching, it is not reactive.

SimpleDDP 1.2.0 is coming out and it will have zero-dependant reactive data source both for the collections, filtered collections and objects, built-in smart sorting, improved subscriptions. So I suggest to use reactive source inside your find function when it came out. I am working on guide right now!

P.S. I will make a PR to your repo.

The subscription method uses the spread operator. This means that I can add any kind of second parameter to the api.sub method including an array.

About the find function. Its indeed a non-reactive function. I’m doing this to make it easy to include it as SSR feature. It still needs to be connected to Vue’s reactivity system, but I want to wait with it, because of your next release.

The reactive part will also become a Vuex feature since that store uses convinient methods to pull this off easily.

That said. Not sure if this is the right approach. I’m always open for feedback :slightly_smiling_face:

About the fetch part. Thanks for that. The example code for find should not have had the await part. I will remove this :joy:

I made a PR :slight_smile: You can check it out.

I see your point, I am not familiar with Vue.js, but I am going to make reactivity by mutating the returned object. So it will be a plain js array or object anyway, just like now. Is it ok for SSR?

@gregivy Congratulations and thank you very much for this amazing work! I am developing a chrome extension connected to a Meteor back-end and was upset with the DDP clients currently available.

One question I have open is: Since it is not possible to require modules on chrome extensions without a huge workaround, is there a way I can use your DDP directly on my project? (I mean, instead of installing through NPM and then requiring)

Thanks!

It sounds like you need to build it from npm and put it on a cdn?

Unfortunately CDN’s are not accepted at all by chrome extensions anymore…

Thank you, @patrickcneuhaus! You can use webpack or a much simpler solution like browserfy.

Just install it and install simpleDDP

npm install -g browserify
npm install simpleddp

Then include simpleDDP in one of your js files like below and use it as you wish

var simpleDDP = require("simpleddp").default;

And run

browserify your_js_file.js -o bundle.js

The bundle.js is the file you want to use in you extensions (it will contain both your code and simpleDDP)!

If you will have any problems with this approach, let me know, I have not tried this approach with google chrome extensions myself.

2 Likes

@gregivy Thanks for the instructions!

I followed them, but it seems like Chrome Extensions are really not capable of handling “require” without further complications.

I get this error on chrome://extensions right after loading the folder:

Uncaught ReferenceError: require is not defined

Any ideas how to solve that?

@patrickcneuhaus I have no errors, I used this guide https://medium.freecodecamp.org/how-to-create-and-publish-a-chrome-extension-in-20-minutes-6dc8395d7153 and included simpleDDP.

Check out my repo.

Inside index.js I have my code related to simpleDDP. Then I run inside the project folder

browserify index.js -o ./extension/bundle.js

And inside extension/newtab.html I connect my bundle with <script> tag.

<!doctype html>
<html>
  <head>
    <script src="bundle.js"></script>
    <title>Test</title>
  </head>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>

Now folder extension is ready to be used as a google chrome extension!

2 Likes

It worked! Thank you very much for this! :slight_smile:

What do I need to do to call methods through simpleDDP on my chrome extension’s popup?
Should I import { simpleDDP } from bundle.js on my popup.js file?

You have few options:

  • Polute window scope inside index.js compile it to bundle.js and use it inside popup.js:
//index.js
window.simpleDDP = require("simpleddp").default;
<script src="bundle.js"></script>
<script src="popup.js"></script>
// popup.js
// now simpleDDP is global variable on window object

var server = new simpleDDP(/*parameters*/);
  • Use only bundle.js, put you popup.js code inside index.js, compile it to bundle.js and that is it.
1 Like