{"id":25397239,"url":"https://github.com/domkm/serene","last_synced_at":"2025-12-12T01:11:52.438Z","repository":{"id":62431938,"uuid":"154993535","full_name":"domkm/serene","owner":"domkm","description":"Generate clojure.spec with GraphQL and extend GraphQL with clojure.spec","archived":false,"fork":false,"pushed_at":"2023-11-06T16:52:55.000Z","size":53,"stargazers_count":135,"open_issues_count":3,"forks_count":3,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-08-20T20:45:07.835Z","etag":null,"topics":["api","clojure","clojure-spec","clojurescript","graphql"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/domkm.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-10-27T17:56:08.000Z","updated_at":"2025-03-09T08:11:03.000Z","dependencies_parsed_at":"2025-04-02T13:01:23.004Z","dependency_job_id":null,"html_url":"https://github.com/domkm/serene","commit_stats":null,"previous_names":["paren-com/serene"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/domkm/serene","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fserene","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fserene/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fserene/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fserene/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/domkm","download_url":"https://codeload.github.com/domkm/serene/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fserene/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281533490,"owners_count":26517827,"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","status":"online","status_checked_at":"2025-10-28T02:00:06.022Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["api","clojure","clojure-spec","clojurescript","graphql"],"created_at":"2025-02-15T21:39:05.702Z","updated_at":"2025-10-30T21:30:24.053Z","avatar_url":"https://github.com/domkm.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Clojars Project](https://img.shields.io/clojars/v/com.paren/serene.svg)](https://clojars.org/com.paren/serene)\n\n# Serene\n\nGenerate [clojure.spec](https://clojure.org/about/spec) with [GraphQL](https://graphql.org/) and extend GraphQL with clojure.spec.\n\n* **100% GraphQL schema spec coverage**\n* **Works with any GraphQL API**\n* **Extend GraphQL with your own specs**\n* **Works with Clojure \u0026 ClojureScript**\n\n## Clojure/conj 2018 - Robust APIs with clojure.spec \u0026 GraphQL \n\n[![Robust APIs with clojure.spec \u0026 GraphQL - Clojure/conj 2018](https://img.youtube.com/vi/mgSSVTDZvkI/0.jpg)](https://www.youtube.com/watch?v=mgSSVTDZvkI)\n\n\u003c!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --\u003e\n**Table of Contents**\n\n- [Serene](#serene)\n    - [Clojure/conj 2018 - Robust APIs with clojure.spec \u0026 GraphQL](#clojureconj-2018---robust-apis-with-clojurespec--graphql)\n    - [QuickStart](#quickstart)\n    - [Rationale](#rationale)\n    - [Usage](#usage)\n        - [Generated Spec Names](#generated-spec-names)\n- [Built-in scalars are defined: :gql/Boolean, :gql/Float , :gql/ID , :gql/Int, :gql/String](#built-in-scalars-are-defined-gqlboolean-gqlfloat--gqlid--gqlint-gqlstring)\n    - [-](#-)\n        - [`paren.serene/def-specs`](#parenserenedef-specs)\n        - [`paren.serene/spit-specs`](#parenserenespit-specs)\n        - [Getting your GraphQL Schema](#getting-your-graphql-schema)\n            - [`paren.serene.schema/fetch`](#parensereneschemafetch)\n            - [`paren.serene.schema/query`](#parensereneschemaquery)\n        - [Compilation Options](#compilation-options)\n            - [`:extend`](#extend)\n            - [`:alias`](#alias)\n            - [`:prefix`](#prefix)\n            - [`:gen-object-fields`](#gen-object-fields)\n            - [Custom Compilation Options](#custom-compilation-options)\n    - [How It Works](#how-it-works)\n    - [Status](#status)\n    - [License](#license)\n\n\u003c!-- markdown-toc end --\u003e\n\n## QuickStart\n\nSerene can generate specs for an entire GraphQL API in one line of code.\n\nLet's say you have a project called \"Serenity\" and a GraphQL API available at `\"http://localhost:3000/graphql\"`:\n\n```clojure\n(ns serenity.now\n  (:require\n   [clojure.spec.alpha :as s]\n   [paren.serene :as serene]\n   [paren.serene.schema :as schema]))\n\n;; Define specs\n(serene/def-specs (schema/fetch \"http://localhost:3000/graphql\"))\n\n;; Use specs\n(s/valid? ::User {:firstName \"Frank\" :lastName \"Costanza\"}) ;=\u003e true\n```\n\n## Rationale\n\nIt is our experience that GraphQL is superior to REST for most APIs used for web and mobile applications. We also think that clojure.spec provides a good balance of expressiveness and strictness.\n\nGraphQL's type system provides a point of leverage for API providers and consumers.\nBecause GraphQL schemas are introspectable, [GraphQL tooling](https://github.com/chentsulin/awesome-graphql#tools) tends to be very powerful.\nSome languages, like OCaml/Reason, can even [validate queries and response code at compile time](https://github.com/mhallin/graphql_ppx).\n\nIf other languages can leverage GraphQL to this extent, Clojure should be able to as well.\nSerene aims to address this.\n\n## Usage\n\n### Generated Spec Names\n\nSpec names are keywords that are prefixed and namespaced by their position in the schema.\nFor the example below, let's assume a prefix of `:gql`, though the prefix is customizable.\n\n```graphql\n# Built-in scalars are defined: :gql/Boolean, :gql/Float , :gql/ID , :gql/Int, :gql/String\n\nscalar Email # :gql/Email\n\nenum Mood { # :gql/Mood\n  SERENE # :gql.Mood/SERENE\n  ANNOYED # :gql.Mood/ANNOYED\n  ANGRY # :gql.Mood/ANGRY\n}\n\ntype User { # :gql/User\n  id: ID! # :gql.User/id\n  username: String! # :gql.User/username\n  email: Email! # :gql.User/email\n  mood: Mood # :gql.User/mood\n}\n\ntype Mutation { # :gql/Mutation\n  createUser(\n    username: String!, # :gql.Mutation.createUser/username\n    email: Email! # :gql.Mutation.createUser/email\n    mood: Mood # :gql.Mutation.createUser/mood\n    # :gql.Mutation.createUser/\u0026args is an anonymous `s/keys` spec for args map\n  ): User! # :gql.Mutation/createUser\n}\n```\n\n### Generating Specs\n\n#### `paren.serene/def-specs`\n\n**Note**: All arguments to `def-specs` are `eval`ed.\n\n`def-specs` is a macro that will define specs for a GraphQL schema.\nIt takes compilation options as an optional second argument.\n[Compilation options](#compilation-options) are documented below.\n\n```clojure\n(serene/def-specs gql-schema options)\n```\n\n#### `paren.serene/spit-specs`\n\n`spit-specs` is like `def-specs`, but outputs `s/def` forms to a file.\nThe file path and namespace are the first two arguments to `spit-specs`.\n\n```clojure\n(serene/spit-specs \"src/api/specs.cljc\" 'api.specs gql-schema options)\n```\n\n### Getting your GraphQL Schema\n\n#### `paren.serene.schema/fetch`\n\n`fetch` takes a GraphQL server endpoint and optional configuration.\n\n```clojure\n(schema/fetch \"https://api.github.com/graphql\" {:headers {\"Authorization\" (str \"bearer \" gh-access-token)}})\n```\n\n#### `paren.serene.schema/query`\n\n`query` is [this GraphQL introspection query string](https://github.com/paren-com/serene/blob/master/resources/main/paren/serene/IntrospectionQuery.graphql).\n\n`fetch` works by asking the HTTP GraphQL server to execute `query`.\n\nYou can use `query` directly if your GraphQL API is not accessible via HTTP.\n\n### Compilation Options\n\n#### `:extend`\n\n`:extend` is a function or map of spec names to spec forms. If a spec form is returned, it will be combined with default specs using `s/and`.\n\nFor example, if you have a custom `Keyword` scalar you could use the following to add custom scalar validation:\n\n```clojure\n(serene/def-specs gql-schema {:extend {:Keyword `keyword?}})\n```\n\n#### `:alias`\n\n`:alias` is a function or map which receives unprefixed spec names and returns aliases for those names.\n\n```clojure\n(serene/def-specs gql-schema {:alias {:Query #{:api/query :api.query/root}\n                                      :Query/node :api.query/get-node}})\n```\n\nThis would cause both `:api/query` and `:api.query/root` to be defined as aliases of the `:Query` type spec and would cause `:api.query/get-node` to be defined as an alias of the `:Query/node` field spec.\n\n#### `:prefix`\n\n`:prefix` is a wrapper around `:alias` for the common case of altering default `*ns*` prefixes.\n\nFor example, instead of having a long namespace prefix, you might want to prefix your specs with `:gql`:\n\n```clojure\n(serene/def-specs gql-schema {:prefix :gql})\n  ```\n\nThis will produce specs like `:gql/Query`, `:gql.Query/node`, etc.\n\n#### `:gen-object-fields`\n\n`:gen-object-fields` will cause test.check generators for object types to generate all fields, \neven though all object fields are optional.\n\nThis is necessary if you are using test.check to generate data for object, interface, or union types\nbecause all object fields are optional (clients can query for any combination of fields).\n\nHowever, generating data for map specs where all keys are optional can be frustrating because you often end up with empty or nearly empty maps.\nIt is also not possible to always generate all fields, because objects can be cyclic, so you have to stop after some predetermined level.\n\n`:gen-object-fields` solves this by always generating all fields up to `n` where `n` is a configurable depth that defaults to `s/*recursion-limit*`.\n\n```clojure\n;; modify all objects to generate `s/*recursion-limit*` levels deep\n(serene/def-specs gql-schema {:gen-object-fields true})\n\n;; modify only `Query` to generate 5 levels deep\n(serene/def-specs gql-schema {:gen-object-fields {:Query 5}})\n```\n\n#### Custom Compilation Options\n\nAll compilation options are implemented and documented in [`paren.serene.compiler.transducers`](https://github.com/paren-com/serene/blob/master/sources/main/paren/serene/compiler/transducers.cljc).\n\nCustom compilation options can be added in the same way that default options are provided.\n\n## How It Works\n\nSerene works in much the same way as [GraphiQL](https://github.com/graphql/graphiql) and other GraphQL tools; it uses GraphQL's introspection capabilities.\nGraphQL schemas are introspectable, meaning that you can query a running API to determine all of the capabilities of that API.\n\nSerene uses [this introspection query](https://github.com/paren-com/serene/blob/master/resources/main/paren/serene/IntrospectionQuery.graphql), which is conveniently defined as `paren.serene/introspection-query`, to generate specs that match your API.\n\n## Status\n\nIf clojure.spec is alpha, then Serene is extra alpha.\n\nConsider everything to be an implementation detail unless it is explicitly documented here.\n\nSerene uses [Break Versioning](https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md).\n\n## License\n\nCopyright © Paren, LLC\n\nDistributed under the [Eclipse Public License version 2.0](http://www.eclipse.org/legal/epl-v20.html).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdomkm%2Fserene","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdomkm%2Fserene","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdomkm%2Fserene/lists"}