michaellperry/jinaga

readme query

leblancmeneses opened this issue · 9 comments

In the readme you show how to use "__not: taskIsCompleted" to create this query:

j.query(chores, [uncompletedTasksInList], taskAdded);

What would the template function look like to only show completed tasks? completedTasksInList would return an object but it is not clear what the key should be for the value taskIsCompleted.

I can get use to j.query, however, query is a little confusing because of my history with sql. I see j.query as an event sourcing handler. Is the goal to eventually take these function expression trees and recreate them on the server side so the back-end can eventually calculate and call these query handlers? How would the client deregister handlers?

j.watch?
j.handle?

If I used this to create a twitter app and the user setup a column for #cute . If they came back a year later and my app had not changed, I would not want j.query to run on a mobile client starting from the beginning and replaying to today. How would query handle that scenario?

If the app is just made with event sourcing all clients will require a copy of all data to eventually be sent to all client apps (could be a lot of data for mobile clients). How would a hybrid approach look like?

j.query('/api/resource', ,,,, addedFunction, removedFunction, updatedFunction)  // entities;  will not *sync* old messages prior to current session start.

j.watch(start, templates, resultAdded, resultRemoved)  // materialized views; syncs all messages old and new.

// It may be possible to just use j.watch with a settings object to accomplish both of these approaches without different methods.

other missing sections in readme:

  • migrations

Good questions. The query will last as long as your session. j.query() will return an object that you can use to cancel the query at any time, or it will close when you leave the page. Subscription state is mostly client-side, so there is very little that needs to be cleaned up on the server.

The client will send the server a list of joins that represent the query - the same join list used to query local memory or storage. The server will send back all matching messages, and then continue to send them as new messages are posted. It will stop when the connection is broken, based on the type of connection used (web sockets, SignalR, long polling, etc.)

The template to find only completed tasks will use __where instead of __not. Otherwise exactly the same.

I'll document migrations next. Thanks!

With respect to the Twitter and related Event Sourcing issues, you should build checkpoints into your model. For example, you wouldn't want to query for all tweets having a certain hash tag. Instead, you would want all tweets in a given day having a certain hash tag. That way, the query can go back only as far as you want it to.

I learned with Correspondence that most models have natural checkpoints in them anyway. When they don't, time is a good one. Just as you don't want open-ended queries in any database, you don't want them in a historical model.

__where: Jinaga.not(taskIsCompleted)  rather than introducing __not?

I also prefer j.watch rather than j.query because it is a live query.

You should update the docs for taskIsCompleted to have the time checkpoint.

Good stuff @michaellperry .

There might be some way to combine __not into __where. I want the syntax to be light, however, since I've found in practice that a not-exists type of query is much more common than an exists query.

Also, let me try on the j.watch nomenclature. In addition to documenting that it is a live query, it also avoids confusion with a certain JavaScript framework.

How about this syntax for fact?

previous:

var email {
   __to: alan1,
   __from: flynn,
   content: "It's all in the wrists."
};

q.fact(email);

to

q.fact({ content: "It's all in the wrists." }, { to: alan1, from: flynn });

this moves writing knowledge of metadata off the object.

You could always append __jinaga to store metadata jinaga uses.

This is an important distinction of historical modeling vs. systems like Firebase: __to and __from are not metadata. They are predecessors, which are part of the fact. I want the developer to be aware of that while coding.

function taskIsNotCompleted(t) {
  return j.not({
    task: t,
    completed: true
  });
}

How do you not an object?

If you do not like:

q.fact({ content: "It's all in the wrists." }, { to: alan1, from: flynn });

consider a builder because memorizing the various __* attributes makes it hard to use. A builder will also work nice with IntelliSense based IDE. Why is flynn needed in your Secrecy? Shouldn't the system know the current user?

Builder Example

var fact = j.builder({ content: "It's all in the wrists." }).directMsg([alan1]);
q.fact(fact.build());
var project = {
  name: "Space Paranoids",
};

var factBuilder = j.builder(project, function(builder){
  builder.locked(function(lb){
    lb.admin([j.user]).write([alan1]).read([alan1]);
   });
   builder.in({  program: "TRON" });
});
q.fact(factBuilder.build());  // fact() can be Object or Array<Object>

OR

var fact1 = j.builder(project).locked();
q.fact(fact1.build());

var fact2 = j.builder(project).admin([j.user]);
q.fact(fact2.build());

var fact3 = j.builder(project).write([alan1]);
q.fact(fact3.build());

var fact4 = j.builder(project).read([alan1]);
q.fact(fact4.build());

var fact5 = j.builder({  program: "TRON" }).in(project);
q.fact(fact5.build());

With templates, I'm committing the same crime that I've seen in several JavaScript frameworks to turn what is admittedly a weak language into a DSL. I'm using a JavaScript literal as a specification, not a literal object.

The not function simply wraps up one specification into another specification that carries with it the information that it has been inverted. I'm thinking that not should also be able to take a function as an argument. That way it can also operate on the template from the outside, similar to what you suggested

{
  __where: j.not(taskIsCompleted)
}

I don't want to go down the builder path because the "reserved words" are actually not reserved at all. They just have additional semantics above being fields and predecessors. In fact, I'm considering going back to the form without the "__", since there is really nothing special about them. They are simply conventions that the distributor understands.

You've seen the complete list of conventions:

  • publicKey
  • from
  • to
  • locked (may actually be redundant, and so might get dropped)
  • admin
  • read
  • write

I'm comfortable with the need to memorize or reference this list, since you'll be following well-worn patterns to use them.

On the other hand, __where is truly treated differently. I'm considering removing it from the template and putting it into a decorator function similar to not:

function uncompletedTasks(l) {
  return j.where({
    list: l
  }, [taskIsIncomplete]);
}

The array, BTW, is so that you can and a bunch of conditions.

Finally, no the system does not necessarily know the current user. Consider a service running in Node, which acts on behalf of several users. Besides, from is simply a predecessor, and all predecessor relationships need to be explicit. Not all messages that a user sends will have that user as a predecessor.

j.where and j.not are working. j.user is enforced. The other security methods are in progress.