@@ -28,6 +28,8 @@ import {
2828} from "./getDiscourseNodes" ;
2929import { isAcceptedSchema } from "./typeUtils" ;
3030import { getTemplatePluginInfo } from "./templates" ;
31+ import { difference } from "@repo/utils/setOperations" ;
32+ import { getAllPages } from "@repo/database/lib/pagination" ;
3133
3234const DEFAULT_TIME = "1970-01-01" ;
3335export type ChangeType = "title" | "content" ;
@@ -226,6 +228,7 @@ type BuildChangedNodesOptions = {
226228 supabaseClient : DGSupabaseClient ;
227229 context : SupabaseContext ;
228230 changeTypesByPath ?: Map < string , ChangeType [ ] > ;
231+ fullSync ?: boolean ;
229232} ;
230233
231234const mergeChangeTypes = (
@@ -322,6 +325,7 @@ const buildChangedNodesFromNodes = async ({
322325 supabaseClient,
323326 context,
324327 changeTypesByPath,
328+ fullSync = false ,
325329} : BuildChangedNodesOptions ) : Promise < ObsidianDiscourseNodeData [ ] > => {
326330 if ( nodes . length === 0 ) {
327331 return [ ] ;
@@ -339,6 +343,34 @@ const buildChangedNodesFromNodes = async ({
339343 context . spaceId ,
340344 ) ;
341345 const changedNodes : ObsidianDiscourseNodeData [ ] = [ ] ;
346+ let missingConcepts : Set < string > | undefined ;
347+ if ( fullSync ) {
348+ const existingConceptIds = await getAllPages (
349+ supabaseClient
350+ . from ( "my_concepts" )
351+ . select ( "source_local_id" )
352+ . eq ( "space_id" , context . spaceId )
353+ . eq ( "arity" , 0 )
354+ . eq ( "is_schema" , false )
355+ . order ( "id" ) ,
356+ 1000 ,
357+ ) ;
358+ if ( Array . isArray ( existingConceptIds ) ) {
359+ // Here, compensating for concepts that never got upserted
360+ // Probably due to non-atomicity of upsert of concept and content
361+ // TODO try to see if there are other cases
362+ // In particular, using timing when concepts get reordered by dependency
363+ // may be error-prone
364+ // fail silently otherwise, there will be other opportunities
365+ const nodeIds = new Set ( nodes . map ( ( n ) => n . nodeInstanceId ) ) ;
366+ const dbConceptIds = new Set (
367+ existingConceptIds
368+ . map ( ( d ) => d . source_local_id )
369+ . filter ( ( id ) => id !== null ) ,
370+ ) ;
371+ missingConcepts = difference ( nodeIds , dbConceptIds ) ;
372+ }
373+ }
342374
343375 for ( const node of nodes ) {
344376 if ( node . frontmatter . importedFromRid ) continue ;
@@ -355,7 +387,10 @@ const buildChangedNodesFromNodes = async ({
355387 : detectedChangeTypes ;
356388 const finalChangeTypes = mergedChangeTypes ;
357389
358- if ( finalChangeTypes . length === 0 ) {
390+ if (
391+ finalChangeTypes . length === 0 &&
392+ ! missingConcepts ?. has ( node . nodeInstanceId )
393+ ) {
359394 continue ;
360395 }
361396
@@ -397,6 +432,7 @@ export const syncAllNodesAndRelations = async (
397432 nodes : allNodes ,
398433 supabaseClient,
399434 context,
435+ fullSync : true ,
400436 } ) ;
401437
402438 const accountLocalId = plugin . settings . accountLocalId ;
0 commit comments