From 40fc65d51d9f236221e34a99cc29586a76b91224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Wed, 1 Feb 2023 23:51:23 +0100 Subject: [PATCH 1/9] Add new test to check feature --- test/unit/replication-graphql.test.ts | 41 ++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/test/unit/replication-graphql.test.ts b/test/unit/replication-graphql.test.ts index 49be1262076..59890306a55 100644 --- a/test/unit/replication-graphql.test.ts +++ b/test/unit/replication-graphql.test.ts @@ -61,7 +61,7 @@ import { buildSchema, parse as parseQuery } from 'graphql'; -import { RxDocumentData } from '../../src/types'; +import { ReplicationPushHandlerResult, RxDocumentData } from '../../src/types'; import { enableKeyCompression } from '../helper/schemas'; declare type WithDeleted = T & { deleted: boolean; }; @@ -1047,6 +1047,45 @@ describe('replication-graphql.test.ts', () => { const docsOnServer = server.getDocuments(); assert.strictEqual(docsOnServer.length, batchSize); + server.close(); + c.database.destroy(); + }); + it('should respect the push.responseModifier', async () => { + const [c, server] = await Promise.all([ + humansCollection.createHumanWithTimestamp(batchSize), + SpawnServer.spawn() + ]); + + let responseHaveBeenCalledTimes = 0; + const replicationState = replicateGraphQL({ + collection: c, + url: server.url, + push: { + batchSize, + queryBuilder: pushQueryBuilder, + responseModifier( + originalResponse: ReplicationPushHandlerResult, + ) { + responseHaveBeenCalledTimes += 1; + return originalResponse; + } + }, + live: false, + retryTime: 1000, + deletedField: 'deleted' + }); + ensureReplicationHasNoErrors(replicationState); + + + await replicationState.awaitInitialReplication(); + + const docsOnServer = server.getDocuments(); + assert.strictEqual(docsOnServer.length, batchSize); + assert.strictEqual( + responseHaveBeenCalledTimes, + 1 + ); + server.close(); c.database.destroy(); }); From 698a104e197d44fccac5e30e63d787e1c448213c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Wed, 1 Feb 2023 23:54:16 +0100 Subject: [PATCH 2/9] Added/modified types for responseModifier --- src/types/plugins/replication-graphql.d.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/types/plugins/replication-graphql.d.ts b/src/types/plugins/replication-graphql.d.ts index 66d891679ae..e393646e31f 100644 --- a/src/types/plugins/replication-graphql.d.ts +++ b/src/types/plugins/replication-graphql.d.ts @@ -1,6 +1,6 @@ import { RxReplicationWriteToMasterRow } from '../replication-protocol'; import { ById, MaybePromise } from '../util'; -import { ReplicationOptions, ReplicationPullHandlerResult, ReplicationPullOptions, ReplicationPushOptions } from './replication'; +import { ReplicationOptions, ReplicationPullHandlerResult, ReplicationPullOptions, ReplicationPushHandlerResult, ReplicationPushOptions } from './replication'; export interface RxGraphQLReplicationQueryBuilderResponseObject { query: string; @@ -43,6 +43,11 @@ export type RxGraphQLPullResponseModifier = ( requestCheckpoint?: CheckpointType ) => MaybePromise>; +export type RxGraphQLPushResponseModifier = ( + // the exact response that was returned from the server + plainResponse: ReplicationPushHandlerResult | any, +) => MaybePromise>; + export type RxGraphQLReplicationPullStreamQueryBuilder = (headers: { [k: string]: string; }) => RxGraphQLReplicationQueryBuilderResponse; export type GraphQLSyncPushOptions = Omit< @@ -50,6 +55,8 @@ ReplicationPushOptions, 'handler' > & { queryBuilder: RxGraphQLReplicationPushQueryBuilder; + dataPath?: string; + responseModifier?: RxGraphQLPushResponseModifier; }; export type GraphQLServerUrl = { From 28e13d801ab26a536d3fe50a2be848259989e2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Wed, 1 Feb 2023 23:56:00 +0100 Subject: [PATCH 3/9] Added export type ReplicationPushHandlerResult type --- src/types/plugins/replication.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/plugins/replication.d.ts b/src/types/plugins/replication.d.ts index e44784087a5..be1bcf781e7 100644 --- a/src/types/plugins/replication.d.ts +++ b/src/types/plugins/replication.d.ts @@ -22,6 +22,8 @@ export type ReplicationPullHandlerResult = { documents: WithDeleted[]; }; +export type ReplicationPushHandlerResult = RxDocType[]; + export type ReplicationPullHandler = ( lastPulledCheckpoint: CheckpointType, batchSize: number @@ -85,6 +87,8 @@ export type ReplicationPushOptions = { */ modifier?: (docData: WithDeleted) => MaybePromise; + + /** * How many local changes to process at once. */ From 8c75efec706e98b2c8fc30bfebdaa407229e6a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Wed, 1 Feb 2023 23:57:50 +0100 Subject: [PATCH 4/9] Added logic for responseModifier in push replication --- src/plugins/replication-graphql/index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plugins/replication-graphql/index.ts b/src/plugins/replication-graphql/index.ts index af921c2f2cf..31b195d2a75 100644 --- a/src/plugins/replication-graphql/index.ts +++ b/src/plugins/replication-graphql/index.ts @@ -161,8 +161,15 @@ export function replicateGraphQL( if (result.errors) { throw result.errors; } - const dataPath = Object.keys(result.data)[0]; - const data: any = getProperty(result.data, dataPath); + const dataPath = push.dataPath || Object.keys(result.data)[0]; + let data: any = getProperty(result.data, dataPath); + + if (push.responseModifier) { + data = await push.responseModifier( + data, + ); + } + return data; }, batchSize: push.batchSize, From cac4f9870dd41b51e62e1da5f761fb22c5d65d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Wed, 1 Feb 2023 23:59:52 +0100 Subject: [PATCH 5/9] Added documentation --- docs-src/replication-graphql.md | 233 +++++++++++++++++++------------- 1 file changed, 140 insertions(+), 93 deletions(-) diff --git a/docs-src/replication-graphql.md b/docs-src/replication-graphql.md index d3696d09c01..31fb0b141c9 100644 --- a/docs-src/replication-graphql.md +++ b/docs-src/replication-graphql.md @@ -131,8 +131,8 @@ const pullQueryBuilder = (checkpoint, limit) => { */ if (!checkpoint) { checkpoint = { - id: '', - updatedAt: 0 + id: "", + updatedAt: 0, }; } const query = `query PullHuman($checkpoint: CheckpointInput, $limit: Int!) { @@ -154,8 +154,8 @@ const pullQueryBuilder = (checkpoint, limit) => { query, variables: { checkpoint, - limit - } + limit, + }, }; }; ``` @@ -207,7 +207,7 @@ const replicationState = replicateGraphQL( For the push-replication, you also need a `queryBuilder`. Here, the builder receives a changed document as input which has to be send to the server. It also returns a GraphQL-Query and its data. ```js -const pushQueryBuilder = rows => { +const pushQueryBuilder = (rows) => { const query = ` mutation PushHuman($writeRows: [HumanInputPushRow!]) { pushHuman(writeRows: $writeRows) { @@ -220,11 +220,11 @@ const pushQueryBuilder = rows => { } `; const variables = { - writeRows: rows + writeRows: rows, }; return { query, - variables + variables, }; }; ``` @@ -232,39 +232,37 @@ const pushQueryBuilder = rows => { With the queryBuilder, you can then setup the push-replication. ```js -const replicationState = replicateGraphQL( - { - collection: myRxCollection, - // urls to the GraphQL endpoints - url: { - http: 'http://example.com/graphql' - }, - push: { - queryBuilder: pushQueryBuilder, // the queryBuilder from above - /** - * batchSize (optional) - * Amount of document that will be pushed to the server in a single request. - */ - batchSize: 5, - /** - * modifier (optional) - * Modifies all pushed documents before they are send to the GraphQL endpoint. - * Returning null will skip the document. - */ - modifier: doc => doc - }, - headers: { - Authorization: 'Bearer abcde...' - }, - pull: { - /* ... */ - }, +const replicationState = replicateGraphQL({ + collection: myRxCollection, + // urls to the GraphQL endpoints + url: { + http: "http://example.com/graphql", + }, + push: { + queryBuilder: pushQueryBuilder, // the queryBuilder from above + /** + * batchSize (optional) + * Amount of document that will be pushed to the server in a single request. + */ + batchSize: 5, + /** + * modifier (optional) + * Modifies all pushed documents before they are send to the GraphQL endpoint. + * Returning null will skip the document. + */ + modifier: (doc) => doc, + dataPath: undefined, // (optional) specifies the object path to access the conflicting document(s). Otherwise, the first result of the response data is used. + }, + headers: { + Authorization: "Bearer abcde...", + }, + pull: { /* ... */ - } -); + }, + /* ... */ +}); ``` - #### Pull Stream To create a **realtime** replication, you need to create a pull stream that pulls ongoing writes from the server. @@ -290,8 +288,8 @@ const pullStreamQueryBuilder = (headers) => { return { query, variables: { - headers - } + headers, + }, }; }; ``` @@ -299,48 +297,52 @@ const pullStreamQueryBuilder = (headers) => { With the `pullStreamQueryBuilder` you can then start a realtime replication. ```js -const replicationState = replicateGraphQL( - { - collection: myRxCollection, - // urls to the GraphQL endpoints - url: { - http: 'http://example.com/graphql', - ws: 'ws://example.com/subscriptions' // <- The websocket has to use a different url. - }, - push: { - batchSize: 100, - queryBuilder: pushQueryBuilder - }, - headers: { - Authorization: 'Bearer abcde...' - }, - pull: { - batchSize: 100, - queryBuilder: pullQueryBuilder, - streamQueryBuilder: pullStreamQueryBuilder - }, - deletedField: 'deleted' - } -); +const replicationState = replicateGraphQL({ + collection: myRxCollection, + // urls to the GraphQL endpoints + url: { + http: "http://example.com/graphql", + ws: "ws://example.com/subscriptions", // <- The websocket has to use a different url. + }, + push: { + batchSize: 100, + queryBuilder: pushQueryBuilder, + }, + headers: { + Authorization: "Bearer abcde...", + }, + pull: { + batchSize: 100, + queryBuilder: pullQueryBuilder, + streamQueryBuilder: pullStreamQueryBuilder, + }, + deletedField: "deleted", +}); ``` **NOTICE**: If it is not possible to create a websocket server on your backend, you can use any other method of pull out the ongoing events from the backend and then you can send them into `RxReplicationState.emitEvent()`. - ### Transforming null to undefined in optional fields GraphQL fills up non-existent optional values with `null` while RxDB required them to be `undefined`. Therefore, if your schema contains optional properties, you have to transform the pulled data to switch out `null` to `undefined` + ```js const replicationState: RxGraphQLReplicationState = replicateGraphQL( { collection: myRxCollection, - url: {/* ... */}, - headers: {/* ... */}, - push: {/* ... */}, + url: { + /* ... */ + }, + headers: { + /* ... */ + }, + push: { + /* ... */ + }, pull: { queryBuilder: pullQueryBuilder, - modifier: (doc => { + modifier: (doc) => { //Wwe have to remove optional non-existend field values // they are set as null by GraphQL but should be undefined Object.entries(doc).forEach(([k, v]) => { @@ -349,7 +351,7 @@ const replicationState: RxGraphQLReplicationState = replicateGraphQL( } }); return doc; - }) + }, }, /* ... */ } @@ -362,17 +364,21 @@ With the `pull.responseModifier` you can modify the whole response from the Grap For example if your endpoint is not capable of returning a valid checkpoint, but instead only returns the plain document array, you can use the `responseModifier` to aggregate the checkpoint from the returned documents. ```js -import { - -} from 'rxdb'; +import {} from "rxdb"; const replicationState: RxGraphQLReplicationState = replicateGraphQL( { collection: myRxCollection, - url: {/* ... */}, - headers: {/* ... */}, - push: {/* ... */}, + url: { + /* ... */ + }, + headers: { + /* ... */ + }, + push: { + /* ... */ + }, pull: { - responseModifier: async function( + responseModifier: async function ( plainResponse, // the exact response that was returned from the server origin, // either 'handler' if plainResponse came from the pull.handler, or 'stream' if it came from the pull.stream requestCheckpoint // if origin==='handler', the requestCheckpoint contains the checkpoint that was send to the backend @@ -384,25 +390,70 @@ const replicationState: RxGraphQLReplicationState = replicateGraphQL( const docs = plainResponse; return { documents: docs, - checkpoint: docs.length === 0 ? requestCheckpoint : { - name: lastOfArray(docs).name, - updatedAt: lastOfArray(docs).updatedAt - } + checkpoint: + docs.length === 0 + ? requestCheckpoint + : { + name: lastOfArray(docs).name, + updatedAt: lastOfArray(docs).updatedAt, + }, }; - } + }, }, /* ... */ } ); ``` +### push.responseModifier +It's also possible to modify the response of a push mutation. For example if your server returns more than the just conflicting docs: + +```graphql +type PushResponse { + conflicts: [Human] + conflictMessages: [ReplicationConflictMessage] +} + +type Mutation { + # Returns a list of all conflicts + # If no document write caused a conflict, return an empty list. + pushHuman(rows: [HumanInputPushRow!]): PushResponse! +} +``` + +```js +import {} from "rxdb"; +const replicationState: RxGraphQLReplicationState = replicateGraphQL( + { + collection: myRxCollection, + url: { + /* ... */ + }, + headers: { + /* ... */ + }, + push: { + responseModifier: async function (plainResponse) { + /** + * In this example we aggregate the conflicting documents from a response object + */ + const pullResponse = plainResponse; + return plainResponse.conflicts; + }, + }, + pull: { + /* ... */ + }, + /* ... */ + } +); +``` #### Helper Functions RxDB provides the helper functions `graphQLSchemaFromRxSchema()`, `pullQueryBuilderFromRxSchema()`, `pullStreamBuilderFromRxSchema()` and `pushQueryBuilderFromRxSchema()` that can be used to generate handlers and schemas from the `RxJsonSchema`. To learn how to use them, please inspect the [GraphQL Example](https://github.com/pubkey/rxdb/tree/master/examples/graphql). - ### RxGraphQLReplicationState When you call `myCollection.syncGraphQL()` it returns a `RxGraphQLReplicationState` which can be used to subscribe to events, for debugging or other functions. It extends the [RxReplicationState](./replication.md) with some GraphQL specific methods. @@ -413,7 +464,7 @@ Changes the headers for the replication after it has been set up. ```js replicationState.setHeaders({ - Authorization: `...` + Authorization: `...`, }); ``` @@ -422,24 +473,20 @@ replicationState.setHeaders({ The underlying fetch framework uses a `same-origin` policy for credentials per default. That means, cookies and session data is only shared if you backend and frontend run on the same domain and port. Pass the credential parameter to `include` cookies in requests to servers from different origins via: ```js -replicationState.setCredentials('include'); +replicationState.setCredentials("include"); ``` or directly pass it in the the `syncGraphQL`: ```js -replicateGraphQL( - { - collection: myRxCollection, - /* ... */ - credentials: 'include', - /* ... */ - } -); +replicateGraphQL({ + collection: myRxCollection, + /* ... */ + credentials: "include", + /* ... */ +}); ``` See [the fetch spec](https://fetch.spec.whatwg.org/#concept-request-credentials-mode) for more information about available options. - **NOTICE:** To play around, check out the full example of the RxDB [GraphQL replication with server and client](https://github.com/pubkey/rxdb/tree/master/examples/graphql) - From c61c56f85581f58bb486ba72f97e7189df12c6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Thu, 2 Feb 2023 00:01:26 +0100 Subject: [PATCH 6/9] Update replication-graphql.md --- docs-src/replication-graphql.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs-src/replication-graphql.md b/docs-src/replication-graphql.md index 31fb0b141c9..10dc01a8c96 100644 --- a/docs-src/replication-graphql.md +++ b/docs-src/replication-graphql.md @@ -416,8 +416,7 @@ type PushResponse { } type Mutation { - # Returns a list of all conflicts - # If no document write caused a conflict, return an empty list. + # Returns a PushResponse type that contains the conflicts along with other information pushHuman(rows: [HumanInputPushRow!]): PushResponse! } ``` From e1a9e45494c2fd259eba0f74d20942af3dae434e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Thu, 2 Feb 2023 11:31:16 +0100 Subject: [PATCH 7/9] Removed some auto formatting changes in docs --- docs-src/replication-graphql.md | 201 +++++++++++++++----------------- 1 file changed, 95 insertions(+), 106 deletions(-) diff --git a/docs-src/replication-graphql.md b/docs-src/replication-graphql.md index 10dc01a8c96..c7212bdec76 100644 --- a/docs-src/replication-graphql.md +++ b/docs-src/replication-graphql.md @@ -131,8 +131,8 @@ const pullQueryBuilder = (checkpoint, limit) => { */ if (!checkpoint) { checkpoint = { - id: "", - updatedAt: 0, + id: '', + updatedAt: 0 }; } const query = `query PullHuman($checkpoint: CheckpointInput, $limit: Int!) { @@ -154,8 +154,8 @@ const pullQueryBuilder = (checkpoint, limit) => { query, variables: { checkpoint, - limit, - }, + limit + } }; }; ``` @@ -207,7 +207,7 @@ const replicationState = replicateGraphQL( For the push-replication, you also need a `queryBuilder`. Here, the builder receives a changed document as input which has to be send to the server. It also returns a GraphQL-Query and its data. ```js -const pushQueryBuilder = (rows) => { +const pushQueryBuilder = rows => { const query = ` mutation PushHuman($writeRows: [HumanInputPushRow!]) { pushHuman(writeRows: $writeRows) { @@ -220,11 +220,11 @@ const pushQueryBuilder = (rows) => { } `; const variables = { - writeRows: rows, + writeRows: rows }; return { query, - variables, + variables }; }; ``` @@ -232,37 +232,39 @@ const pushQueryBuilder = (rows) => { With the queryBuilder, you can then setup the push-replication. ```js -const replicationState = replicateGraphQL({ - collection: myRxCollection, - // urls to the GraphQL endpoints - url: { - http: "http://example.com/graphql", - }, - push: { - queryBuilder: pushQueryBuilder, // the queryBuilder from above - /** - * batchSize (optional) - * Amount of document that will be pushed to the server in a single request. - */ - batchSize: 5, - /** - * modifier (optional) - * Modifies all pushed documents before they are send to the GraphQL endpoint. - * Returning null will skip the document. - */ - modifier: (doc) => doc, - dataPath: undefined, // (optional) specifies the object path to access the conflicting document(s). Otherwise, the first result of the response data is used. - }, - headers: { - Authorization: "Bearer abcde...", - }, - pull: { +const replicationState = replicateGraphQL( + { + collection: myRxCollection, + // urls to the GraphQL endpoints + url: { + http: 'http://example.com/graphql' + }, + push: { + queryBuilder: pushQueryBuilder, // the queryBuilder from above + /** + * batchSize (optional) + * Amount of document that will be pushed to the server in a single request. + */ + batchSize: 5, + /** + * modifier (optional) + * Modifies all pushed documents before they are send to the GraphQL endpoint. + * Returning null will skip the document. + */ + modifier: doc => doc + }, + headers: { + Authorization: 'Bearer abcde...' + }, + pull: { + /* ... */ + }, /* ... */ - }, - /* ... */ -}); + } +); ``` + #### Pull Stream To create a **realtime** replication, you need to create a pull stream that pulls ongoing writes from the server. @@ -288,8 +290,8 @@ const pullStreamQueryBuilder = (headers) => { return { query, variables: { - headers, - }, + headers + } }; }; ``` @@ -297,52 +299,48 @@ const pullStreamQueryBuilder = (headers) => { With the `pullStreamQueryBuilder` you can then start a realtime replication. ```js -const replicationState = replicateGraphQL({ - collection: myRxCollection, - // urls to the GraphQL endpoints - url: { - http: "http://example.com/graphql", - ws: "ws://example.com/subscriptions", // <- The websocket has to use a different url. - }, - push: { - batchSize: 100, - queryBuilder: pushQueryBuilder, - }, - headers: { - Authorization: "Bearer abcde...", - }, - pull: { - batchSize: 100, - queryBuilder: pullQueryBuilder, - streamQueryBuilder: pullStreamQueryBuilder, - }, - deletedField: "deleted", -}); +const replicationState = replicateGraphQL( + { + collection: myRxCollection, + // urls to the GraphQL endpoints + url: { + http: 'http://example.com/graphql', + ws: 'ws://example.com/subscriptions' // <- The websocket has to use a different url. + }, + push: { + batchSize: 100, + queryBuilder: pushQueryBuilder + }, + headers: { + Authorization: 'Bearer abcde...' + }, + pull: { + batchSize: 100, + queryBuilder: pullQueryBuilder, + streamQueryBuilder: pullStreamQueryBuilder + }, + deletedField: 'deleted' + } +); ``` **NOTICE**: If it is not possible to create a websocket server on your backend, you can use any other method of pull out the ongoing events from the backend and then you can send them into `RxReplicationState.emitEvent()`. + ### Transforming null to undefined in optional fields GraphQL fills up non-existent optional values with `null` while RxDB required them to be `undefined`. Therefore, if your schema contains optional properties, you have to transform the pulled data to switch out `null` to `undefined` - ```js const replicationState: RxGraphQLReplicationState = replicateGraphQL( { collection: myRxCollection, - url: { - /* ... */ - }, - headers: { - /* ... */ - }, - push: { - /* ... */ - }, + url: {/* ... */}, + headers: {/* ... */}, + push: {/* ... */}, pull: { queryBuilder: pullQueryBuilder, - modifier: (doc) => { + modifier: (doc => { //Wwe have to remove optional non-existend field values // they are set as null by GraphQL but should be undefined Object.entries(doc).forEach(([k, v]) => { @@ -351,7 +349,7 @@ const replicationState: RxGraphQLReplicationState = replicateGraphQL( } }); return doc; - }, + }) }, /* ... */ } @@ -364,21 +362,17 @@ With the `pull.responseModifier` you can modify the whole response from the Grap For example if your endpoint is not capable of returning a valid checkpoint, but instead only returns the plain document array, you can use the `responseModifier` to aggregate the checkpoint from the returned documents. ```js -import {} from "rxdb"; +import { + +} from 'rxdb'; const replicationState: RxGraphQLReplicationState = replicateGraphQL( { collection: myRxCollection, - url: { - /* ... */ - }, - headers: { - /* ... */ - }, - push: { - /* ... */ - }, + url: {/* ... */}, + headers: {/* ... */}, + push: {/* ... */}, pull: { - responseModifier: async function ( + responseModifier: async function( plainResponse, // the exact response that was returned from the server origin, // either 'handler' if plainResponse came from the pull.handler, or 'stream' if it came from the pull.stream requestCheckpoint // if origin==='handler', the requestCheckpoint contains the checkpoint that was send to the backend @@ -390,15 +384,12 @@ const replicationState: RxGraphQLReplicationState = replicateGraphQL( const docs = plainResponse; return { documents: docs, - checkpoint: - docs.length === 0 - ? requestCheckpoint - : { - name: lastOfArray(docs).name, - updatedAt: lastOfArray(docs).updatedAt, - }, + checkpoint: docs.length === 0 ? requestCheckpoint : { + name: lastOfArray(docs).name, + updatedAt: lastOfArray(docs).updatedAt + } }; - }, + } }, /* ... */ } @@ -426,12 +417,8 @@ import {} from "rxdb"; const replicationState: RxGraphQLReplicationState = replicateGraphQL( { collection: myRxCollection, - url: { - /* ... */ - }, - headers: { - /* ... */ - }, + url: {/* ... */}, + headers: {/* ... */}, push: { responseModifier: async function (plainResponse) { /** @@ -441,9 +428,7 @@ const replicationState: RxGraphQLReplicationState = replicateGraphQL( return plainResponse.conflicts; }, }, - pull: { - /* ... */ - }, + pull: {/* ... */}, /* ... */ } ); @@ -453,6 +438,7 @@ const replicationState: RxGraphQLReplicationState = replicateGraphQL( RxDB provides the helper functions `graphQLSchemaFromRxSchema()`, `pullQueryBuilderFromRxSchema()`, `pullStreamBuilderFromRxSchema()` and `pushQueryBuilderFromRxSchema()` that can be used to generate handlers and schemas from the `RxJsonSchema`. To learn how to use them, please inspect the [GraphQL Example](https://github.com/pubkey/rxdb/tree/master/examples/graphql). + ### RxGraphQLReplicationState When you call `myCollection.syncGraphQL()` it returns a `RxGraphQLReplicationState` which can be used to subscribe to events, for debugging or other functions. It extends the [RxReplicationState](./replication.md) with some GraphQL specific methods. @@ -463,7 +449,7 @@ Changes the headers for the replication after it has been set up. ```js replicationState.setHeaders({ - Authorization: `...`, + Authorization: `...` }); ``` @@ -472,20 +458,23 @@ replicationState.setHeaders({ The underlying fetch framework uses a `same-origin` policy for credentials per default. That means, cookies and session data is only shared if you backend and frontend run on the same domain and port. Pass the credential parameter to `include` cookies in requests to servers from different origins via: ```js -replicationState.setCredentials("include"); +replicationState.setCredentials('include'); ``` or directly pass it in the the `syncGraphQL`: ```js -replicateGraphQL({ - collection: myRxCollection, - /* ... */ - credentials: "include", - /* ... */ -}); +replicateGraphQL( + { + collection: myRxCollection, + /* ... */ + credentials: 'include', + /* ... */ + } +); ``` See [the fetch spec](https://fetch.spec.whatwg.org/#concept-request-credentials-mode) for more information about available options. + **NOTICE:** To play around, check out the full example of the RxDB [GraphQL replication with server and client](https://github.com/pubkey/rxdb/tree/master/examples/graphql) From f70a316f3f85163586facd065bf0debfd2af7edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Thu, 2 Feb 2023 11:32:26 +0100 Subject: [PATCH 8/9] Update replication.d.ts --- src/types/plugins/replication.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types/plugins/replication.d.ts b/src/types/plugins/replication.d.ts index be1bcf781e7..4a3ba4cb4df 100644 --- a/src/types/plugins/replication.d.ts +++ b/src/types/plugins/replication.d.ts @@ -87,8 +87,6 @@ export type ReplicationPushOptions = { */ modifier?: (docData: WithDeleted) => MaybePromise; - - /** * How many local changes to process at once. */ From c5af8271b4433c9e1222f1428c847562358e3bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9once=20Aklin?= Date: Thu, 2 Feb 2023 13:38:10 +0100 Subject: [PATCH 9/9] Corrections in docs - Changed from 'js' to 'ts' in code blocks that explain "pull.responseModifier" and "push.responseModifier" - Removed unused variable definition with wrong name --- docs-src/replication-graphql.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs-src/replication-graphql.md b/docs-src/replication-graphql.md index c7212bdec76..0f69001c4da 100644 --- a/docs-src/replication-graphql.md +++ b/docs-src/replication-graphql.md @@ -361,7 +361,7 @@ const replicationState: RxGraphQLReplicationState = replicateGraphQL( With the `pull.responseModifier` you can modify the whole response from the GraphQL endpoint **before** it is processed by RxDB. For example if your endpoint is not capable of returning a valid checkpoint, but instead only returns the plain document array, you can use the `responseModifier` to aggregate the checkpoint from the returned documents. -```js +```ts import { } from 'rxdb'; @@ -412,7 +412,7 @@ type Mutation { } ``` -```js +```ts import {} from "rxdb"; const replicationState: RxGraphQLReplicationState = replicateGraphQL( { @@ -424,7 +424,6 @@ const replicationState: RxGraphQLReplicationState = replicateGraphQL( /** * In this example we aggregate the conflicting documents from a response object */ - const pullResponse = plainResponse; return plainResponse.conflicts; }, },