-
-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Major core rewrite! #431
Major core rewrite! #431
Conversation
### Removed dependencies - protobufjs - objection - sqlite3 - superagent (pure `fetch`-based codebase now) ### `shared/events.js` rewrite - New `GIMessage` core class - Now virtually everything moved under `model/contracts/*` - SBP is now used to contruct messages ### Database improvements - Now Group Income contract chains are is designed to work purely with key-value stores - `backend/database.js` and `model/database.js` are now virtually identical and in the future can be made to use the same library ### Misc. improvements - Simplified `backend/routes.js` API - Re-added explicit `.js` to imports (please always include it!) - Replaced all `Vue.events` with SBP `'okTurtles.events/*' - Various code improvements - Backend tests now use same pubsub and backend API code as frontend - Deleted lots of classes and converted more parts of the app to SBP - Greatly simplified `handleEvent` action in `state.js` - Changed `okTurtles.events` API to match `Vue.events` behavior
Copying from Slack:
|
var idx = subscriptions.indexOf(contractID) | ||
var method = data.add ? 'sub' : 'unsub' | ||
if ((data.add && idx > -1) || (data.remove && idx === -1)) { | ||
return // if already subscribed or already unsubscribed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually a function should always return something, even if false
only, that I believe it's what's intended here. return false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function doesn't return anything, so returning undefined
is perfectly fine
if (state.proposals[data.proposalHash]) { | ||
state.proposals[data.proposalHash].for.push(data.username) | ||
let threshold = Math.ceil(state.proposals[data.proposalHash].threshold * Object.keys(state.profiles).length) | ||
if (state.proposals[data.proposalHash].for.length >= threshold) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
state.proposals[data.proposalHash]
is used 4 times here. what about having a variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm avoiding messing with the old proposals code as I hope @hubudibu will be able to rewrite it and improve upon it, so my messing with it right now probably wouldn't be worthwhile
'okTurtles.events/on': function (event: string, handler: Function) { | ||
sbp('okTurtles.data/add', `events/${event}/listeners`, handler) | ||
sbp('okTurtles.data/add', listenKey(event), handler) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool work here! The new logs on the console can be helpful!
I played a little with sbp()
locally during this CR and some questions came to my mind:
-
Does
sbp
have a naming conventions for the selectors? I saw selectores in camelCase and kebab-case, (backend/publishLogEntry
vsgi/contract/create-action
) and I believe we should sticky with one, similar to what Vue did with events names (kebab-case). -
Do we have guidelines to decide the scope of a selector?
backend
,gi
,okturtles
,namespace
,state
, etc... It's not hard to give the same name twice. How do you plan to avoid this besides using an SBP logging facility? -
I registered my first
sbp()
using'sbp/selectors/register'
and'okTurtles.events/on'
with ease!
// GroupDashboard.vue
created () {
sbp('sbp/selectors/register', {
'test/selector-1': (origin) =>
console.log('My first selector from ' + origin)
})
sbp('okTurtles.events/on', 'hello', (origin) =>
console.log('My first hello emit from ' + origin)
)
}
// Voting.vue
handleVoteFor () {
sbp('test/selector-1', 'selector-voting')
sbp('okTurtles.events/emit', 'hello', 'emit-voting')
// ...
},
And both logged
> My first selector from selector-voting
> My first hello emit from emit-voting
So, it looks like they both do the same but with different usages. Can you tell/explain me the core difference between both and when should I use one or another?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes indeed they look somewhat similar but the semantics are rather different, for example:
- You can emit an event that has no listeners but you cannot call a selector that hasn't been registered
- A single event can have multiple listeners but a selector can have only one function registered for it
- When you emit an event there is no return value, but when you can a selector there is
etc.
Generally speaking, for all Vue related things you should probably use events only. Selectors should be registered only for things that can be conceptualized as services. In this way events are a bit more "light weight" than selectors.
By separating events and selectors we also have the ability to do things like add SBP filters just for events that are emitted, so for example, if we wanted to re-integrate the Vue.js Chrome debugger extension with our events system, so that okTurtles.events appear in the Vue events debugger, we could add an SBP filter to emit Vue events for every okTurtles.events that were emitted, while ensuring that SBP calls do not appear in that list.
Does sbp have a naming conventions for the selectors? I saw selectores in camelCase and kebab-case, (backend/publishLogEntry vs gi/contract/create-action) and I believe we should sticky with one, similar to what Vue did with events names (kebab-case).
I will think on this. I agree it's important but it's also a decision that requires a lot of thought and I wanted to get this PR in quickly.
Do we have guidelines to decide the scope of a selector? backend, gi, okturtles, namespace, state, etc... It's not hard to give the same name twice
So that would be called the "domain" of a selectors. And no, at the moment we haven't figured out good guidelines for that, besides making sure to only register domains for clearly defined services, and avoid name collisions.
I am considering prefixing backend
etc. under the gi
domain, or gi.frontend
domain, but this is something that I'll need to give more thought to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the extended clarification. It would be cool to add the emit
vs register
sbp usage somewhere on our docs :)
shared/sbp.js
Outdated
for (const selector in sels) { | ||
// TODO: debug log (using an SBP logging facility) if we're already registered | ||
if (!selectors[selector]) { | ||
selectors[selector] = sels[selector] | ||
registered.push(selector) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a quick solution to warn when a selector already exists
} else {
console.warn(`The selector ${selector} already exists`)
}
targetArray.push(data) | ||
const array = _store.get(key) | ||
if (array) { | ||
array.push(data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to verify if data
already exists on array
value, or can it have the same data twice?
var array = store.get('bar') // ['foo', 'foo2']
array.push('foo2');
console.log(store.get('bar')); // ['foo', 'foo2', 'foo2'] // Same data twice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it's fine to have the same data twice for performance reasons. If it's an issue for the caller they can be the ones to check for duplicate data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oki
} else { | ||
subscriptions.splice(idx, 1) | ||
} | ||
var res = await primus[method](contractID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like a risky few lines! how about doing & awaiting the primus sub/unsub and updating the subscriptions array after that?
shared/events.js
Outdated
return blake32Hash(data) | ||
} | ||
|
||
export class GIMessage { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we rename this file to GIMessage
or something alike? events
doesn't really say much about this file any more :)
} | ||
|
||
var subscriptions = [] | ||
var primus |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve been thinking why it was so hard for me to process this file and I think we can make it much easier to understand with a few naming changes.
primus
for me means a) the god of transformers b) a tool that we use in another file. a name saying what it does here would be more helpful, eg.socket
or, more specifically,contractSocket
, as this seems to be for syncing contracts?- similarly, I had an aha moment when I realized that
subscriptions
is storing contracts and that’s what is referred to as rooms at other places, socontractSubscriptions
orcontractRooms
or something alike could be more helpful?
frontend/simple/model/utils.js
Outdated
// and when that GIMessage is received and is applied as a mutation | ||
vuexModule.mutations[name] = function (state, data) { | ||
validate(data.data) | ||
vuex.mutation(state, data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the naming here is a bit misleading, I just spent a considerable amount of time trying to figure out what's this secret vuex feature that somehow constructs mutations :) where this is just a key in our contract structure. I would suggest using a name describing the concept it's for, and not the actual tool name. like contractStore
or at least forVuex
or something?
in general, I don't find this utility function that useful, it seems like most of what it does is make the contracts look significantly different from standard vuex modules with all these transformations, thus making them harder to read & understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, using vuexModuleConfig
, sound ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds much better!
frontend/simple/model/utils.js
Outdated
|
||
// NOTE: must check explicitely if 'constructor' is true | ||
// because that key is defined by default on objects as some function... | ||
if (constructor === true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest using a different name here, like isConstructor
, as constructor is like a 'reserved word' in js land, and as the comment already notes, we are in some cases overwriting existing functions this way
frontend/simple/model/state.js
Outdated
@@ -258,79 +277,47 @@ const actions = { | |||
// mirrors `handleEvent` in backend/server.js | |||
async handleEvent ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment explaining what this method does seems no longer to be true
test fix 1 in test/frontend.js line 429: await n
// .goto(page('mailbox'))
// TODO: navigation gets redirected on login guard but nav click doesn't?
// we might have logged in state problems
.wait(elT('mailboxLink'))
.click(elT('mailboxLink')) also, what's the workflow if I want to push to someone else's PR? |
@@ -21,7 +21,7 @@ exports.register = function ( | |||
if (scheme !== 'gi') return reply(Boom.badRequest('Bad authentication')) | |||
|
|||
try { | |||
json = JSON.parse(b642str(json)) | |||
json = JSON.parse(b64ToStr(json)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is definitely a way more fortunate naming convention for this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... that's a book. Can you be more specific?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I misread your comment, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just confirming that b64ToStr()
is way better (less cryptic) than b642str()
.
Although the best could be base64ToString()
- the less it makes me think, the less i have to decipher mnemonics in my brain the easier it is to understand.
backend/database.js
Outdated
// delete the test database if it exists | ||
!production && fs.existsSync('test.db') && fs.unlinkSync('test.db') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
glad to see this go too! =)
next test fix step: we didn't clean up properly after logout, so the message contracts got borked together. next step here: hubudibu@457cfa2 |
In the template of GroupDashboard.vue on line 11 there needs to be conditional rendering that checks for the current group state before trying to render its properties
|
Removed dependencies
fetch
-based codebase now)shared/events.js
rewriteGIMessage
core classmodel/contracts/*
Database improvements
with key-value stores
backend/database.js
andmodel/database.js
are now virtuallyidentical and in the future can be made to use the same library
Misc. improvements
backend/routes.js
API.js
to imports (please always include it!)Vue.events
with SBP'okTurtles.events/*'
handleEvent
action instate.js
okTurtles.events
API to matchVue.events
behavior