Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 85 additions & 3 deletions packages/database/src/crossAppContracts.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Breaking change to exported contract type

This PR completely replaces the shape of CrossAppNode — from a flat structure with fields like sourceApp, sourceSpaceId, sourceNodeId, sourceNodeRid, sourceModifiedAt, and content.full.format to a deeply nested composable structure using LocalRef, BaseAttributes, Ref, etc. While the grep confirms no other files currently import from this module besides the two example files, any external consumers (other repos, deployed services) that depend on @repo/database/crossAppNodeContract will break at both the import path level (renamed to crossAppContracts) and the type shape level. This is worth noting in release notes or migration documentation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ type DbRef = {
// Generalized reference
export type Ref = LocalRef | DbRef;

type SpaceRef = DbRef | { url: string; sourceApp: Enums<"Platform"> };

// A potentially cross-space reference
export type LocalOrRemoteRef =
| DbRef
| {
localId: string;
// Treat as LocalRef if space is 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.

@mdroidian mdroidian Jul 5, 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.

I'm assuming that localId/space can be used for more use cases? If not, then having it in a union this high up will just complicate things by making the downstream types just more complex for a single use case.

// make the options mutually exclusive
dbId?: never;
rid?: never;
}
| {
// A string that contains combined space and localId
rid: string;
// make the options mutually exclusive
dbId?: never;
localId?: never;
space?: never;
};

// Common attributes for most types
export type CrossAppBase = LocalRef & {
createdAt: Date;
Expand Down Expand Up @@ -63,25 +85,85 @@ export type CrossAppEmbedding = {
embedding?: Enums<"EmbeddingName">;
};

// A Content object. It can be put inline inside a concept.
// Missing CrossAppBase attributes are inferred from enclosing object.
export type InlineCrossAppContent = Partial<CrossAppBase> & {
// An asset reference
export type CrossAppAsset = {
content: ArrayBuffer;
mimetype: string;
createdAt: Date;
modifiedAt?: Date;
filepath?: string;
};

// Document fields
type CrossAppDocumentExtras = {
// MIME type
contentType: string;
};

// An inline document, to put inside Content or Concept.
// Currently, we fully infer Documents (setting content_as_document=true)
// since our nodes are pages; so this is not used yet.
export type InlineCrossAppDocument = Partial<CrossAppBase> &
CrossAppDocumentExtras;

// A standalone document, for `upsert_documents`. Not currently used.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type StandaloneCrossAppDocument = LocalRef &

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.

What is a standalone document? And where is it intended to be used?

If it's not used now, why is it included in this PR?

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.

Standalone: See above. Here, I'm using "future" in a very restricted sense: Not yet used in ENG-1985. This is the type you would use if you were to call upsert_document on its own. It is not speculative in that way, and I think that having both the inline and standalone type clarifies the inline usage.

CrossAppBase &
CrossAppDocumentExtras;

// Content fields
type CrossAppContentExtras = {
value: string;
embedding?: CrossAppEmbedding;
scale?: Enums<"Scale">;
partOf?: Ref;
assets?: CrossAppAsset[];
document?: InlineCrossAppDocument;
contentType?: ContentType;
};

// A Content object. It can be put inline inside a concept.
// Missing CrossAppBase attributes are inferred from enclosing object.
export type InlineCrossAppContent = Partial<CrossAppBase> &
CrossAppContentExtras;

// A standalone content object, for `upsert_content`
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type StandaloneCrossAppContent = LocalRef &
CrossAppBase &
CrossAppContentExtras;

// An inline Content with obligatory typing
type InlineCrossAppTypedContent = InlineCrossAppContent & {
contentType: ContentType;
};

// A platform account
// either standalone for `upsert_account_in_space`,
// or inline in Content or Concept
export type CrossAppAccount = {
accountLocalId: string;
name?: string;
email?: string;
// agentType: Enums<"AgentType"> = 'person' // inferred
// dgAccount?: string; // uuid
};

// A node instance
export type CrossAppNode = CrossAppBase & {
nodeType: Ref;
content: {
direct: InlineCrossAppContent;
full: InlineCrossAppTypedContent;
};
// This is a way to define document globally for all contents
document?: InlineCrossAppDocument;
};

// A relation instance
export type CrossAppRelation = CrossAppBase & {
relationType: Ref;
source: LocalOrRemoteRef;
destination: LocalOrRemoteRef;
};