ssbc / ssb-db2

A new database for secure-scuttlebutt
47 stars 8 forks source link

Client-side db.query() alternatives #89

Open staltz opened 3 years ago

staltz commented 3 years ago

db.query is a bit difficult to support over a muxrpc stream, but it should be possible.

ssb.db.query(
  and( // this produces a function
    type('post') // this produces an object
  ),
  toPullStream() // this produces a function
)

ssb.db.query "in the same thread" (thread being either another Electron renderer, or a React Native rn-bridge thread, etc) is a synchronous function that will call all those chained functions (note that internally ssb.db.query will inject fromDB(jitdb) at the start!) and then will return a pull stream.

"In another thread", there are only async muxrpc calls, so we would have to do something like

ssb.db.query(
  and(type('post')
  toPullStream(),
  (err, ps) => {
     // ps is the pull stream
  })
)

But i'm not even sure if that would work, because toPullStream is a function, and you can't pass functions over the muxrpc stream, I guess.

There's one way to side step all of this, and that's to only use ssb.db.query "on the same thread" as sbot, and if you want to make queries from another thread, you'd have to create a custom muxrpc method on the "server side" such as ssb.extra.getPullStreamOfAllPosts which will do the snippet above, and that's what the client side calls.

But I'm quite sure someone will eventually ask for support for ssb.db.query on the client side, so we could begin thinking how to do that.

One way is that maybe on the client side we could have a custom ssb.db.query that creates objects (after all operations will be encoded as objects) and we can pass the objects from client to server, no problem. Just requires some careful thought because upon receiving such object on the server side, we have to prepend it with fromDB(jitdb) and the waitForDrain deferred thing. Also things like toPullStream could be converted to an object like {type: 'RESULT', data: 'pull-stream'}. But there's also the question of how does muxrpc know to dynamically return either "source" or "async" depending on our input, and I don't think it can do that. Maybe then we'd have ssb.db.queryAsAsync and ssb.db.queryAsSource which are "async" and "source", respectively?

Hmmm! Idea: we keep ssb.db.query as is (implicitly available, not declared on the manifest), then we add ssb.db.queryAsAsync and ssb.db.queryAsSource and those are declared in the manifest. And then maybe we should have another package, ssb-db2-client that doesn't drag in all these heavy dependencies like async-flumelog and jitdb (which are certain to not be needed on the client), and ssb-db2-client basically just has operators, and it would have one special function pipe(...args) which bundles all the arguments and spits out one single ops object, e.g.

// client side
const {pipe, and, type} = require('ssb-db2-client')

pull(
  ssb.db.queryAsSource(
    pipe(
      and(type('post'), author(me)),
      live()
      // no need to do toPullStream because that's done server side
      // in the implementation of queryAsSource()
    )
  ),
  pull.drain(console.log)
)
staltz commented 3 years ago

Improved API proposal, removing pipe:

// client side
const {and, type} = require('ssb-db2-client')

pull(
  ssb.db.queryAsSource(
    and(type('post'), author(me)),
    live()
    // no need to do toPullStream because that's done server side
    // in the implementation of queryAsSource()
  ),
  pull.drain(console.log)
)