{"version":3,"file":"live-query-window-binding.cjs","sources":["../../src/live-query-window-binding.ts"],"sourcesContent":["import { codedMessage, devBuild } from './error-message.js'\nimport { UnhashableQueryIRError } from './errors.js'\nimport { getLiveQueryHash } from './live-query-options.js'\nimport { isFailedStatus } from './live-query-primitives.js'\nimport { createLiveQueryCollection } from './query/live-query-collection.js'\nimport {\n  assertLiveQueryWindowManyResult,\n  compareLiveQueryWindowDependencies,\n  createLiveQueryWindowController,\n  getLiveQueryWindowCollectionWarning,\n  getLiveQueryWindowInputKind,\n  liveQueryWindowMatches,\n  normalizeLiveQueryWindowPageSize,\n  resolveLiveQueryWindowInput,\n  shouldPreserveLiveQueryWindowPageCount,\n} from './live-query-window-controller.js'\nimport type { Collection } from './collection/index.js'\nimport type { DbClient } from './client.js'\nimport type { LiveQueryKey } from './live-query-options.js'\nimport type { LiveQueryWindowController } from './live-query-window-controller.js'\n\n/**\n * Window binding: what one mounted infinite-query hook keeps between updates.\n *\n * Every framework adapter's `useLiveInfiniteQuery` reaches the same decision\n * on each render or reactive rerun. It keeps the binding when nothing changed,\n * replaces only the window controller when the page shape changed, or binds a\n * new window collection when the input changed. A new controller keeps the\n * loaded page count when the old pages still describe the new query.\n * Adapters own only their framework's subscription and disposal of\n * `controller`.\n */\n\n// Live queries created here are cleaned up immediately (0 disables GC).\nconst WINDOW_COLLECTION_GC_TIME_MS = 1\n\n/** Validated infinite-query page shape. */\nexport interface LiveQueryWindowPageShape {\n  pageSize: number\n  /** First result-page label, not a server cursor or remote offset. */\n  initialPageParam: number\n}\n\n/**\n * Read an infinite-query config's page shape. `getNextPageParam` is rejected:\n * the hook widens an ordered live query and has no server cursor to advance.\n */\nexport function readLiveQueryWindowPageShape(config: {\n  pageSize?: number\n  initialPageParam?: number\n}): LiveQueryWindowPageShape {\n  if (`getNextPageParam` in config) {\n    throw new Error(\n      devBuild() && process.env.NODE_ENV !== `production`\n        ? `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`\n        : codedMessage(246),\n    )\n  }\n  return {\n    pageSize: normalizeLiveQueryWindowPageSize(config.pageSize),\n    initialPageParam: config.initialPageParam ?? 0,\n  }\n}\n\n/**\n * When a query input compiles again.\n *\n * - `identity`: when its identity dependencies change by structure, as for a\n *   derived query identity or a `queryKey`.\n * - `always`: on every update, as for a reactive rerun, which happens only\n *   when something the query reads changed.\n */\nexport type LiveQueryWindowRecompile = `identity` | `always`\n\nexport interface LiveQueryWindowUpdate extends LiveQueryWindowPageShape {\n  /**\n   * A supplied live-query Collection, or a callback that returns the query.\n   * The binding calls the callback only when it compiles a new window\n   * collection, except in `always` mode, where it calls it on every update.\n   */\n  input: unknown\n  /** The query input's identity dependencies. */\n  dependencies?: ReadonlyArray<unknown>\n  /** Context compared by reference, such as the `DbClient` a query binds to. */\n  scope?: unknown\n  /** Hydration identity for a client-backed window. */\n  client?: DbClient\n  queryKey?: LiveQueryKey\n  recompile: LiveQueryWindowRecompile\n}\n\nexport interface LiveQueryWindowBinding extends LiveQueryWindowPageShape {\n  /** The supplied Collection; `undefined` for a query input. */\n  supplied: Collection<any, any, any> | undefined\n  dependencies: ReadonlyArray<unknown> | undefined\n  scope: unknown\n  collection: Collection<any, any, any>\n  /** The transport identity of this binding's initial query window. */\n  queryHash: string | undefined\n  /** Whether this binding created `collection` from a query. */\n  ownsCollection: boolean\n  controller: LiveQueryWindowController<any, any>\n  /**\n   * A developer hint about a newly bound supplied collection's window, for the\n   * adapter to print once. Production builds carry none.\n   */\n  warning: string | undefined\n}\n\n/**\n * Bind an infinite-query update. Returns `previous` when it still serves the\n * update; otherwise returns a new binding whose controller the adapter\n * subscribes and later disposes.\n *\n * `uncommitted` is a binding built by an earlier update that never took\n * effect, such as a React render that did not commit. When it answers this\n * update, its window collection is reused instead of compiling another.\n */\nexport function bindLiveQueryWindow(\n  previous: LiveQueryWindowBinding | null,\n  update: LiveQueryWindowUpdate,\n  uncommitted?: LiveQueryWindowBinding | null,\n): LiveQueryWindowBinding {\n  // A reactive rerun resolves its input first: a getter may return a\n  // supplied Collection.\n  const resolved =\n    update.recompile === `always`\n      ? resolveLiveQueryWindowInput<any>(update.input)\n      : undefined\n  const supplied =\n    resolved === undefined\n      ? getLiveQueryWindowInputKind(update.input) === `collection`\n        ? (update.input as Collection<any, any, any>)\n        : undefined\n      : resolved.kind === `collection`\n        ? resolved.collection\n        : undefined\n  const inputKind = supplied ? `collection` : `query`\n  const previousKind = previous\n    ? previous.supplied\n      ? `collection`\n      : `query`\n    : undefined\n  // A snapshot: a caller may change its dependency array in place.\n  const dependencies = supplied ? undefined : [...(update.dependencies ?? [])]\n\n  const change = compareUpdate(previous, update, dependencies)\n  const needsNewCollection =\n    previous === null ||\n    previousKind !== inputKind ||\n    (supplied ? previous.supplied !== supplied : change.recompile)\n  const pageShapeChanged =\n    previous === null ||\n    previous.pageSize !== update.pageSize ||\n    previous.initialPageParam !== update.initialPageParam\n  if (!needsNewCollection && !pageShapeChanged) return previous\n\n  const initialPageCount = shouldPreserveLiveQueryWindowPageCount({\n    hasPreviousController: previous !== null,\n    previousInputKind: previousKind,\n    inputKind,\n    sameCollection: supplied !== undefined && previous?.supplied === supplied,\n    dependenciesChanged: change.dependenciesChanged,\n    dependenciesStructurallyEqual: change.dependenciesStructurallyEqual,\n    pageShapeChanged,\n  })\n    ? Math.max(1, previous!.controller.getSnapshot().pages.length)\n    : 1\n  // The peek-ahead window for every retained page.\n  const requiredLimit = initialPageCount * update.pageSize + 1\n\n  let collection = previous?.collection\n  let queryHash = previous?.queryHash\n  let ownsCollection = previous?.ownsCollection ?? false\n  let warning: string | undefined\n  if (needsNewCollection) {\n    if (\n      !supplied &&\n      uncommitted?.ownsCollection &&\n      !compareUpdate(uncommitted, update, dependencies).recompile &&\n      uncommitted.collection.status !== `cleaned-up` &&\n      !isFailedStatus(uncommitted.collection.status) &&\n      liveQueryWindowMatches(uncommitted.collection, requiredLimit)\n    ) {\n      // Nothing has taken effect since the earlier update built this\n      // collection, and its window is exactly the retained pages, so its first\n      // rows are correct. A collection in terminal error or crashed, such as\n      // after its source restarted, cannot serve the query again.\n      collection = uncommitted.collection\n      queryHash = uncommitted.queryHash\n      ownsCollection = true\n    } else {\n      const window = openWindowCollection(\n        resolved ?? resolveLiveQueryWindowInput<any>(update.input),\n        requiredLimit,\n        update,\n      )\n      collection = window.collection\n      queryHash = window.queryHash\n      ownsCollection = window.ownsCollection\n      warning = window.warning\n    }\n  }\n\n  return {\n    supplied,\n    dependencies,\n    scope: update.scope,\n    pageSize: update.pageSize,\n    initialPageParam: update.initialPageParam,\n    collection: collection!,\n    queryHash,\n    ownsCollection,\n    controller: createLiveQueryWindowController(collection, {\n      pageSize: update.pageSize,\n      initialPageParam: update.initialPageParam,\n      initialPageCount,\n      client: update.client,\n      queryHash,\n    }),\n    warning,\n  }\n}\n\n/** How an update's query identity differs from a binding's. */\nfunction compareUpdate(\n  binding: LiveQueryWindowBinding | null,\n  update: LiveQueryWindowUpdate,\n  dependencies: ReadonlyArray<unknown> | undefined,\n): {\n  recompile: boolean\n  dependenciesChanged: boolean\n  dependenciesStructurallyEqual: boolean\n} {\n  const comparison = compareLiveQueryWindowDependencies(\n    binding?.dependencies,\n    dependencies ?? [],\n  )\n  const scopeChanged = binding?.scope !== update.scope\n  const dependenciesChanged =\n    scopeChanged ||\n    (update.recompile === `identity`\n      ? !comparison.structurallyEqual\n      : comparison.changed)\n  return {\n    recompile: update.recompile === `always` || dependenciesChanged,\n    dependenciesChanged,\n    // An identity that changed differs by structure by definition.\n    dependenciesStructurallyEqual:\n      !scopeChanged &&\n      update.recompile !== `identity` &&\n      comparison.structurallyEqual,\n  }\n}\n\n/**\n * Bind the window collection for a resolved input, starting its sync now when\n * its first published rows are already correct. A created collection's window\n * covers every retained page, so it never publishes fewer rows than the\n * controller's pages; the controller grows it with `setWindow`. A supplied\n * window that the controller must still adjust waits for the subscription.\n */\nfunction openWindowCollection(\n  input: ReturnType<typeof resolveLiveQueryWindowInput<any>>,\n  requiredLimit: number,\n  update: LiveQueryWindowUpdate,\n): {\n  collection: Collection<any, any, any>\n  queryHash: string | undefined\n  ownsCollection: boolean\n  warning: string | undefined\n} {\n  const prepared =\n    input.kind === `collection`\n      ? input.collection\n      : { query: input.query.limit(requiredLimit).offset(0) }\n  let queryHash: string | undefined\n  if (update.client) {\n    try {\n      queryHash = getLiveQueryHash(prepared, update.queryKey)\n    } catch (error) {\n      if (!(error instanceof UnhashableQueryIRError) || update.queryKey?.length)\n        throw error\n    }\n  }\n  if (input.kind === `collection`) {\n    const warning = getLiveQueryWindowCollectionWarning(\n      input.collection,\n      update.pageSize + 1,\n    )\n    if (liveQueryWindowMatches(input.collection, requiredLimit)) {\n      input.collection.startSyncImmediate()\n    }\n    return {\n      collection: input.collection,\n      queryHash,\n      ownsCollection: false,\n      warning,\n    }\n  }\n  const collection = createLiveQueryCollection({\n    query: (prepared as { query: typeof input.query }).query,\n    gcTime: WINDOW_COLLECTION_GC_TIME_MS,\n  })\n  assertLiveQueryWindowManyResult(collection)\n  // Like useLiveQuery, start sync during render once the query is valid. A\n  // render reads only rows the sources already hold, and sends no request\n  // until the subscription attaches.\n  collection.startSyncImmediate()\n  return { collection, queryHash, ownsCollection: true, warning: undefined }\n}\n"],"names":["devBuild","codedMessage","normalizeLiveQueryWindowPageSize","resolveLiveQueryWindowInput","getLiveQueryWindowInputKind","shouldPreserveLiveQueryWindowPageCount","isFailedStatus","liveQueryWindowMatches","createLiveQueryWindowController","compareLiveQueryWindowDependencies","getLiveQueryHash","UnhashableQueryIRError","getLiveQueryWindowCollectionWarning","createLiveQueryCollection","assertLiveQueryWindowManyResult"],"mappings":";;;;;;;;AAkCA,MAAM,+BAA+B;AAa9B,SAAS,6BAA6B,QAGhB;AAC3B,MAAI,sBAAsB,QAAQ;AAChC,UAAM,IAAI;AAAA,MACRA,sBAAA,KAAc,QAAQ,IAAI,aAAa,eACnC,gKACAC,aAAAA,aAAa,GAAG;AAAA,IAAA;AAAA,EAExB;AACA,SAAO;AAAA,IACL,UAAUC,0BAAAA,iCAAiC,OAAO,QAAQ;AAAA,IAC1D,kBAAkB,OAAO,oBAAoB;AAAA,EAAA;AAEjD;AAwDO,SAAS,oBACd,UACA,QACA,aACwB;AAGxB,QAAM,WACJ,OAAO,cAAc,WACjBC,sDAAiC,OAAO,KAAK,IAC7C;AACN,QAAM,WACJ,aAAa,SACTC,0BAAAA,4BAA4B,OAAO,KAAK,MAAM,eAC3C,OAAO,QACR,SACF,SAAS,SAAS,eAChB,SAAS,aACT;AACR,QAAM,YAAY,WAAW,eAAe;AAC5C,QAAM,eAAe,WACjB,SAAS,WACP,eACA,UACF;AAEJ,QAAM,eAAe,WAAW,SAAY,CAAC,GAAI,OAAO,gBAAgB,EAAG;AAE3E,QAAM,SAAS,cAAc,UAAU,QAAQ,YAAY;AAC3D,QAAM,qBACJ,aAAa,QACb,iBAAiB,cAChB,WAAW,SAAS,aAAa,WAAW,OAAO;AACtD,QAAM,mBACJ,aAAa,QACb,SAAS,aAAa,OAAO,YAC7B,SAAS,qBAAqB,OAAO;AACvC,MAAI,CAAC,sBAAsB,CAAC,iBAAkB,QAAO;AAErD,QAAM,mBAAmBC,0BAAAA,uCAAuC;AAAA,IAC9D,uBAAuB,aAAa;AAAA,IACpC,mBAAmB;AAAA,IACnB;AAAA,IACA,gBAAgB,aAAa,UAAa,UAAU,aAAa;AAAA,IACjE,qBAAqB,OAAO;AAAA,IAC5B,+BAA+B,OAAO;AAAA,IACtC;AAAA,EAAA,CACD,IACG,KAAK,IAAI,GAAG,SAAU,WAAW,cAAc,MAAM,MAAM,IAC3D;AAEJ,QAAM,gBAAgB,mBAAmB,OAAO,WAAW;AAE3D,MAAI,aAAa,UAAU;AAC3B,MAAI,YAAY,UAAU;AAC1B,MAAI,iBAAiB,UAAU,kBAAkB;AACjD,MAAI;AACJ,MAAI,oBAAoB;AACtB,QACE,CAAC,YACD,aAAa,kBACb,CAAC,cAAc,aAAa,QAAQ,YAAY,EAAE,aAClD,YAAY,WAAW,WAAW,gBAClC,CAACC,oBAAAA,eAAe,YAAY,WAAW,MAAM,KAC7CC,iDAAuB,YAAY,YAAY,aAAa,GAC5D;AAKA,mBAAa,YAAY;AACzB,kBAAY,YAAY;AACxB,uBAAiB;AAAA,IACnB,OAAO;AACL,YAAM,SAAS;AAAA,QACb,YAAYJ,0BAAAA,4BAAiC,OAAO,KAAK;AAAA,QACzD;AAAA,QACA;AAAA,MAAA;AAEF,mBAAa,OAAO;AACpB,kBAAY,OAAO;AACnB,uBAAiB,OAAO;AACxB,gBAAU,OAAO;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,OAAO;AAAA,IACd,UAAU,OAAO;AAAA,IACjB,kBAAkB,OAAO;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAYK,0BAAAA,gCAAgC,YAAY;AAAA,MACtD,UAAU,OAAO;AAAA,MACjB,kBAAkB,OAAO;AAAA,MACzB;AAAA,MACA,QAAQ,OAAO;AAAA,MACf;AAAA,IAAA,CACD;AAAA,IACD;AAAA,EAAA;AAEJ;AAGA,SAAS,cACP,SACA,QACA,cAKA;AACA,QAAM,aAAaC,0BAAAA;AAAAA,IACjB,SAAS;AAAA,IACT,gBAAgB,CAAA;AAAA,EAAC;AAEnB,QAAM,eAAe,SAAS,UAAU,OAAO;AAC/C,QAAM,sBACJ,iBACC,OAAO,cAAc,aAClB,CAAC,WAAW,oBACZ,WAAW;AACjB,SAAO;AAAA,IACL,WAAW,OAAO,cAAc,YAAY;AAAA,IAC5C;AAAA;AAAA,IAEA,+BACE,CAAC,gBACD,OAAO,cAAc,cACrB,WAAW;AAAA,EAAA;AAEjB;AASA,SAAS,qBACP,OACA,eACA,QAMA;AACA,QAAM,WACJ,MAAM,SAAS,eACX,MAAM,aACN,EAAE,OAAO,MAAM,MAAM,MAAM,aAAa,EAAE,OAAO,CAAC,EAAA;AACxD,MAAI;AACJ,MAAI,OAAO,QAAQ;AACjB,QAAI;AACF,kBAAYC,iBAAAA,iBAAiB,UAAU,OAAO,QAAQ;AAAA,IACxD,SAAS,OAAO;AACd,UAAI,EAAE,iBAAiBC,OAAAA,2BAA2B,OAAO,UAAU;AACjE,cAAM;AAAA,IACV;AAAA,EACF;AACA,MAAI,MAAM,SAAS,cAAc;AAC/B,UAAM,UAAUC,0BAAAA;AAAAA,MACd,MAAM;AAAA,MACN,OAAO,WAAW;AAAA,IAAA;AAEpB,QAAIL,iDAAuB,MAAM,YAAY,aAAa,GAAG;AAC3D,YAAM,WAAW,mBAAA;AAAA,IACnB;AACA,WAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB;AAAA,MACA,gBAAgB;AAAA,MAChB;AAAA,IAAA;AAAA,EAEJ;AACA,QAAM,aAAaM,oBAAAA,0BAA0B;AAAA,IAC3C,OAAQ,SAA2C;AAAA,IACnD,QAAQ;AAAA,EAAA,CACT;AACDC,4BAAAA,gCAAgC,UAAU;AAI1C,aAAW,mBAAA;AACX,SAAO,EAAE,YAAY,WAAW,gBAAgB,MAAM,SAAS,OAAA;AACjE;;;"}