Skip to content

ENG-1983 Define intermediate schemas for node, relations and schemas#1183

Draft
maparent wants to merge 1 commit into
mainfrom
eng-1983-define-intermediate-schemas-for-node-relations-and-schemas
Draft

ENG-1983 Define intermediate schemas for node, relations and schemas#1183
maparent wants to merge 1 commit into
mainfrom
eng-1983-define-intermediate-schemas-for-node-relations-and-schemas

Conversation

@maparent

@maparent maparent commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

@linear-code

linear-code Bot commented Jul 2, 2026

Copy link
Copy Markdown

ENG-1983

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
discourse-graph Ready Ready Preview, Comment Jul 2, 2026 3:56pm

Request Review

@supabase

supabase Bot commented Jul 2, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@maparent maparent force-pushed the eng-1983-define-intermediate-schemas-for-node-relations-and-schemas branch from 3badfb0 to 4bbf2f8 Compare July 2, 2026 13:49
@maparent maparent force-pushed the eng-1983-define-intermediate-schemas-for-node-relations-and-schemas branch from 4bbf2f8 to a8bc33d Compare July 2, 2026 15:07
@maparent maparent force-pushed the eng-1983-define-intermediate-schemas-for-node-relations-and-schemas branch from a8bc33d to 47202b7 Compare July 2, 2026 15:30
@maparent maparent force-pushed the eng-1983-define-intermediate-schemas-for-node-relations-and-schemas branch from 47202b7 to 66ebc78 Compare July 2, 2026 15:55
@@ -0,0 +1,92 @@
import type { ContentType } from "@repo/content-model";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@maparent
Let's only export types that are being used elsewhere. This adds positive signal and really helps navigate and grok the file.

When we export types that are not used, it adds a false positive and makes it harder to understand intent.

@sid597 sid597 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Some questions and suggestions, I read CrossApp... so many time that now I am confused what it even means lol

full: { format: contentTypes.markdown, value: roamFullMarkdown },
},
sourceModifiedAt: "2026-06-12T14:00:00.000Z",
createdAt: new Date("2026-06-12T14:00:00.000Z"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We removed the modifiedAt? example from both cases. Also why is it optional? I think keeping it optional makes downstream code like roamOriginNodeExmple.modifiedAt? and then dependent on that. We can make the initial modifiedAt same as createdAt iff its not present.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I could add it back in the example. It's optional precisely for the reason you gave: it uses createdAt if absent.

sourceApp: "Roam",
url: ROAM_SOURCE_SPACE_ID,
},
local_id: ROAM_SOURCE_NODE_ID,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

snake case -> camelCase

What do you think about naming, like appending source vs direct e.g

  • sourceLocalId, vs localId
  • space.sourceApp vs space.app

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I have tended to remove source here, because everything was named sourceSomething, and it did not seem to add specific information (as you were remarking about CrossApp!) Maybe I missed something? Fully negotiable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to camelCase for TypeScript-facing contract fields

| {
localId: string;
// infer space from context if absent.
space?: SpaceRef;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this for future? Can space be absent in Obsidian <-> roam sync?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is for the case where we have a reference to a node in another space. It currently happens in Obsidian; the source or destination of a relation may be an imported node, in which case we give the reference to the original node (imported nodes are not materialized again.)
But yes, it can be absent and inferred from context in most cases.

export type CrossAppRelationTripleSchema = CrossAppBase & {
label: string;
reverseLabel: string;
relation?: Ref | CrossAppRelationTypeSchema;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorry this is confusing like we already have the label, and reverseLabel is that like source space's version? and this one is for the target's space (or vice-versa)? If so can we name them to reflect that?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

reverseLabel is the label of the reverse relation. So nothing to do with source space or import.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah, and I put it on the triple, which is redundant, because in Roam we don't have the relation as a separate object.

@mdroidian mdroidian Jul 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's use complement here instead of reverseLabel

reverseLabel looks new in this PR, while complement is already the repo-wide term for the inverse/reverse relation label in both Roam and Obsidian. For example, Roam relation settings and query logic use label / complement, and Obsidian relation types also store label / complement.

value: string;
localId?: string;
embedding?: CrossAppEmbedding;
author: CrossAppAccount | Ref;

@mdroidian mdroidian Jul 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When would author use Ref (specifically LocalRef)?

};

export type CrossAppAccount = {
account: string;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this intended to be accountLocalId?

account is ambiguous here: it could mean a display label, platform username, database account id, or the whole account object. Existing DB-facing shapes use account_local_id, and TypeScript-facing code generally uses accountLocalId.

createdAt: Date;
modifiedAt?: Date;
author: CrossAppAccount | Ref;
contributors?: (CrossAppAccount | Ref)[];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't believe contributors is actively used anywhere today, correct? I see the DB tables for it, but I don't see app/converter paths producing or consuming it.

If that's right, I'd prefer we wait to add this to the cross-app contract until we have a concrete use case and are actually wiring it through. Otherwise it adds another field to reason about without a clear behavior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants