Created
May 26, 2020 10:47
-
-
Save craicoverflow/a165dbb42688e26d794f6f0a18bd2ebc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { GraphQLObjectType } from 'graphql'; | |
import { NoDataError } from '@graphback/runtime'; | |
import { getDatabaseArguments } from '@graphback/core'; | |
import { ObjectId } from 'mongodb'; | |
import { MongoDBDataProvider } from './MongoDBDataProvider'; | |
enum GraphbackDirective { | |
UpdatedAt = 'updatedAt' | |
} | |
interface FieldDirectiveTransformer { | |
name: string | |
fieldName: string | |
fieldTransform: Function | |
} | |
/** | |
* Go through all fields in the model and build up a list of directives in each | |
* Each directive has a fieldTransform field that binds some logic to that field | |
* @param modelType Model | |
*/ | |
function createDirectiveTransformers(modelType: GraphQLObjectType): FieldDirectiveTransformer[] { | |
const modelFields = Object.values(modelType.getFields()) | |
const fieldDirectiveTransformers: FieldDirectiveTransformer[] = [] | |
for (const field of modelFields) { | |
const fieldDirectives = field.astNode.directives | |
for (const directive of fieldDirectives) { | |
if (directive.name.value === 'updatedAt') { | |
fieldDirectiveTransformers.push({ | |
name: GraphbackDirective.UpdatedAt, | |
fieldName: field.name, | |
fieldTransform: () => { | |
return (new Date()) | |
} | |
}) | |
} | |
} | |
} | |
return fieldDirectiveTransformers | |
} | |
/** | |
* Mongo provider that contains special handlers for offix conflict resolution format: | |
* | |
* https://offix.dev/docs/conflict-server#structure-of-the-conflict-error | |
*/ | |
export class OffixMongoDBDataProvider<Type = any, GraphbackContext = any> extends MongoDBDataProvider<Type, GraphbackContext> { | |
protected fieldDirectiveTransformers: FieldDirectiveTransformer[] | |
public constructor(baseType: GraphQLObjectType, client: any) { | |
super(baseType, client); | |
this.fieldDirectiveTransformers = createDirectiveTransformers(baseType) | |
} | |
public async update(data: any): Promise<Type> { | |
const { idField } = getDatabaseArguments(this.tableMap, data); | |
if (!idField.value) { | |
throw new NoDataError(`Cannot update ${this.collectionName} - missing ID field`) | |
} | |
// TODO Can be improved by conditional updates | |
const queryResult = await this.db.collection(this.collectionName).find({ _id: new ObjectId(idField.value) }).toArray(); | |
if (queryResult && queryResult[0]) { | |
queryResult[0][idField.name] = queryResult[0]._id; | |
if (data.version !== queryResult[0].version) { | |
const conflictError: any = new Error(); | |
conflictError.conflictInfo = { serverState: queryResult[0], clientState: data }; | |
throw conflictError | |
} | |
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands | |
data.version = data.version + 1; | |
// get the updated at directive if it exists | |
const updatedAtDirective = this.fieldDirectiveTransformers.find((v => v.name === GraphbackDirective.UpdatedAt)) | |
// apply updated at directive | |
// all logic to apply this is in the FieldDirectiveTransformer | |
if (updatedAtDirective) { | |
document[updatedAtDirective.fieldName] = updatedAtDirective.fieldTransform() | |
} | |
// TODO use findOneAndUpdate to check consistency afterwards | |
const result = await this.db.collection(this.collectionName).updateOne({ _id: new ObjectId(idField.value) }, { $set: data }); | |
if (result.result?.ok) { | |
return this.mapFields(data); | |
} | |
} | |
throw new NoDataError(`Cannot update ${this.collectionName}`); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment