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
115 changes: 111 additions & 4 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 @@ -15,37 +15,144 @@ type DbRef = {
type Ref = LocalRef | DbRef;

// Common attributes for most types
type CrossAppBase = LocalRef & {
type BaseAttributes = {
createdAt: Date;
modifiedAt?: Date;
author: Ref;
};

// Common attributes for most types
export type CrossAppBase = LocalRef & BaseAttributes;

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

export type LocalOrRemoteRef =
| LocalRef
| {
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.

@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.

}
| {
// A string that contains combined space and localId
rid: string;
};

/*
Note on Inline vs Standalone:
The db `upsert_...` functions allow to put some objects inline inside related objects instead of passing a reference.
Those objects will either get upserted or resolved by the upsert functions.
When we provide an inline object, some shared attributes can be specified in the enclosing object.
Hence, the inline version of the object has less required attributes than the standalone version.
*/

// A node schema
export type CrossAppNodeSchema = CrossAppBase & {
label: string;
template?: string;
templateTitle?: string;
};

// A relation type schema
export type CrossAppRelationTypeSchema = CrossAppBase & {
label: string;
complement: string;
// should we add colour? format?
};

// A relation triple schema
export type CrossAppRelationTripleSchema = CrossAppBase & {
label: string;
complement: string;
relation?: Ref | CrossAppRelationTypeSchema;
Comment thread
mdroidian marked this conversation as resolved.
sourceType: Ref;
destinationType: Ref;
};

// An inline vector semantic embedding
type CrossAppEmbedding = {
value: number[];
embedding?: Enums<"EmbeddingName">;
};

// A Content object. It can be put inline inside a concept.
// Missing CrossAppBase attributes are inferred from enclosing object.
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.

BaseAttributes &
CrossAppDocumentExtras;

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

// An inline content object, to be put inside a Concept.
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 &
BaseAttributes &
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;
};