Skip to content

feat: new query syntax #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 72 commits into
base: main
Choose a base branch
from
Open

feat: new query syntax #185

wants to merge 72 commits into from

Conversation

samwillis
Copy link
Collaborator

@samwillis samwillis commented Jun 18, 2025

Implements the new query syntax from #170

todo:

  • new intermedia representation
  • query builder that outputs the intermedia representation
  • compiler that compiles the ir to a D2 pipeline
  • test - and it works...

features:

  • from collection
  • from subquery
  • where
  • join collection
  • join subquery
  • groupBy
  • having
  • orderBy (with limit+offset)
  • return type of a query fully derived from query
  • React useLiveQuery
  • Vue useLiveQuery

The main entry point is a new createLiveQueryCollection function:

createLiveQueryCollection((q) =>
  q.from({ user: usersCollection }).select(({ user }) => ({
    id: user.id,
    name: user.name,
    age: user.age,
    email: user.email,
    active: user.active,
  }))
)

this wraps a new liveQueryCollectionOptions and createCollection call - the former of which can be used like the other xxxCollectionOptions function we have:

createCollection(liveQueryCollectionOptions({
  id: 'name-for-collection`,
  query: (q) =>
    q.from({ user: usersCollection }).select(({ user }) => ({
      id: user.id,
      name: user.name,
      age: user.age,
      email: user.email,
      active: user.active,
    })),
  // any other options
}))

createLiveQueryCollection can be called with either a query builder function, or a config object that takes the query param as a query builder function.

Copy link

changeset-bot bot commented Jun 18, 2025

⚠️ No Changeset found

Latest commit: c200ea8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

github-actions bot commented Jun 18, 2025

Size Change: +669 B (+2.31%)

Total Size: 29.7 kB

Filename Size Change
./packages/db/dist/esm/collection.js 7.69 kB -178 B (-2.26%)
./packages/db/dist/esm/index.js 569 B +137 B (+31.71%) 🚨
./packages/db/dist/esm/query/compiled-query.js 0 B -1.49 kB (removed) 🏆
./packages/db/dist/esm/query/evaluators.js 0 B -1.06 kB (removed) 🏆
./packages/db/dist/esm/query/extractors.js 0 B -870 B (removed) 🏆
./packages/db/dist/esm/query/functions.js 0 B -1.28 kB (removed) 🏆
./packages/db/dist/esm/query/group-by.js 0 B -976 B (removed) 🏆
./packages/db/dist/esm/query/joins.js 0 B -1.14 kB (removed) 🏆
./packages/db/dist/esm/query/order-by.js 0 B -1.42 kB (removed) 🏆
./packages/db/dist/esm/query/pipeline-compiler.js 0 B -878 B (removed) 🏆
./packages/db/dist/esm/query/query-builder.js 0 B -2.14 kB (removed) 🏆
./packages/db/dist/esm/query/select.js 0 B -1.1 kB (removed) 🏆
./packages/db/dist/esm/query/utils.js 0 B -1.13 kB (removed) 🏆
./packages/db/dist/esm/utils.js 0 B -219 B (removed) 🏆
./packages/db/dist/esm/query/builder/functions.js 523 B +523 B (new file) 🆕
./packages/db/dist/esm/query/builder/index.js 3.19 kB +3.19 kB (new file) 🆕
./packages/db/dist/esm/query/builder/ref-proxy.js 851 B +851 B (new file) 🆕
./packages/db/dist/esm/query/compiler/evaluators.js 1.34 kB +1.34 kB (new file) 🆕
./packages/db/dist/esm/query/compiler/group-by.js 2.08 kB +2.08 kB (new file) 🆕
./packages/db/dist/esm/query/compiler/index.js 1.4 kB +1.4 kB (new file) 🆕
./packages/db/dist/esm/query/compiler/joins.js 1.16 kB +1.16 kB (new file) 🆕
./packages/db/dist/esm/query/compiler/order-by.js 933 B +933 B (new file) 🆕
./packages/db/dist/esm/query/compiler/select.js 657 B +657 B (new file) 🆕
./packages/db/dist/esm/query/ir.js 310 B +310 B (new file) 🆕
./packages/db/dist/esm/query/live-query-collection.js 1.98 kB +1.98 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/deferred.js 230 B
./packages/db/dist/esm/errors.js 150 B
./packages/db/dist/esm/optimistic-action.js 294 B
./packages/db/dist/esm/proxy.js 3.75 kB
./packages/db/dist/esm/SortedMap.js 1.24 kB
./packages/db/dist/esm/transactions.js 1.33 kB

compressed-size-action::db-package-size

Copy link
Contributor

github-actions bot commented Jun 18, 2025

Size Change: 0 B

Total Size: 561 B

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 152 B
./packages/react-db/dist/esm/useLiveQuery.js 409 B

compressed-size-action::react-db-package-size

Copy link

pkg-pr-new bot commented Jun 18, 2025

@tanstack/db-example-react-todo

npm i https://pkg.pr.new/@tanstack/db@185
npm i https://pkg.pr.new/@tanstack/db-collections@185
npm i https://pkg.pr.new/@tanstack/react-db@185
npm i https://pkg.pr.new/@tanstack/vue-db@185

commit: c200ea8

@samwillis
Copy link
Collaborator Author

@KyleAMathews
Copy link
Collaborator

Installed this in the expo starter which has this for the query: const { data: todos } = useLiveQuery((q) => q.from({ todoCollection }));

There's no loader so the initial render has 0 items but it never re-renders after that. I called todoCollection.subscribeChanges and it does send in the inserts.

@KyleAMathews
Copy link
Collaborator

KyleAMathews commented Jun 27, 2025

An optimistic/sync mutation does get it to re-render

@KyleAMathews
Copy link
Collaborator

I tried doing a select callback and got this js error for foo:

    q.from({ todoCollection }).select(({ todoCollection }) => {
      return {
        foo: `${todoCollection.id}`,
        id: todoCollection.id,
        text: todoCollection.text,
        completed: todoCollection.completed,
        created_at: todoCollection.created_at,
        updated_at: todoCollection.updated_at,
      };
    }),

Uncaught TypeError: Cannot convert object to primitive value

@KyleAMathews
Copy link
Collaborator

For the main operators — it'd be great to have an inline example or two to remind people of the syntax
Screenshot 2025-06-27 at 9 27 18 AM

@KyleAMathews
Copy link
Collaborator

I'm not sure I love the operators being individual exports (e.g. import { eq } from "@tanstack/react-db" as that makes them less discoverable e.g. right now I'm testing them & I need to look through the PR to get the full list. Maybe just operators so you can either do import { operators as $ } from "@tanstack/db" or import { operators: { eq }} from "@tanstack/db" ?

@KyleAMathews
Copy link
Collaborator

I tried doing a select callback and got this js error for foo:

Ohhhh that's because this isn't supported yet... just the operator selects... hmm that's going to confuse people

@KyleAMathews
Copy link
Collaborator

Another potentially annoying part about individual operators is that the autoimport tool suggests both drizzle & react-db 😅

Screenshot 2025-06-27 at 10 20 39 AM

@KyleAMathews
Copy link
Collaborator

isInFunc is a bit odd... inArray & notInArray is better perhaps like Drizzle https://orm.drizzle.team/docs/operators

@KyleAMathews
Copy link
Collaborator

I've changed my mind about putting operators under operator — there's not that many & w/ docs they'll be plenty discoverable 👍

@KyleAMathews
Copy link
Collaborator

concat doesn't seem to work?

I wrote this code: foo: concat([todoCollection.text, '-', todoCollection.completed]) in my select and got this output when rendering: [{},"-",{}] FOO3

Oh actually the test is misleading I guess? Removing the array made it work i.e. concat(todoCollection.text, '-', todoCollection.completed)

@samwillis
Copy link
Collaborator Author

I tried doing a select callback and got this js error for foo:

Ohhhh that's because this isn't supported yet... just the operator selects... hmm that's going to confuse people

It's planned, as I noted above and on Discord, it will come in another PR asap. We need to decide how to name them - they have to be distinct from the main method as they now take callbacks too.

@samwillis
Copy link
Collaborator Author

I'm not sure I love the operators being individual exports (e.g. import { eq } from "@tanstack/react-db" as that makes them less discoverable e.g. right now I'm testing them & I need to look through the PR to get the full list. Maybe just operators so you can either do import { operators as $ } from "@tanstack/db" or import { operators: { eq }} from "@tanstack/db" ?

There was a discussion about this over on the issue here: #170 (comment)

Happy to change this, we didn't really reach a conclusion. They could be an import, or provided for destructuring on the useLiveQuery callback.

@samwillis
Copy link
Collaborator Author

isInFunc

I thought I had it as isIn and aliased as in... clearly I messed something up.

@samwillis
Copy link
Collaborator Author

Oh actually the test is misleading I guess? Removing the array made it work i.e. concat(todoCollection.text, '-', todoCollection.completed)

Yes this is the correct syntax

@samwillis
Copy link
Collaborator Author

For the main operators — it'd be great to have an inline example or two to remind people of the syntax Screenshot 2025-06-27 at 9 27 18 AM

I'de love to, I need to find a way to stop the type being so verbose though as I think it will push any jsdoc down in the prompt... 🤔

@samwillis
Copy link
Collaborator Author

There's no loader so the initial render has 0 items but it never re-renders after that. I called todoCollection.subscribeChanges and it does send in the inserts.

Is that with a shape that is now lazy? I think thats behaving as it should as there are no rows yet?

@KyleAMathews
Copy link
Collaborator

I need to find a way to stop the type being so verbose though

Explicit named types is the trick I think?

Is that with a shape that is now lazy? I think thats behaving as it should as there are no rows yet?

No the collection loads data just fine — the query just doesn't seem to detect the initial sync. Subsequent syncs & any optimistic changes do trigger a render.

@samwillis
Copy link
Collaborator Author

I need to find a way to stop the type being so verbose though

Explicit named types is the trick I think?

I've tried to make this work, but failed. No patter what I do it always shows the context in the generic in the prompt.

I have added jsdoc comments with examples, so thats an improvement. I think we can come back to trying to minimise the blob at the top later (and maybe a TS expert will come and work it out for us!)

image

@samwillis
Copy link
Collaborator Author

@KyleAMathews on isIn, it was mistakingly aliased in the test isInFunc, it is just isIn.

I'm open to renaming it, but not that it operates on both arrays and strings, so you can do isIn('string', 'isIn works on both arrays and strings')

Obv we can't call it in as thats a reserved word in JS. The another option is contains and flip the arguments around?

We could also split into two functions inArray and inString?

@DawidWraga
Copy link

DawidWraga commented Jun 28, 2025

I need to find a way to stop the type being so verbose though

Explicit named types is the trick I think?

I've tried to make this work, but failed. No patter what I do it always shows the context in the generic in the prompt.

I had a similar problem a while ago, this might be helpful

https://dev.to/dawidcodes/simplifying-complex-type-display-in-typescript-and-vs-code-4f1

@KyleAMathews
Copy link
Collaborator

Ah ok, maybe inArray and includes? It is a bit confusing to have both like and another string operator. It seems we could just have like and inArray?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

4 participants