@n1ru4l/in-memory-live-query-store
Version:
[](https://www.npmjs.com/package/@n1ru4l/in-memory-live-query-store) [](https://www.np
153 lines (118 loc) • 7.56 kB
Markdown
# @n1ru4l/in-memory-live-query-store
[](https://www.npmjs.com/package/@n1ru4l/in-memory-live-query-store) [](https://www.npmjs.com/package/@n1ru4l/in-memory-live-query-store)
A GraphQL live query store that tracks, invalidates and re-executes registered operations. Drop in replacement for `graphql-js` `execute`. Add live query capabilities to your existing GraphQL schema.
Check out the [todo example server](https://github.com/n1ru4l/graphql-live-queries/blob/main/packages/todo-example/server/src/schema.ts) for a sample integration.
## Install Instructions
```bash
yarn add -E @n1ru4l/in-memory-live-query-store
```
## API
### `InMemoryLiveQueryStore`
A `InMemoryLiveQueryStore` instance tracks, invalidates and re-executes registered live query operations.
The store will keep track of all root query field coordinates (e.g. `Query.todos`) and global resource identifiers (e.g. `Todo:1`). The store can than be notified to re-execute live query operations that select a given root query field or resource identifier by calling the `invalidate` method with the corresponding values.
A resource identifier is composed out of the typename and the actual resolved id value separated by a colon, but can be customized. For ensuring that the store keeps track of all your query resources you should always select the `id` field on your object types. The store will only keep track of fields with the name `id` and the type `ID!` (`GraphQLNonNull(GraphQLID)`).
```ts
import { InMemoryLiveQueryStore } from "@n1ru4l/in-memory-live-query-store";
import { parse, execute as executeImplementation } from "graphql";
import { schema } from "./schema";
const inMemoryLiveQueryStore = new InMemoryLiveQueryStore();
const rootValue = {
todos: [
{
id: "1",
content: "Foo",
isComplete: false,
},
],
};
const execute = inMemoryLiveQueryStore.makeExecute(executeImplementation);
execute({
schema, // make sure your schema has the GraphQLLiveDirective from @n1ru4l/graphql-live-query
operationDocument: parse(/* GraphQL */ `
query todosQuery @live {
todos {
id
content
isComplete
}
}
`),
rootValue: rootValue,
contextValue: {},
variableValues: null,
operationName: "todosQuery",
}).then(async (result) => {
if (isAsyncIterable(result)) {
for (const value of result) {
console.log(value);
}
}
});
// Invalidate by resource identifier
rootValue.todos[0].isComplete = true;
inMemoryLiveQueryStore.invalidate(`Todo:1`);
// Invalidate by root query field coordinate
rootValue.todos.push({ id: "2", content: "Baz", isComplete: false });
inMemoryLiveQueryStore.invalidate(`Query.todos`);
```
The `execute` function returned from `InMemoryLiveQueryStore.makeExecute` is a drop-in replacement for the default `execute` function exported from `graphql-js`. You can provide your own `execute` function from any graphql-js version or other library.
Pass it to your favorite graphql transport that supports returning `AsyncIterableIterator` (or `AsyncGenerator`) from `execute` and thus delivering incremental query execution results.
List of known and tested compatible transports/servers:
| Package | Transport | Version | Downloads |
| -------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`@n1ru4l/socket-io-graphql-server`](https://github.com/n1ru4l/graphql-live-queries/blob/main/packages/socket-io-graphql-server) | WebSocket/HTTP Long Polling | [](https://github.com/n1ru4l/graphql-live-queries/blob/main/packages/socket-io-graphql-server) | [](https://github.com/n1ru4l/graphql-live-queries/blob/main/packages/socket-io-graphql-server) |
| [`graphql-helix`](https://github.com/danielrearden/graphql-helix) | HTTP/SSE | [](https://github.com/danielrearden/graphql-helix) | [](https://github.com/danielrearden/graphql-helix) |
| [`graphql-ws`](https://github.com/enisdenjo/graphql-ws) | WebSocket | [](https://github.com/enisdenjo/graphql-ws) | [](https://github.com/enisdenjo/graphql-ws) |
## Recipes
### Using with Redis
You can use Redis to synchronize invalidations across multiple instances.
[A full runnable example can be found here](../example-redis).
```ts
import Redis from "ioredis";
import { InMemoryLiveQueryStore } from "@n1ru4l/in-memory-live-query-store";
import { execute as defaultExecute } from "graphql";
const inMemoryLiveQueryStore = new InMemoryLiveQueryStore();
const client = new Redis(redisUri);
const subClient = new Redis(redisUri);
class RedisLiveQueryStore {
pub: Redis;
sub: Redis;
channel: string;
liveQueryStore: InMemoryLiveQueryStore;
constructor(
pub: Redis,
sub: Redis,
channel: string,
liveQueryStore: InMemoryLiveQueryStore
) {
this.pub = pub;
this.sub = sub;
this.liveQueryStore = liveQueryStore;
this.channel = channel;
this.sub.subscribe(this.channel, (err) => {
if (err) throw err;
});
this.sub.on("message", (channel, resourceIdentifier) => {
if (channel === this.channel && resourceIdentifier)
this.liveQueryStore.invalidate(resourceIdentifier);
});
}
async invalidate(identifiers: Array<string> | string) {
if (typeof identifiers === "string") {
identifiers = [identifiers];
}
for (const identifier of identifiers) {
this.pub.publish(this.channel, identifier);
}
}
makeExecute(execute: typeof defaultExecute) {
return this.liveQueryStore.makeExecute(execute);
}
}
const liveQueryStore = new RedisLiveQueryStore(
client,
subClient,
"live-query-invalidations",
inMemoryLiveQueryStore
);
```