{"id":15471551,"url":"https://github.com/sammarks/objection-graphql-relay","last_synced_at":"2025-10-26T13:37:01.679Z","repository":{"id":32966493,"uuid":"147986476","full_name":"sammarks/objection-graphql-relay","owner":"sammarks","description":"Helpers for combining Objection models with GraphQL Relay","archived":false,"fork":false,"pushed_at":"2023-01-11T01:00:10.000Z","size":1457,"stargazers_count":0,"open_issues_count":22,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-19T01:15:17.180Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sammarks.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-09T02:27:57.000Z","updated_at":"2020-02-28T16:03:48.000Z","dependencies_parsed_at":"2023-01-14T22:51:49.652Z","dependency_job_id":null,"html_url":"https://github.com/sammarks/objection-graphql-relay","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sammarks%2Fobjection-graphql-relay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sammarks%2Fobjection-graphql-relay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sammarks%2Fobjection-graphql-relay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sammarks%2Fobjection-graphql-relay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sammarks","download_url":"https://codeload.github.com/sammarks/objection-graphql-relay/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246034280,"owners_count":20712851,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T02:20:35.553Z","updated_at":"2025-10-26T13:37:01.615Z","avatar_url":"https://github.com/sammarks.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![][header-image]\n\n[![CircleCI][circleci-image]][circleci-url]\n[![NPM version][npm-version]][npm-url]\n[![NPM downloads][npm-downloads]][npm-url]\n![License][license]\n![Issues][issues]\n\n`objection-graphql-relay` is a collection of helpers used for combining [Objection][objection] models \nwith [GraphQL Relay.][graphql-relay]\n\n## Get Started\n\n```sh\nnpm install --save objection-graphql-relay objection\n```\n\nBelow is a full-featured example of how to use the helpers in the library. You should\nonly use the functions you require.\n\n### Setup Models\n\n```js\nconst { Model } = require('objection')\nconst { relayModel } = require('objection-graphql-relay')\n\n@relayModel // Or you can use module.exports = relayModel(Card)\nclass Card extends Model {\n  static tableName = 'cards'\n  static get relationshipMappings () {\n    return {\n      tags: {\n        relation: Model.ManyToManyRelation,\n        modelClass: Tag,\n        join: {\n          from: 'cards.id',\n          to: 'tags.id',\n          through: {\n            from: 'card_tags.card_id',\n            to: 'card_tags.tag_id'\n          }\n        }\n      },\n      organization: {\n        relation: Model.BelongsToOneRelation,\n        modelClass: Organization,\n        join: {\n          from: 'cards.organization_id',\n          to: 'organizations.id'\n        }\n      }\n    }\n  }\n  paginatedTags (first, after) {\n    // Simple many-to-many or has-many relationships.\n    return this.pagedRelationQuery('tags', first, after)\n  }\n  paginatedRelatedCards (first, after) {\n    // More complicated deep relationships with an optional filter.\n    return this.pagedRelationQuery('tags.cards', first, after, (builder) =\u003e {\n      // You can leave out this entire argument if you don't need extra filtering.\n      if (builder._modelClass.tableName === Card.tableName) {\n        builder.where(`${builder._modelClass.tableName}.should_show_related`, true)\n      }\n    })\n  }\n}\n\n@relayModel\nclass Organization extends Model {\n  static tableName = 'organizations'\n  static get relationshipMappings () {\n    return {\n      cards: {\n        relation: Model.HasManyRelation,\n        modelClass: Card,\n        join: {\n          from: 'organizations.id',\n          to: 'cards.organization_id'\n        }\n      }\n    }\n  }  \n}\n\n@relayModel\nclass Tag extends Model {\n  static tableName = 'tags'\n  static get relationshipMappings () {\n    return {\n      cards: {\n        relation: Model.ManyToManyRelation,\n        modelClass: Card,\n        join: {\n          from: 'tags.id',\n          to: 'cards.id',\n          through: {\n            from: 'card_tags.tag_id',\n            to: 'card_tags.card_id'\n          }\n        }\n      }\n    }\n  }\n}\n\nmodule.exports = { Card, Tag, Organization }\n```\n\n### Setup Helper Functions\n\ncards.js\n```js\nconst getCards = (first, after) =\u003e {\n  const query = Card.query().limit(first)\n    .orderBy('id', 'asc') // Cursors are ID-based currently (will have to support sorting later).\n  if (after) {\n    return query.offset(after).range()\n  } else {\n    return query.range() // Adding .range() makes sure the result includes \"results\" and \"total\"\n  }\n}\n\nmodule.exports = { getCards }\n```\n\n### Setup Resolvers\n\ncards_resolver.js\n```js\nconst { idWrapper, connectionWrapper, singleRelationshipWrapper } = require('objection-graphql-relay')\nconst { cursorToOffset, toGlobalId } = require('graphql-relay')\nconst { getCards } = require('./cards.js')\n\nmodule.exports = {\n  Query: {\n    cards: async (parent, args) =\u003e {\n      const after = args.after ? cursorToOffset(args.after) : null\n      const cards = await getCards(args.first, after)\n      return connectionWrapper({ collectionInfo: cards, args })\n    }\n  },\n  Card: {\n    id: idWrapper(),\n    organization: singleRelationshipWrapper('organization'),\n    tags: connectionWrapper('Tags'), // This will call paginatedTags() on the model.\n    relatedCards: connectionWrapper('RelatedCards') // This will call paginatedRelatedCards() on the model.\n  }\n}\n```\n\n## API Reference\n\n### `relayModel(ModelClass): ModelClass`\n\nThis is a decorator function that attaches the proper QueryBuilder to the passed\nmodel class and adds the `pagedRelationQuery()` helper method. It returns the same\nclass with the prototype and static properties modified.\n\n### `idWrapper(modelName): function`\n\nReturns a resolver that automatically generates an ID for the parent model, using\nthe class name as the type. Alternatively, if the parent is not a model, you can\npass in the model name as the first argument.\n\n### `fromGlobalId(modelName, globalId): string`\n\nVerifies that the GraphQL ID (globalId) is of the type `modelName` and returns the\n`modelName`-specific ID. Throws an error if `globalId` is not of the type `modelName`\n\nThis function is useful for grabbing arguments on inputs where you must reference\nother nodes. It handles parsing the ID and verifying that it is the class you're expecting.\n\n### `pagedRelationQuery(instance, field, first, after, [extraFilter, orderBy]): Promise\u003cobject\u003e`\n\n_The `pagedRelationQuery()` method on the Model has the same definition minus the first argument._\n\nLoads the `field` relationship on the passed model instance (or the current model instance if\nusing the model method), limiting based on the `first` and `after` arguments. Optionally filters\nwith the `extraFilter` argument (which is an Objection relationship filter), and ordering by\nthe `orderBy` filter (which is also an Objection relationship filter).\n\n`field` can have one of two types of values:\n\n- `relationship` (example: `tags`) - This fetches the single-depth relationship. In this case,\n    the current model has multiple tags related to it. This will fetch all of those tags and\n    support pagination.\n- `relationship.otherRelationship` (example: `tags.cards`) - This fetches the multi-depth\n    relationship. In this case, the current model has multiple tags related to it, and those\n    tags relate to other `cards` (assuming the current model is a `card`). This will fetch\n    all unique `cards` (that are not the current `card`) related through their common `tags`.\n\n## Other Questions\n\n### Overriding QueryBuilder\n\nIf you have to override the QueryBuilder, make sure you extend it from the QueryBuilder\nexported from this module.\n\n### What is going on inside `pagedRelationQuery()`?\n\nThis is a rather complicated method, but the purpose of it is to help resolve\nrelationships in models. It's used if you have a many-to-many, belongs-to-many,\nor has-many relationship defined on the model. If you're interested in resolving\nrelationships for a single-model relationship, you'll want to use the \n`singleRelationshipWrapper()` function instead. With that said, here we go with\nthe explanation:\n\nFor this example, let's say we have a \"tag\" model and a \"card\" model, and they are\nrelated through a many-to-many relationship. In the example, we are trying to find\nall other cards that are related to the current card through the same tags (so the\nrelationship path would be `tags.cards` because we want all the cards related to\nthe tags that are related to the current card).\n\nTo start with, we'll explain this line:\n\n```js\nconst results = field.split('.').reduce(...)\n```\n\nThis section is responsible for gathering all of the second+-level relationships\nand flattening them into a single array, also reducing duplicates. We start by\nsplitting the field (so maybe something like \"tags.cards\" to find all other cards\nwith at least one common tag with the current card) into segments. So we end up with\n`[\"tags\", \"card\"]`, and then we reduce with those segments starting with the initial\nresult object inside an array.\n\nThen, inside the `reduce()`, we process each of the relationships for the current item,\nflatten the results, and remove duplicates. In the case of the example above, result\ncontains a key called \"tags\" (since result is a card in this example). So segment is\ncurrently \"tags\" and finalResult is currently `[result]`.\n\nWe loop through each item in finalResult and pull out the \"tags\" array, and then flatten\nto remove duplicates.\n\nIn the second iteration of this function, finalResult is equal to all of the tags in the\noriginal result (the original card), and segment is equal to \"cards.\"\n\nSo we loop through each tag, and pull out the \"cards\" inside each tag. Then we flatten\nall of them into a single array, and then remove duplicates based on the ID.\n\nOnce that's done, that'll be the final iteration of our example and we'll end up with a flat\narray of unique cards that are related to the current card through the tags the current card\nis associated with.\n\n[header-image]: https://raw.githubusercontent.com/sammarks/art/master/objection-graphql-relay/header.jpg\n[circleci-image]: https://img.shields.io/circleci/project/github/sammarks/objection-graphql-relay.svg\n[circleci-url]: https://circleci.com/gh/sammarks/objection-graphql-relay/tree/master\n[npm-version]: https://img.shields.io/npm/v/objection-graphql-relay.svg\n[npm-downloads]: https://img.shields.io/npm/dm/objection-graphql-relay.svg\n[npm-url]: https://www.npmjs.com/package/objection-graphql-relay\n[license]: https://img.shields.io/github/license/sammarks/objection-graphql-relay.svg\n[issues]: https://img.shields.io/github/issues/sammarks/objection-graphql-relay.svg\n[objection]: https://www.npmjs.com/package/objection\n[graphql-relay]: https://www.npmjs.com/package/graphql-relay\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsammarks%2Fobjection-graphql-relay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsammarks%2Fobjection-graphql-relay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsammarks%2Fobjection-graphql-relay/lists"}