Giter Site home page Giter Site logo

gqless's Introduction

Hi there πŸ‘‹ welcome to my profile :)

gqless's People

Contributors

0xflotus avatar cristianbote avatar dependabot-preview[bot] avatar dependabot[bot] avatar github-actions[bot] avatar karaggeorge avatar lukejagodzinski avatar monkeywithacupcake avatar natew avatar pabloszx avatar samdenty avatar yomanz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gqless's Issues

Polling without throwing promise

Hey, I've modified hackernews example to use polling every 5 seconds https://codesandbox.io/s/gqless-polling-y8k50

The effect is that the Loading... indicator is being displayed every 5 seconds while it's reloading data from the server. Is it possible to just serve data from the cache while it's loading and then just update it data and rerender component? I guess it would require not throwing promise fetching data and just invalidating components that require this data after updating cache.

Quick start not working: "globby not found"

I followed the quickstart in the docs. When trying to run the script to generate the code, I got an error:

(node:85825) [MODULE_NOT_FOUND] Error Plugin: @gqless/cli: Cannot find module 'globby'
module: @oclif/[email protected]
task: not loading commands, globby not found
plugin: @gqless/cli

I was able to work around by running npm install globby, but it seems like this should be a dependency of the package itself.

"@gqless/cli": "0.0.1-alpha.30"
"gqless": "0.0.1-alpha.28"

Implicit any when schema has loops

When a schema loops typescript falls back to any.

eg

type User {
  id: ID
  friends: [User!]!
}

TS7023: 'User' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.

Authentication for graphql schema

My graphql endpoint (using FuanaDB) requires authentication via a header. It would be nice if there was support for providing headers that are passed on to the endpoint, both on the querying side and the generation cli.

Workaround: (at least for generating)

Setting up a temporary proxy to add the header to the request and using that to generate the schema

I'm probably going to work on a PR to add this in the next few days if it sounds reasonable

Local schema

πŸ‘‹

I think it'd be worthwhile to be able to point the config file to a local schema file instead of a remote endpoint, essentially using gqless to manage local state, in a way. This way you can decide later to sync with a remote GraphQL endpoint. What do you think of such an idea?

Multiple endpoint workflow

Lots of GraphQL React clients do not provide a first-class way of using multiple GraphQL endpoints.

There are even opinions that it's against the GraphQL way and one should always aim towards a singular endpoint either by using stitching or gateways or whatnot.

But, there are usually problems with these approaches.
For instance, Apollo first came on board with schema-stitching, now having dropped it in favor of Federation. Cool! But not cool when one is looking to combine a 3rd party GraphQL endpoint with a managed one, where the 3rd party may not be Federation compliant.

I will be honest - I haven't done the homework of using this library yet and checking if that's already possible, though, skimming through the docs, I see no mention of it. Also, viewing the React examples, it hints to me, that it's impossible at this time.

I see how it could be used outside React, that's also the case for all the other clients - outside React, everything is gucci. Though, in React, because of the client usually being provided via a singular Provider, that's causing headaches.

Not to mention the plethora of problems when one is looking to integrate TypeScript and type-checking for multiple endpoints.

So, is it currently possible to work with multiple clients? If not, how about adopting such usage as first-class for this library?

Bug: Produces invalid query when trying to access same node with different args > twice

I've created a reproduction to help make the bug clear: https://gqless-merged-queries-bug.now.sh/ (source code)

If you increase "Number of pages" by 1 or 2 it's fine. For example, if you increase the number from 1 to 3, you can see gqless produces a query like this:

{
  allPosts__1:allPosts(first:5,skip:5){title},
  allPosts(first:5,skip:10){title}}
}

But if you increase "Number of pages" by more than 2, gqless produces an invalid query which is rejected by the graphql server. For example, if you increase the number from 3 to 6, you can see gqless produces a query like this:

{
  allPosts__2:allPosts(first:5,skip:15){title},
  allPosts(first:5,skip:20){title},
  allPosts(first:5,skip:25){title}
}

Which gets the obvious error from the graphql server:

Field 'allPosts' conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.

will cursor based connections with pollers be supported?

I've read the #43 issue, and one "issue" that I'm still having with apollo, is cursor-based pagination combined with polling. It's possible but requires a lot of boilerplate. The apollo team won't fix this (any time soon)

Closing because we don’t currently intend to make polling work with fetch more. The implementation would just be to difficult persevere

If this library provides that in a (better) way, it's definitely a unique selling point.

Having an example in the docs that shows how to implement pagination, with something like react-window, would be a killer.

Query more than 3 levels deep

Hi thanks for this library!

I'm getting an error when requesting data on "4rd level" field.

This is my graphql schema:

type Query {
  getFirstUsers(filter: String, pagination: String, where: Condition, _debug: Boolean, _cache: Boolean): Users
  getPagePosts(filter: String, pagination: String, where: Condition, _debug: Boolean, _cache: Boolean): PagePosts
}

type Mutation {
  putItemUsers(_debug: Boolean, input: InputUsers!): Users
  putItemPosts(_debug: Boolean, input: InputPosts!): Posts
}

type Users {
  id: Int
  username: String
  password: String
  firstname: String
  lastname: String
  posts(filter: String, pagination: String, where: Condition, _debug: Boolean, _cache: Boolean): PagePosts
  fullname(foo: String): String
}

type PageUsers {
  total: Int
  items: [Users]
}

type Posts {
  id: Int
  users_id: Int
  title: String
  categories_id: Int
  publish: Boolean
  users_id_users: Users
  categories_id_categories: Categories
}

type PagePosts {
  total: Int
  items: [Posts]
}

input Condition {
  sql: String!
  val: [String!]!
}

This is the javascript:

const Author = graphql(() => {
  const user = query.getFirstUsers;
  return (
    <div>
      <h1>
        {user.id}: {user.username}
      </h1>
      <PagePostsComp user={user} />
    </div>
  );
});

const PagePostsComp = graphql(({ user }) => {
  const args = {
    where: {
      sql: "publish=? and users_id=?",
      val: [String(1), String(user.id)]
    }
  };
  const items = user.posts(args).items;
  return (
    <div>
      {items.map(post =>
        <h2>
          {post.id}: {post.title}
        </h2>
      )}
    </div>
  );
});

This is the resulting ajax query:

{
  "query":
    "{ getFirstUsers { id username posts { __typename } } }"
  }
}

This is the javascript error:
image

Basically, I'm asking:
query.getFirstUsers.posts.items.title

But the Grqphql query only asks:
query.getFirstUsers.posts.__typename

I'm very new to this library so any help is appreciated. Thanks!

How to query by id?

Hi, a noob here. Sorry but I couldn't find and don't understand how to query by id.

I fetched all courses in <CourseList /> component:

...
// course_list.tsx

const Row = ({ index, style }: any) => {
  return (
    <>
      {query.courses.map((c, _) => (
        <div
          key={c.id}
          style={style}
        >
            <Link to={`/courses/${c.id}/edit`}>
              {c.id}: {c.title}
            </Link>
        </div>
      ))}
    </>
  )
}

const CourseList = graphql(() => {
  return (
      <List width={330} height={500} itemCount={50} itemSize={100}>
        {Row}
      </List>
  )
})

...

I want to fetch a course by course_id in <CourseForm /> component:

// course_form.tsx

...

type Props = {
  course_id?: number
}

const CourseForm = graphql(({ course_id }: Props) => {
  const { register, handleSubmit, errors } = useForm()
  const onSubmit = (data: any) => {
    console.log(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Flex flexDirection="column">
        <Text color="grey" mt="3">
          Teacher ID
        </Text>
        <Input
          placeholder="Teacher ID"
          name="teacher_id"
          ref={register({ required: true })}
          my="3"
          isRequired
          defaultValue={teacher_id}
        />
        {errors.teacher_id && <Text color="tomato">Teacher ID required *</Text>}
        <Text color="grey" mt="3">
          Title
        </Text>
        <Input
          placeholder="Title"
          name="title"
          ref={register({ required: true })}
          my="3"
          isRequired
          defaultValue={title}
        />
        {errors.title && <Text color="tomato">Title required *</Text>}
        <Submit />
      </Flex>
    </form>
  )
})

...

Please guide me.

Bug nullable type

This kind of error is generated in src/graphql/generated/types.ts in the latest change of nullable types.

Property 'description' is incompatible with index signature. Type 'Type<Kind.scalar, string, any> | null | undefined' is not assignable to type 'ValidType[] | Type<any, any, any> | FieldsTypeArg<any, any> | null'. Type 'undefined' is not assignable to type 'ValidType[] | Type<any, any, any> | FieldsTypeArg<any, any> | null'.

I will send a PR that fixes it

Pagination support

I really like the concept of this library and want to use it in production apps,
but it does not seem to support the pagination function.
The only clue is fetchMore in the NEW_API_EXAMPLES document, which does not seem to be implemented.

Subscriptions

Will there be subscriptions?
Does the polling practically makes subscriptions unnecessary?

Mutations

Hi! Thanks for the great package!

I was searching for information about how to perform mutations and I've just realized that it's work in progress. Any expected date of release? I would like to ditch Apollo Client :) and this library is one of the best things that happen for the client side GraphQL in a while.

Generate command not found

Following the tutorial, I only get this error

$ yarn generate
yarn run v1.19.0
$ gqless generate -u https://myserver.com/graphql ./gqless
 Β»   Error: command generate not found
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
$ gqless help
Codegen for gqless

VERSION
  @gqless/cli/0.0.1-alpha.23 win32-x64 node-v8.11.2

USAGE
  $ gqless [COMMAND]

COMMANDS
  help  display help for gqless

Done in 0.76s.

License

I couldn't find a license. gqless is under which license?

Outputting to plain javascript

I've followed the instructions in the CLI adding gqless generate --typescript=false but it just outputs the gen files into a directory called false, but still typescript.

Has this feature not been implemented yet?

Updating cache (optimistic update)

While we're waiting for the official mutations support I'm trying to use the update function to perform "optimistic update". What would be the best approach to optimistically remove one element from the list?

Let's say I have a list of users:

query.users({ teamId }).map(user => <User user={user} />);

Now in the User component I have the remove button that will perform mutation and I would like to remove user object from the cache without refetching query.

It would be easy if I want to change name.

update(query.user({ userid }).firstName, "John");

But removing values is a mystery :) especially when it's an array. Any clues? :)

Inadequate error reasoning/handling.

If you look at the other issue I've made which showcases the HTTP 404 (Bad Request) error, there isn't enough diagnostic information for us to test anything.

In-depth diagnostic information could help the end-users figure out issues without external interaction.

Suggestion about @gqless scope

I've seen in the GH projects that you're considering changing package name. Whatever decision you're gonna make I would just like to propose renaming gqless package to @gqless/core or similar. It's a good pattern to have everything under one namespace/scope so it's easier for example to upgrade all packages in batch. Similar naming is used by many packages, one example could be @material-ui/core, @material-ui/styles etc. So just a suggestion :)

Client-only field depending on query

This tool is amazing, but some of the docs are hard to parse. I'm trying to define a client-only field that runs a query and uses that result to generate the field.

It looks like I would add an export to the extensions generated directory's index.ts. For a simple example, let's use this sample graphql endpoint: https://lucasconstantino.github.io/graphiql-online/.

Say I want to add a field to the User object called nameLength that queries the name and counts the number of letters in the name. How would I define the User object to run the query on itself?

Nullable input object properties are required

This project is genius. Thanks for it.

I have a schema

    project(
        "distinct select on columns"
        distinct_on: [project_select_column!],
        "limit the number of rows returned"
        limit: Int,
        "skip the first n rows. Use only with order_by"
        offset: Int,
        "sort the rows by one or more columns"
        order_by: [project_order_by!],
        "filter the rows returned"
        where: project_bool_exp
    ): [project!]!

input project_bool_exp {
    _and: [project_bool_exp]
    _not: project_bool_exp
    _or: [project_bool_exp]
    id: Int_comparison_exp
    name: String_comparison_exp
    organization: organization_bool_exp
    organization_id: Int_comparison_exp
    slug: String_comparison_exp
}

input String_comparison_exp {
    _eq: String
    _gt: String
    _gte: String
    _ilike: String
    _in: [String!]
    _is_null: Boolean
    _like: String
    _lt: String
    _lte: String
    _neq: String
    _nilike: String
    _nin: [String!]
    _nlike: String
    _nsimilar: String
    _similar: String
}

Properties in String_comparison_exp are nullable, so it should be not required in TS.

In my TS code

query.project({ where: { slug: { _eq: slug } } })

Show error

Error:(38, 41) TS2322: Type '{ _eq: string; }' is not assignable to type '{ _eq: string | Variable<string | null> | null; _gt: string | Variable<string | null> | null; _gte: string | Variable<string | null> | null; ... 11 more ...; _similar: string | ... 1 more ... | null; } | Variable<...> | null'.
  Type '{ _eq: string; }' is missing the following properties from type '{ _eq: string | Variable<string | null> | null; _gt: string | Variable<string | null> | null; _gte: string | Variable<string | null> | null; ... 11 more ...; _similar: string | ... 1 more ... | null; }': _gt, _gte, _ilike, _in, and 10 more.

Working TS code will be

query.project({ where: { slug: { _eq: slug, _gt: null, _gte: null /*, ... all properties as null  */ } } })

Local client state

I love the Apollo feature local client state. I guess you can get part way with Extensions, but what's nice is being able to export local state into queries as variables. So you don't have to manually specify and pass around variables, don't have to endlessly pass variables through props to where you need them. And in combination with cache persist those state variables persist across page loads, and changing a variable anywhere in the app causes queries referencing them to re-fetch.

E.g. we have a calendar app and we use local state for the view (day/month/year) and the date range. Then that state is referenced by both the UI and the queries.

All that said, while I love it the Apollo implementation is crazy buggy and basically untested by them unfortunately. Also since gqless doesn't expose the queries it writes then half of the query syntax features of local state wouldn't apply. I'm not sure what the syntax for using local state as a variable in gqless would be. Finally I can see the argument that state and queries should be managed by separate tools (it's just very convenient having them in one, an all in one model layer).

Anyway, see this as a pie in the sky tentative feature suggestion that I'd totally understand not doing or wanting to do. Gqless is totally amazing even as it currently stands, I've been wanting something like it for years! Thank you for making and sharing it ❀️

Browser Support

Great Library!
Really love it!

As you are using Proxies. How is Internet Explorer supported?

SyntaxError: ';' expected.

Hey!

It's my first time using gqless, and when I run gqless generate I get the following error:

SyntaxError: ';' expected. (9095:6)
  9093 | * @type OBJECT
  9094 |  */
> 9095 | type t_HighLevelStatses = FieldsType<{
       |      ^
  9096 | __typename: t_String<'HighLevelStatses'>
  9097 | highLevelStats: t_HighLevelStats
  9098 | highLevelStatsForMonths: (t_HighLevelStatsForMonth)[]
    at t (~/development/jill/jills-office-web/node_modules/prettier/parser-typescript.js:1:285)
    at Object.parse (~/development/jill/jills-office-web/node_modules/prettier/parser-typescript.js:14:180461)
    at Object.parse (~/development/jill/jills-office-web/node_modules/prettier/index.js:9739:19)
    at coreFormat (~/development/jill/jills-office-web/node_modules/prettier/index.js:13252:23)
    at format (~/development/jill/jills-office-web/node_modules/prettier/index.js:13510:73)
    at formatWithCursor (~/development/jill/jills-office-web/node_modules/prettier/index.js:13526:12)
    at ~/development/jill/jills-office-web/node_modules/prettier/index.js:44207:15
    at Object.format (~/development/jill/jills-office-web/node_modules/prettier/index.js:44226:12)
    at Object.exports.generateSchema (~/development/jill/jills-office-web/node_modules/@gqless/cli/dist/utils/generateSchema.js:25:33)
    at async Generate.run (~/development/jill/jills-office-web/node_modules/@gqless/cli/dist/commands/generate.js:27:9)

It looks like it's just some formatting after the generation takes place, I believe the generation succeeded, though I'm not quite sure.

question re: `Arrays will have a length of 1`

As the readme writes:

All the GraphQL objects will be available, but the data on them won't.

Arrays will have a length of 1
Scalars will return null

Is there a reason why arrays become [null] and not just []? It's definitly not a deal-breaker, but [] would be a bit more convenient for the developer.

How do I generate the whole schema

Not knowing this that well, if I'm using typescript, then hopefully I can query strongly typed data, right? I don't want to rely on any,

Do you have an example of how it can be done? I could generate schema once in a while, but without having strongly typed, it's a little difficult,

I can see gqless generate script being run on the demo app, but I don't see any endpoint definition or where to dump the generated data

Support for hooks

This library looks amazing, first time I've felt excited to write react in a while. tada

How difficult would it be to support hooks instead of HOCs?

function User({ user }: { user: User }) {
  useGraphql();

  return (
    <div>
      <h2>
        {user.name}
      </h2>
      <img src={user.avatarUrl({ size: 100 })} />
    </div>
  );
}

instead of

const User = graphql(({ user }: { user: User }) =>
  <div>
    <h2>
      {user.name}
    </h2>
    <img src={user.avatarUrl({ size: 100 })} />
  </div>
);

It might even provide a nice way to put query into scope:

function App() {
  const { query } = useGraphql();

  return (
    <div>
      <h1>
        {query.me.name}
      </h1>
      <Query>
        <Description user={query.me} />
      </Query>
    </div>
  );
}

Can't query enum fields

I've discovered that in gqless it's not possible to query fields of enum type. Here is reproduction https://codesandbox.io/s/gqless-enum-bug-4lu4b

In this reproduction for each story I'm also querying the type field which is of enum type.
The resulting query should be:

{
  hn {
    topStories(limit: 25) {
      id
      type
      url
      title
      score
      by {
        id
      }
      descendants
    }
  }
}

but instead it tries to query:

{
  hn {
    topStories(limit: 25) {
      id
      type {
        __typename
      }
      url
      title
      score
      by {
        id
      }
      descendants
    }
  }
}

The error message is: "Field "type" of type "ItemType!" must not have a sub selection.
And you can see that it treats the enum type like object type.

Also, speaking about enums. It would be great if enums after generation are still enums and not type in the form:

type t_ItemType = EnumType<"job" | "story" | "comment" | "poll" | "pollopt">;

but this instead:

enum t_ItemType {
  job = "job",
  story = "story",
  comment = "comment",
  poll = "poll",
  pollopt = "pollopt"
}

It's what tools like graphql-codegen do and it's pretty handy as I can use syntax like ItemType.job instead of providing value directly.

Modifying cache through the query object

Currently it's possible to modify cache through the query object only when a value was "resolved" in the graphql context (sorry for using probably wrong vocabulary). Here is an example:

const User = graphql(({ user }) => {
  return (
    <div onClick={() => (user.name = String(Math.random()))}>{user.name}</div>
  );
});

const Users = graphql(() => {
  return (
    <div>
      {query.users.map(user => (
        <User key={user.id} user={user} />
      ))}
    </div>
  );
});

However, when I try doing something like this it will not work:

const User = graphql(({ user }) => {
  return <div onClick={() => query.users.pop()}>{user.name}</div>;
});

Would it be possible to change that to work more like it's done in MobX. I haven't used MobX before and I'm just finding out how great it is and that a lot of concepts are being used in gqless as well. So if it's possible there then maybe here as well? That would make thinking about cache much easier.

The idea is to just "resolve" values if they are outside of the graphql context like in async functions without actually performing the graphql query which of course is not a case already as it's out of the context.

Remove graphql hoc

Let's discuss the removal/improvement of graphql HOC. I assume it was created so it's possible to detect a scope of GraphQL schema that is to be queried within a single component - please correct me if I am wrong.

mobx-react is using the same approach and it gets pretty messy if a single function component has to be wrapped twice or more:

import { graphql } from "@gqless/react";
import { observer } from "mobx-react";
import * as React from "react";

export type MyComponentProps = {};

export const MyComponent = graphql(
  observer((props: MyComponentProps) => {
    return <div>content...</div>;
  })
);

Ideas:

  1. A basic Provider/Consumer API would be nice where the user has to just pass gqless client into a provider and the rest magically works out of the box. I guess it's "doable" but would require recursive iteration and dynamic wrapping of all react children with graphql HOC. This method is expensive and probably should be avoided - just throwing the idea out here.

  2. Babel plugin that automatically detects and applies graphql HOC to components accessing queries. I think this should work fairly well and doesn't require too much magic/hacking. The downside is that it won't work for people not using babel - obviously πŸ˜„ .

  3. Last and in my opinion the best solution would be to talk with react team and discuss creating/exposing an API that would allow detecting/hooking into react rendering lifecycles required by libraries like gqless & mobx-react.

Fields are wrongly typed as `null | undefined`

I initially thought it was just my own GraphQL API that was misconfigured but this even happens with publicly available endpoints.

I've run gqless generate with the endpoint set to https://graphql-pokemon.now.sh and it does generate a bunch of types. But each property of the query object seems to have type null | undefined.

This seems to be the full type of query:

(alias) const query: {
    __typename: "Query";
    query?: {
        __typename: "Query";
        query?: {
            __typename: "Query";
            query?: {
                __typename: "Query";
                query?: {
                    __typename: "Query";
                    query?: {
                        __typename: "Query";
                        query?: {
                            __typename: "Query";
                            query?: {
                                ...;
                            } | ... 1 more ... | undefined;
                            pokemons?: null | undefined;
                            pokemon?: null | undefined;
                        } | null | undefined;
                        pokemons?: null | undefined;
                        pokemon?: null | undefined;
                    } | null | undefined;
                    pokemons?: null | undefined;
                    pokemon?: null | undefined;
                } | null | undefined;
                pokemons?: null | undefined;
                pokemon?: null | undefined;
            } | null | undefined;
            pokemons?: null | undefined;
            pokemon?: null | undefined;
        } | null | undefined;
        pokemons?: null | undefined;
        pokemon?: null | undefined;
    } | null | undefined;
    pokemons?: null | undefined;
    pokemon?: null | undefined;
}

So when I try to access either the pokemon or pokemons field they are both typed as pokemons?: null | undefined. I also get a TS error saying Type instantiation is excessively deep and possibly infinite.ts(2589) which may be what is ultimately causing this in the first place.

Either way the TypeScript information for a publicly available API isn't working because gqless gives up trying to type any of the possible queries.

TODO: Blog post

Emphasize it is a standalone GraphQL client, alternative to Apollo/Relay. Built to be used in complex applications the size of Discord.

Emphasize the reactivity, components are selectively updated - similar to using MobX. Emphasize being able to observe values

Emphasize the extensions, the ability to add client-only state to your endpoint. Being able to convert date strings into Date objects, automatically. Being able to create custom abstractions, essentially your own custom SDK.
Being able to compute fields, from other fields if they're already fetched - if not just fetching.

Emphasize the ability to orchestrate how queries are fetched at runtime, without manually modifying queries. The ability to perform A/B tests etc.

Emphasize the dev tools. With normal clients you feel like your separated from your endpoint, having to open up a playground to see data. Emphasize being able to log an object - see it's fields, update it, and fetch additional fields on it.

Emphasize the 100% type safety, within your whole app. The instant feedback, inline documentation, find-all references

Emphasize the 'smart objects'. Always having the current version of the data throughout your entire application. Emphasize the power it gives you, ie. React hooks.

Emphasize being able to update the cache, just by having a reference to the data you want to update. No manual updating of a cache store

Emphasize how flexible the cache is. Being able to link any parts of it together.

Emphasize the abilty to create custom abstractions, without modifying the internals. Eg. Poller

Emphasize how well it works with suspense, the ability to have progressive loading states

Emphasize how it tries to reduce data fetching. Fetching the single fields it needs + fields required to ID a type. If the returned ID changes - it sends out another request to re-fetch everything

[Perf] SharedWorker Cache

SharedWorker

Motivation

The new architecture for gqless's Cache, will be SharedWorker backed.
By utilizing a SharedWorker, cache updates can happen crosstab.

Although limited to the same origin, this could be worked around using an iframe. Imagine cross-domain caches for the same API. TravisCI, loading data already cached on github.com

We don't want to have to clone the cache to/from main-thread on each change, so we will utilize a SharedArrayBuffer.

ArrayBuffer cache representation

Using an ArrayBuffer means we have to create a structured memory representation for the GraphQL data. As GraphQL is a strongly typed language, we can utilize the schema to create a highly efficient representation of the data (without JSON keys).

Benefits

  • This ArrayBuffer can be directly written into IndexedDB without a serialization/deserialization step. This will be blazing fast

  • This binary format can be directly returned by the server, instead of JSON. Allowing for super-small payloads, and super fast cache merges.

  • SSR hydration will be super fast

Why not use X

We could use existing tech like protobuf, but we can get better perf/bundle sizes from scratch.

Additionally they have concepts like RPC, which aren't required

WASM

The SharedWorker will be responsible for fetching/merging cache updates. We could either implement this logic in JS or Rust.

WASM will inevitably be way faster, but has massive payload sizes. TBD

Optimized server responses

As we now have a binary representation for GraphQL data, servers can return raw bytes - instead of JSON responses.

This will need to be baked into Apollo server or whatnot, as the representation requires the schema.

The client add the Accept-Encoding: application/gqless header, which the server will respect or ignore.

For servers that don't implement the binary format, gqless will still support JSON responses

Compatibility

As SharedWorker aren't available inside Node, we need to have a fallback that works without SharedWorker.

This fallback will have to run on the main thread, which means we don't technically need SharedArrayBuffer.

It would make sense to use this fallback when SharedWorker/SharedArrayBuffer is not available. So all that should be required is ArrayBuffer

Mapping over empty list

There is a problem with mapping an empty array of values. Let's take the hackernews example. If I query the following data:

<div>
  {query.hn!.topStories({ limit: 0 })!.map(story =>
    <Story key={story!.id} story={story!} />
  )}
</div>

It will still try to render one element because the story element is a proxy object.
Here is the reproduction repo: https://codesandbox.io/s/gqless-empty-list-rprc5

I thought that maybe using the ofType function would help but not. I guess it would be good to add some helper function that checks if a value in the proxy is loaded? We shouldn't even try to render an element if a value is not there. Or maybe proxies for arrays should work differently?

EDIT:
What I think is wrong, is that query.hn!.topStories({ limit: 0 })!.length always returns 1 for empty list

EDIT2:
I guess in this line https://github.com/samdenty/gqless/blob/master/gqless/src/Node/ArrayNode.ts#L77 it has to be changed to:

return arr?.length ?? 0

In my tests it worked but I'm not sure if it's not gonna mess something else.

fetch is not defined

Trying to run a simple query in a node.js project, getting this error:

[ <1 empty item> ]
ReferenceError: fetch is not defined

Does this package not work outside the browser?

Edit: I got it to work by manually adding the node-fetch package to the generated client.ts code.

Support for Node

Can this also be used for command-line apps? Or does it depend on react?

It looks quite awesome for fast prototyping and quering a graphql endpoint in a node repl.

The refetch and usePoll functions rerender component with old data

Ok I've checked cache with empty arrays and everything works well there, so no need for changes. However, I found another issue that is not related to the length = 1 issue. There is a problem in the refetch helper. I've confirmed it in my tests. Here is how to reproduce it (sorry for no reproduction repo):

  1. Display list of elements, let's say query.users. Let's say that initial query returns 5 elements
  2. Manually change in DB number of users so that the query.users query will return different number of elements now
  3. Run refetch for the query.users query
  4. Component will rerender but will the old list of elements even though that cache was properly updated
  5. Run refetch again and now component will render with appropriate number of elements

EDIT:
The same is true for usePoll. At first run it gets updated data from gql server, properly updates cache but component is rerendered with the old data. Only the next poll operation updates component properly.

Originally posted by @lukejagodzinski in #63 (comment)

Cache persistance

For the slowest bits of our app we like to have the query cache persist between page loads in localstorage (or wherever). We currently use apollo-cache-persist for that.

We use the fetch method "cache-and-network" meaning that while the app may have stale data on first load at least something is showing, then fresh data loads in the background and updates the UI as necessary.

Query batching

It would be nice if like Apollo you could optionally choose to have queries batched together. AKA if 5 components request data, the client would make just 1 request with an array of queries. This is good for reducing network requests and tends to speed up apps provided the queries are fast.

It should still be optional as with slow queries it may make overall load time slower. E.g. we have one place in our app where we disable batching because a few of the queries are very slow so it's better to have them run in parallel such that the faster queries finish and render without waiting for the slow ones.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.