Skip to content
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

Introduce support for bi-temporal versioning in the Graph Service #928

Merged
merged 56 commits into from
Feb 3, 2023

Conversation

Alfred-Mountfield
Copy link
Contributor

@Alfred-Mountfield Alfred-Mountfield commented Feb 1, 2023

🌟 What is the purpose of this PR?

As expressed in previous PRs, we intend to add the foundations of support for bi-temporal datastores within the BP as part of the 0.3 release. This PR modifies the Graph Service to support such use-cases. It is necessarily a large PR as adding generics tends to propagate immensely.

I recommend you review this commit by commit.

🔗 Related links

🚫 Blocked by

N/A

🔍 What does this change?

Please see commits :(

📜 Does this require a change to the docs?

  • As discussed in previous PRs, this absolutely will need changes to the docs. This is deferred to a further point, but effort has been made to have good in-code documentation.

⚠️ Known issues

  • We ideally want users to be able to import different views of the package.
    • Blocks or EAs which don't care about (or do not support) temporal versioning should ideally be able to import types and functions as if there was no temporal versioning. It would be nice for us to be able to strip the generics, or prefill them in with defaults for these consumers
    • Blocks which do care about temporal versioning should ideally be able to import types and functions where it was if there were no generics and instead everything had required temporal versioning. Similar to above, would be nice to be able to prefill or strip them or something.
    • Blocks or EAs that need to handle general cases (EAs with support for temporal versioning may need to interface with blocks that do not have it for example) need to be able to import the types which have the generics in place, without default values. An example of such a consumer is MBD which will be updated in the next PR.
    • I have been unable to find a way to ergonomically do this in this PR. Because of that, I have consolidated the stdlib and stdlib-temporal endpoints for now. And am exposing the most general implementations of everything on the API
  • Some other issues have been highlighted by GitHub comments on the diff

🐾 Next steps

  • Migrate MBD and fix other accompanying packages
  • Figure out how to expose the various endpoints

🛡 What tests cover this?

  • I'm unsure, at the very least tsc and eslint.
  • I wanted to write a proper suite of tests to check this, but made the uncomfortable compromise at the moment to defer that until later on, to unblock some further work.

❓ How to test this?

📹 Demo

N/A

@github-actions github-actions bot added area: libs Relates to first-party libraries/crates/packages (area) area: libs > @Þ/graph Affects the `@blockprotocol/graph` package (library) labels Feb 1, 2023
Comment on lines +75 to +76
// @ts-expect-error -- @todo, how do we convince TS that we're only setting this when the generic is satisfied
const subgraph: Subgraph<Temporal, EntityRootType<Temporal>> = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't want to sink much time into this but completely ignoring tsc here is also not great.. I haven't found a good way to inform TSC that the generic has been narrowed at certain points within function bodies. This is a recurring inconvenience and in the following PR there will be many places where it's something like if (subgraph.temporalAxes !== undefined)... and it would be really great if there was a way to then tell TS "hey Temporal is actually true here"

Comment on lines +7 to +11
/*
* @todo - we _should_ be able to use `Extract<GraphElementForIdentifier<TemporalSupport, VertexId<any, any>>`
* here to actually get a strong type (like `EntityId` or `BaseUri`). TypeScript seems to break on using it with a
* generic though. So for now we write `string` because all of the baseId's of `VertexId` are string aliases anyway.
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually quite frustrating and if we end up making the BaseIds template strings or branded types this will catch us out.

Comment on lines -22 to -25
* - isSubgraph<DataTypeRootType>
* - isSubgraph<PropertyTypeRootType>
* - isSubgraph<EntityTypeRootType>
* - isSubgraph<EntityRootType>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, slipped through on the previous one

export * from "./edges/outward-edge-alias.js";
export * from "./edges/variants.js";

/** @todo - Re-express these and `Vertices` as `Record`s? */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure why I ended up expressing them like this originally, it seems that they'd be better as Records, and perhaps they'd behave slightly nicer sometimes with narrowing.

I don't want to change it here though because I want to make sure it doesn't break any code in usage in MBD, so will probably look into this as part of the follow-up PR.

Copy link
Member

@CiaranMn CiaranMn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phew! Thanks @Alfred-Mountfield, fantastic work.

My comments are mostly about naming / explaining stuff.

I started to optimise things but I think you're aware that we can reduce a lot of looping so I stopped.

I haven't looked at the TS errors you flag as I thought I'd get this in and then we can revisit the most egregious ones – let me know which you think might be worth further investigation.

Comment on lines +74 to +75
* @todo This assumes that the left and right entity ID of a link entity is static for its entire lifetime, that is
* not necessarily going to continue being the case
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably the relevant task to link would be whatever change we might introduce which affects this – what is it?

libs/@blockprotocol/graph/src/internal/mutate-subgraph.ts Outdated Show resolved Hide resolved
Comment on lines +22 to +25
export type EntityValidInterval = {
entityId: EntityId;
validInterval: TimeInterval<LimitedTemporalBound, TemporalBound>;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment to this type explaining this please?

I think it would also help if the name captured that this relates to the period an edge was valid for rather than an entity.

e.g.

export type EdgeApplicableFor = {
  entityId: EntityId;
  interval: TimeInterval<LimitedTemporalBound, TemporalBound>;
};

I also wondered if introducing the word 'valid' risked confusion with 'valid time'. We don't use it outside this context.

Comment on lines 95 to 103
/*
these casts are safe as we check for `targetRevisionInformation === undefined` above and that's only ever
defined if `Temporal extends true`
*/
(entity as Entity<true>).metadata.temporalVersioning[
(subgraph as Subgraph<true>).temporalAxes.resolved.variable.axis
],
targetTime,
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically there's nothing to stop JS users passing a non-temporal subgraph and targetRevisionInformation but I know that worrying about that is a whole can of worms 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been slightly concerned about this too.. I hope they'll at least read the documentation? 😅

Copy link
Contributor

@thehabbos007 thehabbos007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, Alfie! I've gotten through most files now and added some comments :)

) as Entity<Temporal>[];
} else {
return getEntityRevisionsByEntityId(
subgraph as Subgraph<true>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we have Subgraph<false> in this branch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are very correct

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) as Entity<Temporal>[];
} else {
return getEntityRevisionsByEntityId(
subgraph as Subgraph<true>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above (you are very correct), thanks for spotting! Hard to find these things and annoying that TS doesn't care

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intervalOverlapsInterval(
interval,
/*
these casts are safe as we check for `targetRevisionInformation === undefined` above and that's only ever
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This targetRevisionInformation === undefined check doesn't seem t obe present in this function, it happens in getEntityRevision line 70. Is this check meant to happen here as well or is the comment not relevant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a mistake, it should be interval !== undefined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: libs > @Þ/core Affects the `@blockprotocol/core` package (library) area: libs > @Þ/graph Affects the `@blockprotocol/graph` package (library) area: libs > @Þ/hook Affects the `@blockprotocol/hook` package (library) area: libs Relates to first-party libraries/crates/packages (area)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants