# Bug 3243: Fix `Query.run()` to wait for complete result

- Status: closed
- Project: Zero
- Creator: @aboodman
- Labels: zero
- Created: 2024-12-15T23:39:41Z
- Modified: 2025-04-25T11:57:57Z
- Reactions: ➕️ ×4 (Jonovono, bitsoflogic, lukeshay, miguelrk) · 👀 ×6 (AceroM, Jonovono, citriqa, jonlambert, klaemo, miguelrk) · 👍️ ×2 (cloleb, miguelrk)
- URL: https://bugs.rocicorp.dev/p/zero/issue/3243

## Description

Now that we have resultType, I *think* this involves just waiting for `resultType` to go to `'complete'`.

But I'm not sure exactly what the API should be. Should you be able to choose whether you want authoritative or optimistic results? Should it return both somehow? Also how should the bindings layer look?

Perhaps it makes sense to actually remove this `run()` method and always implement this feature only in the bindings? I could imagine, in React a `useQueryOnce()` thing that just delegates to `useQuery()`.

Also when I made this API I was struggling with naming, but since I have settled into calling it `runOnce()`. So I think at the React layer it should be called something like `useQueryOnce()`.

Until this is implemented users can workaround by doing something like:

```ts
const view = z.query.table.where(...).materialize();
view.addListener((result, resultType) => {
  if (resultType === 'complete') {
    // query is done
    view.destroy();
  }
});
```

User requests:

* https://discord.com/channels/830183651022471199/1288232858795769917/1331013664966312036

## Comments (6)

### @jorroll — 2025-01-08T04:53:29Z

Worth noting that it's useful to have the ability to *either* run a query against the in-memory data synchronously *or* run a query and await the authoritative results. I'm evaluation Zero for use in an application which is already running in production, Comms. We've found that there are times when being able to execute a query synchronously is helpful and other times when stale data is acceptable, both of which benefit from being able to synchronously query the in-memory cache.

When calling `getRecord` in Comms, we support the following fetch strategies. Note that we currently use both an in-memory sqlite database running in each tab, along with a shared persisted sqlite database running in a worker. Both have the same schema.

```ts
/**
 * The strategy to use when loading records or queries. Regardless of the strategy, we will end up
 * loading the record from the persisted database into the in-memory database.
 */
export type FetchStrategy =
  /**
   * Load from the server regardless of how up-to-date the local cache appears to be.
   * The query will not resolve until the server responds. If offline, the query will reject.
   */
  | "server"

  /**
   * Load from the server unless we have an active subscription for the requested query (in
   * which case trust that the local cache is up-to-date). If offline, the query will resolve
   * with any cached data (and will resolve even if there is no cached data).
   */
  | "server-first"

  /**
   * Attempt to load from the in memory cache first. If any data is found, the query will resolve
   * immediately. Else attempt to load from the persisted cache. If any data is found, the query will resolve
   * and the server will not be queried. If no data is found, the query will load from the server. If offline,
   * the query will resolve with any cached data (and will resolve even if there is no cached data).
   */
  | "cache-first"

  /**
   * Loads data from the local cache and then resolves regardless of what the cache contains.
   * Then, in the background fresh data is loaded from the server unless we have an active
   * subscription to the query. For "get" requests this is functionality equivalent to the "cache" fetch strategy, but for subscriptions the fetch strategies are different.
   */
  | "cache-and-server"

  /**
   * Load data from the persisted database into in-memory database. Will not load anything from
   * the server.
   */
  | "cache";
```

Reactions: ➕️ ×2 (lukeshay, malakhov-dmitrii)

### @aboodman — 2025-04-12T02:59:01Z

I think we should just add `resultType` parameter:

```ts
z.query.messages.run({resultType: "complete"})
```

This would make Zero wait for a `complete` (i.e., server) result type.

### @aboodman — 2025-04-12T03:00:09Z

By default the `resultType` will be `partial`, same as behavior today.

### @aboodman — 2025-04-12T03:00:32Z

Oh, and I guess this means that the result type can be either `T` or `Promise<T>` depending on `resultType`.

### @arv — 2025-04-15T11:50:19Z

@jorroll For more advanced cases I want to expose more primitives to cover more case.

https://github.com/rocicorp/mono/pull/4226

### @arv — 2025-04-16T09:46:48Z

> Oh, and I guess this means that the result type can be either T or Promise<T> depending on resultType.

Yes and no. We also have `run` inside custom mutators and on the server. On the server we always get complete results and these are always async, so even if you pass in `{type: 'unknown'}` you will get complete results async.

So it is better to keep this async which allows a more consistent model.
