Skip to content

Commit

Permalink
feat(live-query): new docs and options
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Sep 18, 2022
1 parent 2efa23f commit ff251e4
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 68 deletions.
6 changes: 6 additions & 0 deletions .changeset/polite-items-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-mesh/plugin-live-query': patch
'@graphql-mesh/types': patch
---

See the new configuration schema for the new options
39 changes: 25 additions & 14 deletions packages/plugins/live-query/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
import { useLiveQuery } from '@envelop/live-query';
import { MeshPluginOptions, YamlConfig } from '@graphql-mesh/types';
import { InMemoryLiveQueryStore } from '@n1ru4l/in-memory-live-query-store';
import { defaultResourceIdentifierNormalizer, InMemoryLiveQueryStore } from '@n1ru4l/in-memory-live-query-store';
import { Plugin } from '@envelop/core';
import { useInvalidateByResult } from './useInvalidateByResult';
import { process } from '@graphql-mesh/cross-helpers';
import { stringInterpolator } from '@graphql-mesh/string-interpolation';

export default function useMeshLiveQuery(options: MeshPluginOptions<YamlConfig.LiveQueryConfig>): Plugin {
options.logger.debug(`Creating Live Query Store`);
const liveQueryStore = new InMemoryLiveQueryStore({
includeIdentifierExtension: true,
});
options.polling?.forEach(pollingConfig => {
const interval = setInterval(() => {
liveQueryStore
.invalidate(pollingConfig.invalidate)
.catch((e: Error) => options.logger.warn(`Invalidation failed for ${pollingConfig.invalidate}: ${e.message}`));
}, pollingConfig.interval);
const id = options.pubsub.subscribe('destroy', () => {
clearInterval(interval);
options.pubsub.unsubscribe(id);
});
buildResourceIdentifier:
options.resourceIdentifier != null
? function resourceIdentifierFactory({ typename, id }) {
return stringInterpolator.parse(options.resourceIdentifier, {
typename,
id,
env: process.env,
});
}
: defaultResourceIdentifierNormalizer,
includeIdentifierExtension:
options.includeIdentifierExtension != null ? options.includeIdentifierExtension : process.env.DEBUG === '1',
idFieldName: options.idFieldName,
indexBy: options.indexBy,
});
options.pubsub.subscribe('live-query:invalidate', (identifiers: string | string[]) =>
liveQueryStore.invalidate(identifiers)
);
return {
onPluginInit({ addPlugin }) {
addPlugin(useLiveQuery({ liveQueryStore }));
if (options.invalidations?.length) {
addPlugin(
useInvalidateByResult({ liveQueryStore, invalidations: options.invalidations, logger: options.logger })
useInvalidateByResult({
pubsub: options.pubsub,
invalidations: options.invalidations,
logger: options.logger,
})
);
}
},
Expand Down
65 changes: 30 additions & 35 deletions packages/plugins/live-query/src/useInvalidateByResult.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Plugin } from '@envelop/core';
import { getInterpolatedStringFactory, ResolverDataBasedFactory } from '@graphql-mesh/string-interpolation';
import { Logger, YamlConfig } from '@graphql-mesh/types';
import { InMemoryLiveQueryStore } from '@n1ru4l/in-memory-live-query-store';
import { Logger, MeshPubSub, YamlConfig } from '@graphql-mesh/types';
import { getOperationAST, TypeInfo, visit, visitWithTypeInfo } from 'graphql';

interface InvalidateByResultParams {
liveQueryStore: InMemoryLiveQueryStore;
pubsub: MeshPubSub;
invalidations: YamlConfig.LiveQueryInvalidation[];
logger: Logger;
}
Expand All @@ -23,38 +22,34 @@ export function useInvalidateByResult(params: InvalidateByResultParams): Plugin
onExecute() {
return {
onExecuteDone({ args: executionArgs, result }) {
queueMicrotask(() => {
const { schema, document, operationName, rootValue, contextValue } = executionArgs;
const operationAST = getOperationAST(document, operationName);
if (!operationAST) {
throw new Error(`Operation couldn't be found`);
}
const typeInfo = new TypeInfo(schema);
visit(
operationAST,
visitWithTypeInfo(typeInfo, {
Field: () => {
const parentType = typeInfo.getParentType();
const fieldDef = typeInfo.getFieldDef();
const path = `${parentType.name}.${fieldDef.name}`;
if (liveQueryInvalidationFactoryMap.has(path)) {
const invalidationPathFactories = liveQueryInvalidationFactoryMap.get(path);
const invalidationPaths = invalidationPathFactories.map(invalidationPathFactory =>
invalidationPathFactory({
root: rootValue,
context: contextValue,
env: process.env,
result,
})
);
params.liveQueryStore
.invalidate(invalidationPaths)
.catch((e: Error) => params.logger.warn(`Invalidation failed for ${path}: ${e.message}`));
}
},
})
);
});
const { schema, document, operationName, rootValue, contextValue } = executionArgs;
const operationAST = getOperationAST(document, operationName);
if (!operationAST) {
throw new Error(`Operation couldn't be found`);
}
const typeInfo = new TypeInfo(schema);
visit(
operationAST,
visitWithTypeInfo(typeInfo, {
Field: () => {
const parentType = typeInfo.getParentType();
const fieldDef = typeInfo.getFieldDef();
const path = `${parentType.name}.${fieldDef.name}`;
if (liveQueryInvalidationFactoryMap.has(path)) {
const invalidationPathFactories = liveQueryInvalidationFactoryMap.get(path);
const invalidationPaths = invalidationPathFactories.map(invalidationPathFactory =>
invalidationPathFactory({
root: rootValue,
context: contextValue,
env: process.env,
result,
})
);
params.pubsub.publish('live-query:invalidate', invalidationPaths);
}
},
})
);
},
};
},
Expand Down
35 changes: 30 additions & 5 deletions packages/plugins/live-query/yaml-config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,34 @@ type LiveQueryConfig {
"""
invalidations: [LiveQueryInvalidation]
"""
Allow an operation can be used a live query with polling
Custom strategy for building resources identifiers
By default resource identifiers are built by concatenating the Typename with the id separated by a color (`User:1`).
This may be useful if you are using a relay compliant schema and the Typename information is not required for building a unique topic.
Default: "{typename}:{id}"
"""
resourceIdentifier: String
"""
Whether the extensions should include a list of all resource identifiers for the latest operation result.
Any of those can be used for invalidating and re-scheduling the operation execution.
This is mainly useful for discovering and learning what kind of topics a given query will subscribe to.
The default value is `true` if `DEBUG` environment variable is set
"""
includeIdentifierExtension: Boolean

"""
polling: [LiveQueryPolling]
Identifier unique field
Default: "id"
"""
idFieldName: String

"""
Specify which fields should be indexed for specific invalidations.
"""
indexBy: [LiveQueryIndexBy]
}

type LiveQueryInvalidation {
Expand All @@ -21,7 +46,7 @@ type LiveQueryInvalidation {
invalidate: [String!]!
}

type LiveQueryPolling {
interval: Int!
invalidate: [String!]!
type LiveQueryIndexBy {
field: String!
args: [String]!
}
30 changes: 21 additions & 9 deletions packages/types/src/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1947,13 +1947,25 @@
"additionalItems": false,
"description": "Invalidate a query or queries when a specific operation is done without an error"
},
"polling": {
"resourceIdentifier": {
"type": "string",
"description": "Custom strategy for building resources identifiers\nBy default resource identifiers are built by concatenating the Typename with the id separated by a color (`User:1`).\n\nThis may be useful if you are using a relay compliant schema and the Typename information is not required for building a unique topic.\n\nDefault: \"{typename}:{id}\""
},
"includeIdentifierExtension": {
"type": "boolean",
"description": "Whether the extensions should include a list of all resource identifiers for the latest operation result.\nAny of those can be used for invalidating and re-scheduling the operation execution.\n\nThis is mainly useful for discovering and learning what kind of topics a given query will subscribe to.\nThe default value is `true` if `DEBUG` environment variable is set"
},
"idFieldName": {
"type": "string",
"description": "Identifier unique field\n\nDefault: \"id\""
},
"indexBy": {
"type": "array",
"items": {
"$ref": "#/definitions/LiveQueryPolling"
"$ref": "#/definitions/LiveQueryIndexBy"
},
"additionalItems": false,
"description": "Allow an operation can be used a live query with polling"
"description": "Specify which fields should be indexed for specific invalidations."
}
}
},
Expand All @@ -1976,23 +1988,23 @@
},
"required": ["field", "invalidate"]
},
"LiveQueryPolling": {
"LiveQueryIndexBy": {
"additionalProperties": false,
"type": "object",
"title": "LiveQueryPolling",
"title": "LiveQueryIndexBy",
"properties": {
"interval": {
"type": "integer"
"field": {
"type": "string"
},
"invalidate": {
"args": {
"type": "array",
"items": {
"type": "string"
},
"additionalItems": false
}
},
"required": ["interval", "invalidate"]
"required": ["field", "args"]
},
"MockingConfig": {
"additionalProperties": false,
Expand Down
33 changes: 28 additions & 5 deletions packages/types/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1744,9 +1744,32 @@ export interface LiveQueryConfig {
*/
invalidations?: LiveQueryInvalidation[];
/**
* Allow an operation can be used a live query with polling
* Custom strategy for building resources identifiers
* By default resource identifiers are built by concatenating the Typename with the id separated by a color (`User:1`).
*
* This may be useful if you are using a relay compliant schema and the Typename information is not required for building a unique topic.
*
* Default: "{typename}:{id}"
*/
resourceIdentifier?: string;
/**
* Whether the extensions should include a list of all resource identifiers for the latest operation result.
* Any of those can be used for invalidating and re-scheduling the operation execution.
*
* This is mainly useful for discovering and learning what kind of topics a given query will subscribe to.
* The default value is `true` if `DEBUG` environment variable is set
*/
includeIdentifierExtension?: boolean;
/**
* Identifier unique field
*
* Default: "id"
*/
idFieldName?: string;
/**
* Specify which fields should be indexed for specific invalidations.
*/
polling?: LiveQueryPolling[];
indexBy?: LiveQueryIndexBy[];
}
export interface LiveQueryInvalidation {
/**
Expand All @@ -1755,9 +1778,9 @@ export interface LiveQueryInvalidation {
field: string;
invalidate: string[];
}
export interface LiveQueryPolling {
interval: number;
invalidate: string[];
export interface LiveQueryIndexBy {
field: string;
args: string[];
}
/**
* Mock configuration for your source
Expand Down
8 changes: 8 additions & 0 deletions website/src/pages/docs/guides/live-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ module.exports = {
}
```

## Config API Reference

import API from '../../../generated-markdown/ResponseCacheConfig.generated.md'

<API />

## Codesandbox Example

<Callout>
You can learn more about [GraphQL Live Query](https://github.com/n1ru4l/graphql-live-query) in its documentation. You
can check out our example that uses live queries
Expand Down

0 comments on commit ff251e4

Please sign in to comment.