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

syncit-server

v0.9.7

Published

A very modular reference server for SyncIt for use with Express

Downloads

28

Readme

syncit-server

Basic Server for SyncIt using MongoDB / Mongoskin (or Memory) and designed to be embedded into Node.JS's Express.

MongoDB Data Model

The data is stored in the following format

{
	"s" : "cars",
	"k" : "bmw",
	"b" : 1,
	"m" : "userId@deviceId",
	"r" : null,
	"u" : "{\"$set\":{\"model\":\"320d"}}",
	"o" : "update",
	"t" : 1399316280922,
	"j" : {
		"i" : {
			"color" : "blue",
			"size" : "medium",
			"model" : "320d"
		},
		"v" : 2,
		"r" : false,
		"t" : 1399316280922,
		"m" : "userId@deviceId"
	},
	"_id" : ObjectId("5367df39f0dfe30104790dbf")
}

This tells us the following information:

  • Dataset: This is the major classification of data, it takes the place of what would be a table in MySQL. In the example the data is "cars".
  • Datakey: A Datakey uniquely identifies a piece of data within a Dataset. The "bmw" data here can only occur once.
  • Based On: 1 - This change is based on version 1 of the cars.bmw Dataset / Datakey.
  • Modifier: The modifier was userId@deviceId - Note this must be unique for every synchronization client, even if they are the same user.
  • Removed: There was no change to the removed status.
  • Update: The update set the "model" to "320d" while preserving all other data.
  • Operation: The operation was an "update". It is possible to do other operations such as "set", for example.
  • Time: The time when the client made the change was the timestamp 1399316280922 (according to the client)
  • There is some de-normalized data in "J" which is so that we can get the data without reading all previous updates to the Dataset / Datakey.
  • Id: This is used partly as a sequencer for the data. It is possible to request / recieve updates on a specified dataset after this.

The code in this repository will also set up a unique non-sparse index on across "s", "k" and "b" to ensure that there is only one update for each data point... The clients, are in charge of merging changes.

Should you wish to shard this data, you should use just "s" as your shard key.

Processes

Request changes within a Dataset - Suggested URL: /syncit/sequence/:s[/:seqId]

When a client wants to request data, it will request it for a known Dataset. It may or may not already have data for that Dataset, it it does it can also pass the optional parameter 'seqId' as part of the URL. The expected response to this a a "200 OK" with the following data format.

{
	"queueitems": [
		{
			"s": "cars",
			"k": "bmw",
			"b": 0,
			"m": "userId@deviceId",
			"r": null,
			"u": {
				"color" : "blue",
				"size" : "medium",
			},
			"o": "set",
			"t": 1399576539003
		}
	],
	"seqId": "536bd7db7a2c620a469b37dd"
}

If the Dataset has not been written to it will still respond with a "200 OK", taking it as a Dataset which has just not been written to yet. It probably should, but does not currently validate the Dataset name.

Keep updated with new Changes - Suggested URL: '/sync/:deviceId?dataset[]=...&dataset[]=...'

Ideally, when the user is connected to the internet and the App is open they should not have to wait for updates. This URL will provide an EventSource (Server Sent Events) connection so they do not have to. The data format for the events is as follows:

{
	"command": "queueitem",
	"queueitem": {
		"s": "wks79OnGbgwa",
		"k": "X7p6au7g00",
		"b": 0,
		"m": "X2pbtb9q00",
		"t": 1399578164699,
		"u": { "completed":false, "editing":false, "title":"j" },
		"o": "set"
	},
	"seqId": "536bd7db7a2c620a469b37dd"
}

This connection will be held open as long as possible, until it is severed by server disconnection or a communication error. As such there is no HTTP status code. Note as datasets are passed as part of the URL you should be wary of making applications that require watching of vast amounts of datasets.

Push changes onto server - Suggested URL: '/syncit/:deviceId'

Being able to get changes, by itself will serve no purpose should changes to be able to be pushed onto the server. This could be done with a HTTP POST request with the following data:

{
	"o": "set",
	"u": {
		"completed": false,
		"editing": false,
		"title": "a"
	},
	"s": "wks79OnGbgwa",
	"k": "X7rmhofr00",
	"t": 1399662274271,
	"b": 0
}

