{"id":13465512,"url":"https://github.com/NicholasBellucci/SociableWeaver","last_synced_at":"2025-03-25T16:32:05.854Z","repository":{"id":36511666,"uuid":"224062140","full_name":"NicholasBellucci/SociableWeaver","owner":"NicholasBellucci","description":"Build declarative GraphQL queries in Swift.","archived":false,"fork":false,"pushed_at":"2022-04-12T17:22:51.000Z","size":224,"stargazers_count":77,"open_issues_count":0,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-27T11:06:24.279Z","etag":null,"topics":["domain-specific-language","function-builder","graphql","swift","swift-package-manager","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","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/NicholasBellucci.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-25T23:39:39.000Z","updated_at":"2024-10-12T16:00:11.000Z","dependencies_parsed_at":"2022-08-08T15:16:53.044Z","dependency_job_id":null,"html_url":"https://github.com/NicholasBellucci/SociableWeaver","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NicholasBellucci%2FSociableWeaver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NicholasBellucci%2FSociableWeaver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NicholasBellucci%2FSociableWeaver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NicholasBellucci%2FSociableWeaver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NicholasBellucci","download_url":"https://codeload.github.com/NicholasBellucci/SociableWeaver/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222088564,"owners_count":16928978,"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":["domain-specific-language","function-builder","graphql","swift","swift-package-manager","xcode"],"created_at":"2024-07-31T15:00:31.425Z","updated_at":"2024-10-29T17:31:06.433Z","avatar_url":"https://github.com/NicholasBellucci.png","language":"Swift","funding_links":[],"categories":["Libs","Data Management [🔝](#readme)","Swift","Data and Storage","GraphQL"],"sub_categories":["Data Management"],"readme":"# SociableWeaver\n\nSwift meets GraphQL in this lightweight, easy to use framework. SociableWeaver uses a declarative style of programming and makes GraphQL queries look natural in Swift code. Through the use of Swift 5.1 function builders and `CodingKeys`, SociableWeaver removes all of the need for unsafe strings and Dictionaries when creating objects and fields.\n\n## Requirements\nXcode 11.x or a Swift 5.1x toolchain with Swift Package Manager.\n\n## Installation\n\n### Swift Package Manager\nFor projects using a `.xcodeproj` the best method is to navigate to `File \u003e Swift Packages \u003e Add Package Dependency...`. From there just simply enter `https://github.com/NicholasBellucci/SociableWeaver` as the package repository url and use the master branch or the most recent version. Master will always be inline with the newest release. The other method is to simply add `.package(url: \"https://github.com/NicholasBellucci/SociableWeaver.git\", from: \"0.1.0\")` to your `Package.swift` file's `dependencies`.\n\n### Carthage\n\nAdd the following entry to your `Cartfile` and run `$ carthage update SociableWeaver`\n\n```\ngithub \"NicholasBellucci/SociableWeaver\"\n```\n\n## Table of Contents\n   * [Objects and Fields](#objects-and-fields)\n   * [Arguments](#arguments)\n        * [Optionals](#optionals)\n   * [Alias](#alias)\n   * [Fragments](#fragments)\n   * [Operation Name](#operation-name)\n   * [Variables](#variables)\n   * [Directives](#directives)\n   * [Mutations](#mutations)\n   * [Inline Fragments](#inline-fragments)\n   * [Meta Fields](#meta-fields)\n   * [Pagination](#pagination)\n   * [Custom Types](#custom-types)\n        * [ForEachWeavable](#foreachweavable)\n        * [BuilderType](#buildertype)\n        * [CaseStyleOption](#casestyleoption)\n        * [EnumValueRepresentable](#enumvaluerepresentable)\n\n## Usage\n\nSociableWeaver supports all that GraphQL has to offer. In order to get everything out of this framework, just make sure that any `Codable` models used contain `CodingKeys`. For example: \n\n```swift\npublic struct Post: Codable {\n    public enum CodingKeys: String, CodingKey, CaseIterable {\n        case id, title, content\n    }\n\n    public let id: String\n    public let title: String\n    public let content: String\n\n    public init(id: String, title: String, content: String) {\n        self.id = id\n        self.title = title\n        self.content = content\n    }\n}\n```\n\nIf `CodingKeys` aren't possible, SociableWeaver does support strings. It is highly recommended this be used as a last resort as it will make queries more difficult to manage.\n\n### Objects and Fields\n[GraphQL Fields](https://graphql.org/learn/queries/#fields)\n\nGraphQL is all about querying specific fields on objects and returning only what is needed. With SociableWeaver constructing objects with fields is a breeze.\n\n##### Swift\n```swift\nWeave(.query) {\n    Object(Post.self) {\n        Field(Post.CodingKeys.id)\n        Field(Post.CodingKeys.title)\n        Field(Post.CodingKeys.content)\n    }\n}\n```\n\n##### GraphQL Query\n```graphql\nquery {\n    post {\n        id\n        title\n        content\n    }\n}\n```\n\n### Arguments\n\n[GraphQL Arguments](https://graphql.org/learn/queries/#arguments)\n\nArguments are a key part of GraphQL and allow for much more refined queries. SociableWeaver supports arguments on both objects and fields.\n\nThe only requirement is that the value for the argument conforms to `ArgumentValueRepresentable`. Core types such as `String`, `Int`, `Bool` etc. will already conform. Enumerations will need to conform to the [EnumValueRepresentable](https://github.com/NicholasBellucci/SociableWeaver#enumvaluerepresentable) protocol.\n\n##### Swift\n```swift\nWeave(.query) {\n    Object(Post.self) {\n        Field(Post.CodingKeys.title)\n\n        Object(Post.CodingKeys.author) {\n            Field(Author.CodingKeys.id)\n            Field(Author.CodingKeys.name)\n                .argument(key: \"lastName\", value: \"Doe\")\n        }\n\n        Object(Post.CodingKeys.comments) {\n            Field(Comment.CodingKeys.id)\n            Field(Comment.CodingKeys.content)\n        }\n        .argument(key: \"filter\", value: CommentFilter.recent)\n    }\n}\n```\n\n##### GraphQL Query\n```graphql\nquery {\n    post {\n        title\n        author {\n            id\n            name(lastName: \"Doe\")\n        }\n        comments(filter: RECENT) {\n            id\n            content\n        }\n    }\n}\n```\n\n#### Optionals\n\nOptionals are supported and can be included in the query. In the instance where an optional should be included and the value is nil, the resulting GraphQL value will be `null`.\n\nIn order to include an optional make sure to get the argument value of the property without including a `?`. This will result in a query param of `age: null`.\n```swift\npublic struct Author: Codable {\n    public enum CodingKeys: String, CodingKey, CaseIterable {\n        case id, name, age, birthplace\n    }\n\n    ...\n    public let age: Int?\n    ...\n}\n\nextension Author: ArgumentValueRepresentable {\n    public var argumentValue: String {\n        var params: [String: String?] = [:]\n\n        ...\n        params[\"age\"] = age.argumentValue\n        ...\n\n        let paramStrings: [String] = params.compactMap { argument in\n            guard let value = argument.value else {\n                return nil\n            }\n\n            return \"\\(argument.key): \\(value)\"\n        }\n\n        return \"{ \\(paramStrings.joined(separator: \",\")) }\"\n    }\n}'\n```\n\n### Alias\n\n[GraphQL Alias](https://graphql.org/learn/queries/#aliases)\n\nAliases are key when querying a single object multiple times in the same request.\n\n##### Swift\n```swift\nWeave(.query) {\n    Object(Post.self) {\n        Object(Post.CodingKeys.comments) {\n            Field(Comment.CodingKeys.id)\n            Field(Comment.CodingKeys.content)\n        }\n        .argument(key: \"filter\", value: CommentFilter.recent)\n        .alias(\"newComments\")\n        \n        Object(Post.CodingKeys.comments) {\n            Field(Comment.CodingKeys.id)\n            Field(Comment.CodingKeys.content)\n        }\n        .argument(key: \"filter\", value: CommentFilter.old)\n        .alias(\"oldComments\")\n    }\n}\n```\n\n##### GraphQL Query\n\n```graphql\nquery {\n    post {\n        newComments: comments(filter: RECENT) {\n            id\n            content\n        }\n        oldComments: comments(filter: OLD) {\n            id\n            content\n        }\n    }\n}\n```\n\n### Fragments\n\n[GraphQL Fragments](https://graphql.org/learn/queries/#fragments)\n\nGraphQL fragments can help when building complicated queries. SociableWeaver makes them extremely simple and allows the proper references to be placed exactly where they would be in the query. With the help of a `FragmentBuilder` the `FragmentReference` can be added to the objects that require the fields and the `Fragment` can be added to the operation itself.\n\n##### Swift\n```swift\nlet authorFragment = FragmentBuilder(name: \"authorFields\", type: Author.self)\nlet query = Weave(.query) {\n    Object(Post.self) {\n        Object(Post.CodingKeys.author) {\n            FragmentReference(for: authorFragment)\n        }\n\n        Object(Post.CodingKeys.comments) {\n            Field(Comment.CodingKeys.content)\n            \n            Object(Comment.CodingKeys.author) {\n                FragmentReference(for: authorFragment)\n            }\n        }\n    }\n\n    Fragment(authorFragment) {\n        Field(Author.CodingKeys.id)\n        Field(Author.CodingKeys.name)\n    }\n}\n```\n\n##### GraphQL Query\n```graphql\nquery {\n  post {\n    author {\n      ...authorFields\n    }\n    comments {\n      content\n      author {\n        ...authorFields\n      }\n    }\n  }\n}\n\nfragment authorFields on Author {\n  id\n  name\n}\n```\n\n### Operation Name\n\n[GraphQL Operation Name](https://graphql.org/learn/queries/#operation-name)\n\nOperation names aren't required but can make the queries more unique.\n\n```swift\nWeave(.query) {\n    Object(Post.self) {\n        Field(Post.CodingKeys.id)\n        Field(Post.CodingKeys.title)\n        Field(Post.CodingKeys.content)\n    }\n}\n.name(\"GetPostAndContent\")\n```\n\n##### GraphQL Query\n```graphql\nquery GetPost {\n  post {\n    id\n    title\n    content\n  }\n}\n```\n\n### Variables\n\n[GraphQL Variables](https://graphql.org/learn/queries/#variables)\n\nSince direct JSON is not needed when making queries in SociableWeaver, variables can and should be define in a method and passed into the query as arguments.\n\n##### Swift\n```swift\nqueryPost(id: 1)\n\nfunc queryPost(id: Int) {\n    Weave(.query) {\n        Object(Post.self) {\n            Field(Post.CodingKeys.title)\n            Field(Post.CodingKeys.content)\n            \n            Object(Post.CodingKeys.author) {\n                Field(Author.CodingKeys.id)\n                Field(Author.CodingKeys.name)\n            }\n        }\n        .argument(key: \"id\", value: id)\n    }\n}\n```\n\n##### GraphQL Query\n```graphql\nquery {\n  post(id: 1) {\n    title\n    content\n    author {\n      id\n      name\n    }\n  }\n}\n```\n\n### Directives\n\n[GraphQL Directives](https://graphql.org/learn/queries/#directives)\n\nDirectives in GraphQL allows the server to affect execution of the query. The two directives are `@include` and `@skip` both of which can be added to fields or included fragments. The example defines true or false but in an actual query these values would be boolean variables.\n\nJust to note, Skip will always take precedent over include. Also any objects/fragments that end up not having fields will be removed from the query.\n\n```swift\nlet query = Weave(.query) {\n    Object(Post.self) {\n        Field(Post.CodingKeys.title)\n        Field(Post.CodingKeys.content)\n            .include(if: true)\n\n        Object(Post.CodingKeys.author) {\n            Field(Author.CodingKeys.name)\n        }\n        .include(if: false)\n\n        Object(Post.CodingKeys.comments) {\n            Field(Comment.CodingKeys.content)\n                .include(if: true)\n                .skip(if: true)\n                \n            Object(Comment.CodingKeys.author) {\n                Field(Author.CodingKeys.name)\n                    .skip(if: true)\n            }\n        }\n    }\n}\n```\n\n##### GraphQL Queries\n```graphql\nquery { \n    post { \n        title \n        content \n    } \n}\n```\n\n### Mutations\n\n[GraphQL Mutations](https://graphql.org/learn/queries/#mutations)\n\nMutations work the same as simple queries and should be used when data is supposed to be written. An `Object.schemaName` will replace the name of the Object or Key included in the initializer.\n\n##### Swift\n```swift\nWeave(.mutation) {\n    Object(Post.self) {\n        Field(Post.CodingKeys.id)\n        Field(Post.CodingKeys.title)\n        Field(Post.CodingKeys.content)\n    }\n    .schemaName(\"createPost\")\n    .argument(key: \"title\", value: \"TestPost\")\n    .argument(key: \"content\", value: \"This is a test post.\")\n    .argument(key: \"author\", value: \"John Doe\")\n}\n```\n\n##### GraphQL Mutation\n```graphql\nmutation {\n  createPost(title: \"TestPost\", content: \"This is a test post.\", author: \"John Doe\") {\n    id\n    title\n    content\n  }\n}\n```\n\n### Inline Fragments\n\n[GraphQL Inline Fragments](https://graphql.org/learn/queries/#inline-fragments)\n\nInline fragments are useful when querying on an interface or union type as they allow the return of underlying types.\n\n##### Swift\n```swift\nWeave(.query) {\n    Object(Post.self) {\n        Field(Post.CodingKeys.title)\n        Field(Post.CodingKeys.content)\n\n        Object(Post.CodingKeys.comments) {\n            Field(Comment.CodingKeys.content)\n        \n            Object(Comment.CodingKeys.author) {\n                InlineFragment(\"AnonymousUser\") {\n                    Field(Author.CodingKeys.id)\n                }\n\n                InlineFragment(\"RegisteredUser\") {\n                    Field(Author.CodingKeys.id)\n                    Field(Author.CodingKeys.name)\n                }\n            }\n        }\n    }\n}\n```\n\n##### GraphQL Query\n```graphql\nquery {\n  post {\n    title\n    content\n    comments {\n      content\n      author {\n        ... on AnonymousUser {\n          id\n        }\n        ... on RegisteredUser {\n          id\n          name\n        }\n      }\n    }\n  }\n}\n```\n\n### Meta Fields\n\n[GraphQL Meta Fields](https://graphql.org/learn/queries/#meta-fields)\n\nGraphQL meta fields can be customized and are recognized to have two proceeding underscores. The `__typename` meta field is a GraphQL default and can be used to return the object type in the results of a query.\n\nCustom meta fields can be defined by using `MetaFieldType.custom`. This enum takes an associated String which does not need to include the double underscores before the name. For example: `.custom(\"schema\")` results in `__schema`.\n\n##### Swift\n```swift\nWeave(.query) {\n    Object(Post.self){\n        Field(Post.CodingKeys.title)\n        Field(Post.CodingKeys.content)\n\n        Object(Post.CodingKeys.author) {\n            MetaField(.typename)\n            Field(Author.CodingKeys.name)\n        }\n    }\n}\n```\n\n##### GraphQL Query\n```graphql\nquery {\n  post {\n    title\n    content\n    author {\n      __typename\n      name\n    }\n  }\n}\n```\n\n### Pagination\n\n[GraphQL Pagination](https://graphql.org/learn/pagination/)\n\nSociableWeaver support pagination out of the box and can be easily customized. Features supported include slicing, edges, and page info inclusion.\n\n#### Slicing\n\nSlicing in GraphQL is great for fetching a specified amount of objects in a response. With SociableWeaver this can be specified with the `Object.slice` method.\n\n##### Swift\n```swift\nWeave(.query) {\n    Object(Post.CodingKeys.comments) {\n        Field(Comment.CodingKeys.id)\n        Field(Comment.CodingKeys.author)\n        Field(Comment.CodingKeys.content)\n    }\n    .slice(amount: 2)\n}\n```\n\n##### GraphQL Query\n```graphql\n{\n  comments(first: 2) {\n    id\n    author\n    content\n  }\n}\n```\n\n#### Cursor-Based Pagination\n\nCursor-based pagination is described as being the most powerful pagination type GraphQL provides. Setup this pagination by declaring the pagination type for an object.\n\n##### Swift\n```swift\nWeave(.query) {\n    Object(Post.CodingKeys.comments) {\n        Field(Comment.CodingKeys.id)\n        Field(Comment.CodingKeys.author)\n        Field(Comment.CodingKeys.content)\n    }\n    .slice(amount: 2)\n    .paginationType(.cursor)\n}\n```\n\n##### GraphQL Query\n```graphql\n{\n  comments(first: 2) {\n    edges {\n      cursor\n      node {\n        id\n        author\n        content\n      }\n    }\n  }\n}\n```\n\n#### Pagination Page Info\n\nIncluding page info such as whether or not there is a next page or the end cursor is very flexible and supports a custom model.\n\n##### Swift\n```swift\nWeave(.query) {\n    Object(Post.CodingKeys.comments) {\n        Field(Comment.CodingKeys.id)\n        Field(Comment.CodingKeys.author)\n        Field(Comment.CodingKeys.content)\n    }\n    .slice(amount: 2)\n    .paginationType(.cursor)\n    .pageInfo(type: PageInfo.self,\n              keys: PageInfo.CodingKeys.startCursor,\n                    PageInfo.CodingKeys.endCursor,\n                    PageInfo.CodingKeys.hasNextPage)\n}\n```\n\n##### GraphQL Query\n```graphql\n{\n  comments(first: 2) {\n    edges {\n      cursor\n      node {\n        id\n        author\n        content\n      }\n    }\n    pageInfo {\n      startCursor\n      endCursor\n      hasNextPage\n    }\n  }\n}\n```\n\n### Custom Types\n\nSociableWeaver provides a couple of custom types that help to build more natural looking queries. These types may or may not have been included in examples but will also be defined in this section to provide more clarity.\n\n#### ForEachWeavable\n\nThe ForEachWeavable struct was added for times where you may want to add objects or fields in a loop. More discussion around this use case can be found [here](https://github.com/NicholasBellucci/SociableWeaver/issues/42).\n\n##### Swift\n```swift\nlet authors = [\n    Author(id: \"1\", name: \"John\", age: 17, birthplace: [:]),\n    Author(id: \"2\", name: \"Jane\", age: 29, birthplace: [:]),\n    Author(id: \"3\", name: \"Adam\", age: 41, birthplace: [:])\n]\n\nlet query = Weave(.query) {\n    ForEachWeavable(authors) { author in\n        Object(\"postsForAuthor\") {\n            Field(Author.CodingKeys.id)\n            Field(Author.CodingKeys.name)\n            Field(Author.CodingKeys.age)\n            Field(Author.CodingKeys.birthplace)\n        }\n        .argument(key: \"id\", value: author.id)\n    }\n}\n```\n\n##### GraphQL Query\n```graphql\n{\n  postsForAuthor(id: \"1\") {\n    id\n    name\n    age\n    birthplace\n  }\n  postsForAuthor(id: \"2\") {\n    id\n    name\n    age\n    birthplace\n  }\n  postsForAuthor(id: \"3\") {\n    id\n    name\n    age\n    birthplace\n  }\n}\n```\n\n#### BuilderType\n\nDue to current limitations with function builders, individual elements are not currently accepted. For that reason each function builder initializer has a corresponding initializer for a single element. `BuilderType.individual` has been set up to specify when an object or fragment will consist of only one element. The default value for the `builderType` parameter on all initializations is `.individual`. This means that passing it is not required and will result in the same outcome.\n\n```swift\nObject(Post.CodingKeys.author) {\n    Field(Author.CodingKeys.name)\n}\n\nFragment(authorFragment, .individual) {\n    Field(Author.CodingKeys.name)\n}\n```\n\n#### CaseStyleOption\n\nThis enumeration has been provided to allow for customization when it comes to object and fields that are initialized with a model or coding key. Defaulted to camel case.\n\n```swift\nField(Comment.CodingKeys.createdAt)\n    .caseStyle(.lowercase)\n\npublic enum CaseStyleOption {\n    case lowercase\n    case uppercase\n    case capitalized\n    case camelCase\n    case pascalCase\n    case snakeCase\n    case kebabCase\n}\n```\n\n#### EnumValueRepresentable\n\nGraphQL enumeration values are represented as uppercase representations of the case names. For this reason, custom enumerations in swift that should be passed as argument values can conform to `EnumValueRepresentable`. This protocol conforms to `ArgumentValueRepresentable` and is extended to provide the `argumentValue` as an uppercase version of the case value.\n\n```swift\nenum PostCategories: EnumValueRepresentable {\n    case art\n    case music\n    case technology\n}\n\n\nObject(Post.self) {\n    ...\n}\n.argument(key: \"category\", value: PostCategories.technology)\n\n/// Result: post(category: TECHNOLOGY) { ... }\n```\n\n## License\n\nSociableWeaver is, and always will be, MIT licensed. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNicholasBellucci%2FSociableWeaver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNicholasBellucci%2FSociableWeaver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNicholasBellucci%2FSociableWeaver/lists"}