{"version":3,"file":"subscribe.mjs","names":["fragmentSubscription: FragmentSubscription<TReadFromStore>"],"sources":["../../src/core/subscribe.ts"],"sourcesContent":["import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison';\nimport type { EncounteredIds } from './cache';\nimport type {\n  FragmentReference,\n  UnknownTReadFromStore,\n} from './FragmentReference';\nimport type {\n  FragmentSubscription,\n  IsographEnvironment,\n} from './IsographEnvironment';\nimport { logMessage } from './logging';\nimport { type WithEncounteredRecords, readButDoNotEvaluate } from './read';\nimport type { ReaderAst } from './reader';\n\nexport function subscribe<TReadFromStore extends UnknownTReadFromStore>(\n  environment: IsographEnvironment,\n  encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,\n  fragmentReference: FragmentReference<TReadFromStore, any>,\n  callback: (\n    newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,\n  ) => void,\n  readerAst: ReaderAst<TReadFromStore>,\n): () => void {\n  const fragmentSubscription: FragmentSubscription<TReadFromStore> = {\n    kind: 'FragmentSubscription',\n    callback,\n    encounteredDataAndRecords,\n    fragmentReference,\n    readerAst,\n  };\n\n  // subscribe is called in an effect. (We should actually subscribe during the\n  // initial render.) Because it's called in an effect, we might have missed some\n  // changes since the initial render! So, at this point, we re-read and call the\n  // subscription (i.e. re-render) if the fragment data has changed.\n  callSubscriptionIfDataChanged(environment, fragmentSubscription);\n\n  environment.subscriptions.add(fragmentSubscription);\n  return () => environment.subscriptions.delete(fragmentSubscription);\n}\n\n// Calls to readButDoNotEvaluate can suspend (i.e. throw a promise).\n// Maybe in the future, they will be able to throw errors.\n//\n// That's probably okay to ignore. We don't, however, want to prevent\n// updating other subscriptions if one subscription had missing data.\nfunction logAnyError(\n  environment: IsographEnvironment,\n  context: any,\n  f: () => void,\n) {\n  try {\n    f();\n  } catch (e) {\n    logMessage(environment, () => ({\n      kind: 'ErrorEncounteredInWithErrorHandling',\n      error: e,\n      context,\n    }));\n  }\n}\n\nexport function callSubscriptions(\n  environment: IsographEnvironment,\n  recordsEncounteredWhenNormalizing: EncounteredIds,\n) {\n  environment.subscriptions.forEach((subscription) =>\n    logAnyError(environment, { situation: 'calling subscriptions' }, () => {\n      switch (subscription.kind) {\n        case 'FragmentSubscription': {\n          // TODO if there are multiple components subscribed to the same\n          // fragment, we will call readButNotEvaluate multiple times. We\n          // should fix that.\n          if (\n            hasOverlappingIds(\n              recordsEncounteredWhenNormalizing,\n              subscription.encounteredDataAndRecords.encounteredRecords,\n            )\n          ) {\n            callSubscriptionIfDataChanged(environment, subscription);\n          }\n          return;\n        }\n        case 'AnyRecords': {\n          logAnyError(\n            environment,\n            { situation: 'calling AnyRecords callback' },\n            () => subscription.callback(),\n          );\n          return;\n        }\n        case 'AnyChangesToRecord': {\n          if (\n            recordsEncounteredWhenNormalizing\n              .get(subscription.recordLink.__typename)\n              ?.has(subscription.recordLink.__link) != null\n          ) {\n            logAnyError(\n              environment,\n              { situation: 'calling AnyChangesToRecord callback' },\n              () => subscription.callback(),\n            );\n          }\n          return;\n        }\n      }\n    }),\n  );\n}\n\nfunction callSubscriptionIfDataChanged<\n  TReadFromStore extends UnknownTReadFromStore,\n>(\n  environment: IsographEnvironment,\n  subscription: FragmentSubscription<TReadFromStore>,\n) {\n  const newEncounteredDataAndRecords = readButDoNotEvaluate(\n    environment,\n    subscription.fragmentReference,\n    // Is this wrong?\n    // Reasons to think no:\n    // - we are only updating the read-out value, and the network\n    //   options only affect whether we throw.\n    // - the component will re-render, and re-throw on its own, anyway.\n    //\n    // Reasons to think not:\n    // - it seems more efficient to suspend here and not update state,\n    //   if we expect that the component will just throw anyway\n    // - consistency\n    // - it's also weird, this is called from makeNetworkRequest, where\n    //   we don't currently pass network request options\n    {\n      suspendIfInFlight: false,\n      throwOnNetworkError: false,\n    },\n  );\n\n  const mergedItem = mergeObjectsUsingReaderAst(\n    subscription.readerAst,\n    subscription.encounteredDataAndRecords.item,\n    newEncounteredDataAndRecords.item,\n  );\n\n  logMessage(environment, () => ({\n    kind: 'DeepEqualityCheck',\n    fragmentReference: subscription.fragmentReference,\n    old: subscription.encounteredDataAndRecords.item,\n    new: newEncounteredDataAndRecords.item,\n    deeplyEqual: mergedItem === subscription.encounteredDataAndRecords.item,\n  }));\n\n  if (mergedItem !== subscription.encounteredDataAndRecords.item) {\n    logAnyError(\n      environment,\n      { situation: 'calling FragmentSubscription callback' },\n      () => {\n        subscription.callback(newEncounteredDataAndRecords);\n      },\n    );\n    subscription.encounteredDataAndRecords = newEncounteredDataAndRecords;\n  }\n}\n\nfunction hasOverlappingIds(\n  ids1: EncounteredIds,\n  ids2: EncounteredIds,\n): boolean {\n  for (const [typeName, set1] of ids1.entries()) {\n    const set2 = ids2.get(typeName);\n    if (set2 === undefined) {\n      continue;\n    }\n\n    if (isNotDisjointFrom(set1, set2)) {\n      return true;\n    }\n  }\n  return false;\n}\n\n// TODO use a polyfill library\nfunction isNotDisjointFrom<T>(set1: Set<T>, set2: Set<T>): boolean {\n  for (const id of set1) {\n    if (set2.has(id)) {\n      return true;\n    }\n  }\n  return false;\n}\n"],"mappings":";;;;;AAcA,SAAgB,UACd,aACA,2BACA,mBACA,UAGA,WACY;CACZ,MAAMA,uBAA6D;EACjE,MAAM;EACN;EACA;EACA;EACA;EACD;AAMD,+BAA8B,aAAa,qBAAqB;AAEhE,aAAY,cAAc,IAAI,qBAAqB;AACnD,cAAa,YAAY,cAAc,OAAO,qBAAqB;;AAQrE,SAAS,YACP,aACA,SACA,GACA;AACA,KAAI;AACF,KAAG;UACI,GAAG;AACV,aAAW,oBAAoB;GAC7B,MAAM;GACN,OAAO;GACP;GACD,EAAE;;;AAIP,SAAgB,kBACd,aACA,mCACA;AACA,aAAY,cAAc,SAAS,iBACjC,YAAY,aAAa,EAAE,WAAW,yBAAyB,QAAQ;AACrE,UAAQ,aAAa,MAArB;GACE,KAAK;AAIH,QACE,kBACE,mCACA,aAAa,0BAA0B,mBACxC,CAED,+BAA8B,aAAa,aAAa;AAE1D;GAEF,KAAK;AACH,gBACE,aACA,EAAE,WAAW,+BAA+B,QACtC,aAAa,UAAU,CAC9B;AACD;GAEF,KAAK;AACH,QACE,kCACG,IAAI,aAAa,WAAW,WAAW,EACtC,IAAI,aAAa,WAAW,OAAO,IAAI,KAE3C,aACE,aACA,EAAE,WAAW,uCAAuC,QAC9C,aAAa,UAAU,CAC9B;AAEH;;GAGJ,CACH;;AAGH,SAAS,8BAGP,aACA,cACA;CACA,MAAM,+BAA+B,qBACnC,aACA,aAAa,mBAab;EACE,mBAAmB;EACnB,qBAAqB;EACtB,CACF;CAED,MAAM,aAAa,2BACjB,aAAa,WACb,aAAa,0BAA0B,MACvC,6BAA6B,KAC9B;AAED,YAAW,oBAAoB;EAC7B,MAAM;EACN,mBAAmB,aAAa;EAChC,KAAK,aAAa,0BAA0B;EAC5C,KAAK,6BAA6B;EAClC,aAAa,eAAe,aAAa,0BAA0B;EACpE,EAAE;AAEH,KAAI,eAAe,aAAa,0BAA0B,MAAM;AAC9D,cACE,aACA,EAAE,WAAW,yCAAyC,QAChD;AACJ,gBAAa,SAAS,6BAA6B;IAEtD;AACD,eAAa,4BAA4B;;;AAI7C,SAAS,kBACP,MACA,MACS;AACT,MAAK,MAAM,CAAC,UAAU,SAAS,KAAK,SAAS,EAAE;EAC7C,MAAM,OAAO,KAAK,IAAI,SAAS;AAC/B,MAAI,SAAS,OACX;AAGF,MAAI,kBAAkB,MAAM,KAAK,CAC/B,QAAO;;AAGX,QAAO;;AAIT,SAAS,kBAAqB,MAAc,MAAuB;AACjE,MAAK,MAAM,MAAM,KACf,KAAI,KAAK,IAAI,GAAG,CACd,QAAO;AAGX,QAAO"}