This lets you write SQL in your frontend code, that automagically refreshes on all clients when a resource is changed on any (optionally) connected DB. Additionally, create any generic JS variables on your server to be realtime synced across all clients using "Server Props".
Agnostic of framework, build tool, server lib and SQL database. Requires Node.js >= 16 LTS.
In your project root dir:
npm i socio
Contains compiled JS files + TS type definition files.
Socio is a "middle man" framework between your DB and browser clients. The SocioServer
creates a WebSocket server on your backend, that can optionally be hooked up to any DB. The SocioClient
sits on the browser (or backend with Deno) and communicates with your server through socios protocols and mechanisms. E.g. SocioClient.Query()
or .Subscribe()
with SQL strings and/or .SetProp()
and .SubscribeProp()
for generic data. Additionally, the server can also at any time push any data to any client(s), creating duplex real-time connections. Pretty much everything you'd need, including file transfer, is supported.
When using SQL, client-side JS source files contain only encrypted strings of your SQL. The used AES-256-GCM algorithm guarantees Confidentiality (cannot be read), Integrity (cannot be altered) and Authenticity (server can verify the author of the created cypher text). Dynamic data inserted as query parameters should be server-side sanitized by you as usual. In addition, all queries can use opt-in markers for authentification and table permissions requirements, that are managed by Socio Server for you.
The encryption preproc step is done with the SocioSecurity
class manually or automagically with the included Vite plugin SocioSecurityVitePlugin
.
//TS server side. For SvelteKit, this can be in proj_root/src/hooks.server.ts . Check the Framework Demo for an example.
import { SocioServer } from 'socio/dist/core-server'; //Might need to put .js at the end.
import { SocioSecurity } from 'socio/dist/secure';
async function QueryWrap(client: SocioSession, id: id, sql: string, params: any):Promise<object> {
//do whatever u need to run the sql on your DB and return its result. E.g. sequelize.query()
//Or any other way you want to retrieve data, like reading a local txt etc.
//sanatize dynamic params!
}
const socsec = new SocioSecurity({ secure_private_key: '...', logging:{verbose:true} }); //for decrypting incoming queries. This same key is used for encrypting the source files when you build and bundle them. Has to be the same in the Vite plugin.
const socserv = new SocioServer({ port: 3000 }, { db:{Query:QueryWrap}, socio_security: socsec, logging:{verbose:true} }); //creates localhost:3000 web socket server
//client side browser code.
import { SocioClient } from 'socio/dist/core-client'; //Might need to put .js at the end.
import { socio } from 'socio/dist/utils';
const sc = new SocioClient('ws://localhost:3000', {logging:{verbose:true}, name:'Main'}); //create as many as you like
await sc.ready(); //wait to establish the connection
//will recall the callback whenever the Users table is altered. Can also unsubscribe.
const sub_id = sc.Subscribe({sql:socio`SELECT * FROM Users;`}, (res:object) => {...});
//send a single query and wait for its result:
await sc.Query(socio`INSERT INTO Users (name, num) VALUES(:name, :num);`, {name:'bob', num:42}); //sanatize dynamic data yourself in QueryWrap on the server!
//work with general server side data - "props":
const my_obj = await sc.Prop('my_obj') as {num:0}; //in this case the prop must be a js object registered on the server
if(my_obj?.num === 0) my_obj.num += 1; // use it like a regular js obj, but its value is always synced across clients and server (magic!):
my_obj.num--; my_obj['num'] = 0; //etc.
// or have manual control over any js datatype as a prop:
let color = await sc.GetProp('color') as string; //the prop needs first to be created on the server and can be any json serializable object (including Map and Set)
sc.SubscribeProp('color', (c:string) => color = c); //can be unsubscribed
const res = await sc.SetProp('color', '#ffffff'); //this will rerun ^ the sub, if/when the server has set it, so no need to double your code everywhere!
Currently the performance is neglegable for small projects. I havent stress tested yet, but I optimize my data structures and procedures. Current estimate is about 100 concurrent users should be a breeze on a cheap Linode server. I expect your backend DB to be set up properly with table indexing and caching.
According to this blog WebSockets are much more network traffic efficient than HTTP at scale.
The use of the Socio lib does not prohibit the use of standard HTTP technologies. Even better - socio server can be configured to run on your existing http webserver, like one that you'd create with express.js. Since WebSockets are established over HTTP, then take over with their own protocol. Though, seeing as they are different technologies, there are situations where you'd need to "stitch" your own solutions between the two, e.g. tracking sessions.
I cannot guarantee perfect safety of the query encryption. Neither can anything. You may use SocioServer hooks to double check the incoming data yourself for your peace of mind. However, I can guarantee this is safer than HTTP cookie based sessions (search "cookie spoofing").
You should be using WSS:// and HTTPS:// protocols for everything, so that the data is secure over the network. That's up to you and your server configuration.
"Socio.js" comes from the latin verb "socio", which means to link or associate. Since this lib syncs your frontend and backend. Its also a play on words for "WebSockets" and "IO".