This will respond with one of the following HTTP Status codes:

  • 200 OK - When an Queueitem has been added to a Dataset / Datakey which already existed.
  • 201 Created - Queueitem added to the Dataset / Datakey which did not already exist.
  • 400 Bad Request - The URL or data provided is not valid.
  • 301 See Other - The Modifier (deviceId) has already added data for that Dataset / Datakey / Version (b).
  • 412 Precondition Failed - The "b" field (version) is higher than what is expected.
  • 409 Conflict - There already exists data for the supplied Dataset / Datakey / Version (and that deviceId (m) did not add it.
  • 410 Gone - All given data is valid however the Queueitem already has been removed (the "r" flag).

The data returned within the body of this request will look something like the following:

{
	"sequence": "/syncit/sequence/cars/536d2f9f6fbc9fb87644aa61",
	"change": "/syncit/change/cars/bmw/1"
}

This data gives you the path portions of two URL's. The "change" is the change you just uploaded. The "sequence" is a URL which you can use to get all changes posted after this one, it does not include this change itself.

Notify your App of when SyncIt data changes.

SyncIt Server will fire an event when it has accepted new data. See the referenceServer.listenForFed() method documented below.

Usage

Note this is mostly taken from the SyncItTodoMvc project.

Import Classes

Load the required classes.

var SseCommunication = require('./lib/SseCommunication/Simple'),
	ReferenceServer = require('syncit-server/ReferenceServer'),
	mongoskin = require('mongoskin');

Setup SyncIt Server instances

When creating an instance of ServerPersistMongodb you will need to provide a serialization function, a Mongoskin connection, The MongoDB ObjectId class, The collection to store the data in and a logging function (empty in the example). This ServerPersistMongodb instance will then be passed into the ReferenceServer class, along with a class for doing Server Sent Events communcation and a function used for extracting deviceIds (modifiers).

var sseCommunication = new SseCommunication(),
	mongoskinConnection = mongoskin.db(
		'mongodb://mongodb.yourdomain.com:27017/syncitdb,
		{w:true}
	),
	mongoDbPersistance = new ServerPersistMongodb(
		function(v) { "use strict"; return JSON.parse(JSON.stringify(v)); },
		mongoskinConnection,
		mongoskin.ObjectID,
		appConfig.syncit.data_collection,
		function() {}
	),
	referenceServer = new ReferenceServer(
		function(req) { "use strict"; return req.params.deviceId; }, // The method of how to retrieve the deviceId... Depending on your use case this may need to be more secure than this... deviceIds are public to anyone with access to the Dataset.
		mongoDbPersistance,
		sseCommunication
	);

Common Functions

ReferenceServer does not actually respond with HTTP Status codes, but with strings such as "bad_request" for code readability sake. There are functions here for transforming those strings into real status codes as well the "getQueueitemSequence()" function which is only there to keep the code DRY.

var statusCodesObj = (function(data) {
	"use strict";
	var oData = {};
	for (var i=0, l=data.length; i<l; i++) {
		oData[
			data[i].description.toLowerCase().replace(/[^a-z]/,'_')
		] = data[i].status;
	}
	return oData;
}(require('./res/http_status_codes.js')));

var getStatusCode = function(status) {
	"use strict";
	var aliases = {
		validation_error: 'bad_request'
	};
	if (aliases.hasOwnProperty(status)) {
		status = aliases[status];
	}
	if (!statusCodesObj.hasOwnProperty(status)) {
		throw "Could not find status code for status '" + status + "'";
	}
	return statusCodesObj[status];
};

var getQueueitemSequence = function(req, res, next) {
	"use strict";
	referenceServer.getQueueitems(
		req,
		function(err, status, data) {
			if (err) { return next(err); }
			res.json(getStatusCode(status), data);
		}
	);
};

Set up the URL's

app.get('/syncit/sequence/:s/:seqId', getQueueitemSequence);
app.get('/syncit/sequence/:s', getQueueitemSequence);

app.get('/sync/:deviceId', referenceServer.sync.bind(referenceServer));

app.get('/syncit/change/:s/:k/:v', function(req, res, next) {
	"use strict";
	referenceServer.getDatasetDatakeyVersion(
		req,
		function(err, status, data) {
			if (err) { return next(err); }
			res.json(getStatusCode(status), data);
		}
	);
});

app.post('/syncit/:deviceId', function(req, res, next) {
	"use strict";
	referenceServer.push(req, function(err, status, data) {
		if (err) { return next(err); }
		res.json(getStatusCode(status), data);
	});
});

Integration with the rest of your system.

ReferenceServer also will expose an event which allows you to get notification of when data has been added. It is used like this:

referenceServer.listenForFed(
	function(seqId, dataset, datakey, processedQueueitem, processedJrec) {
		console.log(
			"A change has been added to " + dataset + "." + datakey + " which is " +
			"sequenceId " + sequenceId + ".\n\n" +
			"The update was " + JSON.stringify(processedQueueitem) + " and it " +
			"resulted in the the following data being stored:\n\n" +
			JSON.stringify(processedJrec)
		);
	}
);