-
Notifications
You must be signed in to change notification settings - Fork 14
Refactor activity log types #127
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
Refactor activity log types #127
Conversation
The types are incomplete, will fix |
@@ -1,31 +1,30 @@ | |||
'use strict' | |||
|
|||
/** @typedef {import('./typings').ActivityEvent} ActivityEvent */ | |||
/** @typedef {import('./typings').ActivityEntry} ActivityEntry */ | |||
/** @typedef {import('./typings').RecordActivityOptions} RecordActivityOptions */ |
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 think we can simplify the types by thinking about them this way: One is the actual activity (Activity
), the other is the options passed to .recordActivity
(RecordActivityOptions
). To me this is clearer than having to juggle Activity
, Event
and Entry
in your mind, and the variable names are nicely consistent.
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.
We could also rename this to use Event
instead of Activity
, which is the more common term, but I do think here Activity
works better, because it's clearer that we're referring to a product concept, and not a generic event.
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 like the idea to user Activity
to describe the data structure we are storing in our log 👍🏻
I have mixed feelings about the name RecordActivityOptions
. In my mind, options is for optional configuration tweaking the behaviour of a function. How about something like RecordActivityFields
or NewActivityProps
?
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 good, I tried to look up prior art on this and found that the types for node for example call arguments options
but their type ...Args
:
Either would work for me 👍
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.
Let's use NewActivityArgs
then.
export interface RecordActivityOptions { | ||
type: ActivityType; | ||
source: ActivitySource; | ||
message: string; |
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 removed the inheritance, because Activity
is the main type, and RecordActivityOptions
more a partial of it.
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.
It makes sense to remove inheritance 👍🏻
An alternative approach that does not duplicate property definitions:
export type RecordActivityOptions = Pick<Activity, 'type' | 'source' | 'message'>;
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 I like that!
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.
On the second thought, this is probably more future proof when it comes to adding new Activity props:
export type RecordActivityOptions = Omit<Activity, 'id' | 'timestamp'>;
return a.timestamp !== b.timestamp | ||
? b.timestamp - a.timestamp | ||
: a.id.localeCompare(b.id) | ||
}) |
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.
if we moved sorting to the backend, then we wouldn't have to sort on every new event. Otherwise I think we could do it in either location, and having it on the view layer might be easier to get right wrt race conditions
@bajtos I don't understand why this isn't showing up in main ci 🤔 |
/** @type {ActivityEntry} */ | ||
const entry = { | ||
id: String(nextId), | ||
recordActivity ({ source, type, message }) { |
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.
Since this is a method of ActivityLog
class, maybe we can shorten the name to simply record
?
recordActivity ({ source, type, message }) { | |
record ({ source, type, message }) { |
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.
good idea!
export interface RecordActivityOptions { | ||
type: ActivityType; | ||
source: ActivitySource; | ||
message: string; |
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.
It makes sense to remove inheritance 👍🏻
An alternative approach that does not duplicate property definitions:
export type RecordActivityOptions = Pick<Activity, 'type' | 'source' | 'message'>;
activities.sort((a, b) => { | ||
return a.timestamp !== b.timestamp | ||
? b.timestamp - a.timestamp | ||
: a.id.localeCompare(b.id) |
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.
Are you sure ids created by crypto.randomUUID()
monotonically increasing over time?
Maybe the order of events reported in the same millisecond does not matter and that's why we can fallback our sorting comparisons to comparing ids?
I think it would be great to add a code comment to clarify this.
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.
They don't increase, and that's not an issue in my mind. As long as we sort deterministically, no human will have noticed that the events from the same millisecond have been mixed up. And we sort deterministically by giving events IDs when storing them. I'll add a comment 👍
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.
alternative idea: Is there a timing api with such precision that it's guaranteed that two successive calls won't get the same values?
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.
process.hrtime()
seems to work:
> [process.hrtime(), process.hrtime()]
[ [ 845635, 876114697 ], [ 845635, 876117208 ] ]
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.
As long as we sort deterministically, no human will have noticed that the events from the same millisecond have been mixed up.
Unless these two events happen to be something like "Starting the station" and "Starting Saturn module". It would look weird to have them in the reverse direction.
Anyhow, I think this does not really matter, especially not now.
Let's merge this into my branch and then work together there. |
To help clarify my reasoning in #110 (comment), I decided to code it up.
@bajtos what do you think?