Here is my updated thoughts on the matter. Can we make it mongoDB like. Mongoose v8.0.3: Schemas
With a directive @timestamps( createdAt: Boolean, updatedAt: Boolean )
It could be used the following way:
type Post @timestamps { # defaults both createdAt and updatedAt to true
id: ID
title: String!
}
The predicate _createdAt
and _updatedAt
should be added to the list of reserved names.
If a user needed to manually set or update the _createdAt
or _updatedAt
I believe it should be allowed through DQL only. Meaning that GraphQL does not allow any input for these values other than searching.
Cons: There is no way to timestamp edges without either using facets or a linking type. Facets are further out than 21.03 I believe and even then are not first rate citizens. Like many other implementations of timestamps, it does not indicate which predicates/edges were updated, just that there was something updated. With other databases using joining tables it is possible to update timestamps in a join table without updating timestamps on the tables that were joined.
Pros: All timestamps are handled 100% by the rewriting methods in Go. These timestamps are directly immutable by a user within GraphQL. This ensures that the _createdAt
is true and was not updated by a later GraphQL mutation. Auto updating timestamps on nodes when edges are mutated, gives a sync script the ability to refresh the data for that entire node and their referenced edges.
Implementation:
- When an add mutation is rewritten and its type has the
@timestamp
directive, it gets the_createdAt
predicate added with the equivalent ofnew Date()
- When an update mutation is rewritten and its type has the
@timestamp
directive, the_updatedAt
predicate is set to the equivalent ofnew Date()
- When an edge is created/removed that utilizes the
@hasInverse
directive, then both nodes on either side are updated with the_updatedAt
predicate with the equivalent ofnew Date()
.
This would then generate this GraphQL schema:
type Post {
id: ID
title: String!
_createdAt: DateTime!
_updatedAt: DateTime
}
input PostFilter {
id: [ID!]
_createdAt: DateTimeFilter
_updatedAt: DateTimeFilter
has: PostHasFilter
and: [PostFilter]
or: [PostFilter]
not: PostFilter
}
enum PostHasFilter {
"title"
"_createdAt" # seems kinda silly having a required field, but following precedents already set.
"_updatedAt"
}
input PostOrder {
asc: PostOrderable
desc: PostOrderable
then: PostOrder
}
enum PostOrderable {
"title"
"_createdAt"
"_updatedAt"
}
type PostAggregateResult {
count: Int
titleMin: String
titleMax: String
_createdAtMin: DateTime
_createdAtMax: DateTime
_updatedAtMin: DateTime
_updatedAtMax: DateTime
}
input AddPostInput {
title: String!
# Notice, do not include _createdAt and _updatedAt here because they are handled internally
}
type AddPostPayload {
post(
filter: PostFilter
order: PostOrder
first: Int
offset: Int
): [Post]
numUids: Int
}
input UpdatePostInput {
filter: PostFilter!
set: PostPatch
remove: PostPatch
}
input PostPatch {
title: String
# Notice, do not include _createdAt and _updatedAt here because they are handled internally
}
type UpdatePostPayload {
post(
filter: PostFilter
order: PostOrder
first: Int
offset: Int
): [Post]
numUids: Int
}
type DeletePostPayload {
post(
filter: PostFilter
order: PostOrder
first: Int
offset: Int
): [Post]
msg: String
numUids: Int
}
PostRef {
id: ID
title: String
# Notice, do not include _createdAt and _updatedAt here because they are handled internally
}
type Query {
getPost(id: ID!): Post
queryPost(
filter: PostFilter
first: Int
offset: Int
order: PostOrder
): [Post]
aggregatePost(filter: PostFilter): PostAggregateResult
}
type Mutation {
addPost(input: [AddPostInput!]!): AddPostPayload
updatePost(input: UpdatePostInput!): UpdatePostPayload
deletePost(filter: PostFilter!): DeletePostPayload
}
Which would translate roughly to this DQL schema:
Post.title: string .
Post._createdAt: dateTime @index(hour) .
Post._updatedAt: dateTime @index(hour) .
Maybe the index should be configurable from the @timestamp directive, hmm…
Alternatively, the _createdAt
and _updatedAt
GraphQL fields could all map to the same two predicates. This would make DQL statements easier to get a list of everything that was updated filtering on a single predicate. However, this may degrade distributed performance for larger databases as the number of predicates that could be sharded is dropped leaving a large amount of work always in a single alpha/group.
After Thoughts through PM:
So would that be sortable?
Timestamps would be sortable but not mutable directly. Meaning in GraphQL you couldn’t do set: {_createdAt: "2020-01-20"}
as that is reserved only for the add mutation or when doing any input ref that creates new.
What if you needed to update a time stamp? For instance for importing data with current timestamps
for importing, I think it should go 100% into DQL if you are importing something that has a preset timestamp. Anything imported using GraphQL you could create your own DateTime field and write to that instead. That would allow to do:
- This was imported at [_createdAt]
- The imported data was originally created at [custom importedDateTime field]
Sounds good. Just seems like there needs to be some sort of way to modify that. Not super easily of course since it should only be used in special cases
It would be modifiable through DQL. I think that is the preferred method anyways. Thinking of GraphQL as the API and DQL as the database language. It would be similar to a REST API serving MySQL. In MySQL you could update the timestamps as needed, but when using the REST API, the timestamps are all handled internally blindly, outside of your control.