{"id":19376630,"url":"https://github.com/gavinray97/hasura-types","last_synced_at":"2025-04-23T18:33:25.910Z","repository":{"id":42090363,"uuid":"276244897","full_name":"GavinRay97/hasura-types","owner":"GavinRay97","description":null,"archived":false,"fork":false,"pushed_at":"2022-07-07T22:16:17.000Z","size":1873,"stargazers_count":12,"open_issues_count":11,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-20T11:08:54.129Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/GavinRay97.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-01T01:13:58.000Z","updated_at":"2022-06-30T16:13:24.000Z","dependencies_parsed_at":"2022-08-12T05:01:46.916Z","dependency_job_id":null,"html_url":"https://github.com/GavinRay97/hasura-types","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinRay97%2Fhasura-types","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinRay97%2Fhasura-types/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinRay97%2Fhasura-types/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinRay97%2Fhasura-types/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GavinRay97","download_url":"https://codeload.github.com/GavinRay97/hasura-types/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250490596,"owners_count":21439172,"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-11-10T08:44:33.471Z","updated_at":"2025-04-23T18:33:25.557Z","avatar_url":"https://github.com/GavinRay97.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Table of Contents\n\n- [Table of Contents](#table-of-contents)\n- [Introduction](#introduction)\n- [How to use this (aka TL;DR)](#how-to-use-this-aka-tldr)\n- [Demos](#demos)\n    - [Typescript SDK](#typescript-sdk)\n    - [Type-Checking \u0026 Docs inside of Metadata YAML files](#type-checking--docs-inside-of-metadata-yaml-files)\n- [SDK Usage Examples (Typescript)](#sdk-usage-examples-typescript)\n    - [Extending the Generated Class Functionality](#extending-the-generated-class-functionality)\n    - [Programmatically Interacting with Metadata](#programmatically-interacting-with-metadata)\n- [Generator Config File Options](#generator-config-file-options)\n- [Test Config File Options](#test-config-file-options)\n- [Programmatic Usage](#programmatic-usage)\n- [Metadata IDE Type-Checking Integration](#metadata-ide-type-checking-integration)\n    - [VS Code](#vs-code)\n    - [Jetbrains](#jetbrains)\n\n# Introduction\n\nThis repo contains a script used to generate SDK's in various languages from either Typescript or JSON Schema sources. The script is configurable and built to be consumed from something such as a Github Action or a git hook.\n\nIt is being used to generate SDK's for Hasura Metadata V2\n\n# How to use this (aka TL;DR)\n\n_**\"I want to...\"**_\n\n- Add support to my IDE for type-checking and documentation of metadata files\n\n  - See: [Metadata IDE Type-Checking Integration](#metadata-ide-type-checking-integration)\n\n- _Use an existing typed-language SDK in my project_\n\n  - Download the SDK from the [`/generated`](./generated) directory\n  - Follow the guide in [SDK Usage Examples (Typescript)](#sdk-usage-examples-typescript)\n\n- _Generate a new SDK for a language that isn't already present in the `/generated` directory (or customize the generator options on existing ones)_\n\n  - Update the config, following guide here: [Generator Config File Options](#generator-config-file-options)\n  - `yarn install` or `npm install`\n  - `yarn generate-types` or `npm run generate-types`\n\n# Demos\n\n### Typescript SDK\n\n![](typescript-typecheck-demo.gif)\n\n### Type-Checking \u0026 Docs inside of Metadata YAML files\n\n![](json-schema-typecheck-demo.gif)\n\n# SDK Usage Examples (Typescript)\n\n### Extending the Generated Class Functionality\n\nThe SDK generated for Typescript contains types, but also produces a top-level class called `Convert`, which contains methods to do runtime parsing and validation of types. These are named e.g. `Convert.toCronTrigger()` and `Convert.cronTriggerToJson()`:\n\n```ts\npublic static toCronTrigger(json: string): CronTrigger {\n    return cast(JSON.parse(json), r(\"CronTrigger\"));\n}\n\npublic static cronTriggerToJson(value: CronTrigger): string {\n    return JSON.stringify(uncast(value, r(\"CronTrigger\")), null, 2);\n}\n```\n\nThis class can be extended from another file to add extra functionality. Here is an example we will be using, which adds `diff` functionality and a YAML conversion function.\n\n```ts\n// customMetadataConverter.ts\nimport fs from 'fs'\nimport { load, dump } from 'js-yaml'\nimport { createPatch } from 'diff'\nimport { detailedDiff } from 'deep-object-diff'\nimport {\n  Convert as _Convert,\n  TableEntry,\n  Action,\n  CustomTypes,\n  CronTrigger,\n  HasuraMetadataV2,\n} from '../generated/HasuraMetadataV2'\n\ninterface DiffOutput {\n  structuralDiff: object\n  textDiff: string\n}\n\ninterface WriteDiffOpts {\n  folder: string\n  file: string\n  diffs: DiffOutput\n}\n\nexport class Convert extends _Convert {\n  public static loadYAML = load\n  public static dumpYAML = dump\n  public static diffYaml = createPatch\n  public static diffJson = detailedDiff\n\n  public static clone(obj: any) {\n    if (obj == null || typeof obj != 'object') return obj\n    let temp = new obj.constructor()\n    for (var key in obj) {\n      if (obj.hasOwnProperty(key)) {\n        temp[key] = Convert.clone(obj[key])\n      }\n    }\n    return temp\n  }\n\n  public static diff(before: object, after: object): DiffOutput {\n    const originalYaml = Convert.metadataToYaml(before)\n    const updatedYaml = Convert.metadataToYaml(after)\n    const structuralDiff = Convert.diffJson(before, after)\n    const textDiff = Convert.diffYaml('', originalYaml, updatedYaml)\n    return { structuralDiff, textDiff }\n  }\n\n  public static writeDiff(opts: WriteDiffOpts) {\n    const { file, folder, diffs } = opts\n    fs.writeFileSync(`${folder}/${file}.diff`, diffs.textDiff)\n    fs.writeFileSync(\n      `${folder}/${file}.json`,\n      JSON.stringify(diffs.structuralDiff, null, 2)\n    )\n  }\n\n  /**\n   * Converts metadata objects into YAML strings\n   */\n  public static metadataToYaml(value: object): string {\n    // JSON Stringify + Parse to remove \"undefined\" key/values from YAML\n    return dump(JSON.parse(JSON.stringify(value)))\n  }\n}\n```\n\n### Programmatically Interacting with Metadata\n\nBelow is an example to demonstrate the common usecases you may encounter when wanting to script your interactions with metadata. It includes:\n\n- Loading `tables.yaml`, and `actions.yaml` files\n- Adding a new table\n- Creating a JSON and text diff of `tables.yaml`, and writing it to a `diffs` folder\n- Repeating the above process for `metadata.json` (could be `metadata.yaml` as well)\n\n```ts\nimport { Convert } from './customMetadataConverter'\nimport {\n  TableEntry,\n  Action,\n  CustomTypes,\n  HasuraMetadataV2,\n} from '../generated/HasuraMetadataV2'\n\n// Read \"tables.yaml\" file as text from filesystem\nconst tablesMetadataFile = fs.readFileSync('./metadata/tables.yaml', 'utf8')\n// Convert it to JSON object with type annotation using loadYAML utility\nconst tablesMetadata: TableEntry[] = Convert.loadYAML(tablesMetadataFile)\ntablesMetadata.forEach(console.log)\n\n// Read \"actions.yaml\" file as text from filesystem\nconst actionMetadataFile = fs.readFileSync('./metadata/actions.yaml', 'utf8')\n// Convert it to JSON object with type annotation using loadYAML utility\nconst actionMetadata: {\n  actions: Action[]\n  custom_types: CustomTypes\n} = Convert.loadYAML(actionMetadataFile)\nactionMetadata.actions.forEach(console.log)\nconsole.log(actionMetadata.custom_types)\n\n// Make a new table object\nconst newTable: TableEntry = {\n  table: { schema: 'public', name: 'user' },\n  select_permissions: [\n    {\n      role: 'user',\n      permission: {\n        limit: 100,\n        allow_aggregations: false,\n        columns: ['id', 'name', 'etc'],\n        computed_fields: ['my_computed_field'],\n        filter: {\n          id: { _eq: 'X-Hasura-User-ID' },\n        },\n      },\n    },\n  ],\n}\n\n// Clone the tables for comparison after changes using diff()\nconst originalTablesMetadata = Convert.clone(tablesMetadata)\n// Add the new table to tables metadata\ntablesMetadata.push(newTable)\n\n// Generate a structural and text diff from the changes between original and now\nconst tableDiff = Convert.diff(originalTablesMetadata, tablesMetadata)\n// Write the diffs to /diffs folder, will output \"tables.json\" and \"tables.diff\"\nConvert.writeDiff({ folder: 'diffs', file: 'tables', diffs: tableDiff })\n// Ouput the updated \"tables.yaml\" to filesystem\nfs.writeFileSync(\n  './tables-updated.yaml',\n  Convert.metadataToYAML(tablesMetadata)\n)\n\n// Read \"metadata.json\"\nconst metadataFile = fs.readFileSync('./metadata.json', 'utf-8')\n// Convert.to\u003ctypeName\u003e does runtime validation of the type\nconst allMetadata: HasuraMetadataV2 = Convert.toHasuraMetadataV2(metadataFile)\nconsole.log(allMetadata)\n\n// Clone, add table\nconst beforeMetadataChanges = Convert.clone(allMetadata)\nallMetadata.tables.push(newTable)\n\n// Diff, write diff\nconst metadataDiff = Convert.diff(beforeMetadataChanges, allMetadata)\nConvert.writeDiff({ folder: 'diffs', file: 'metadata', diffs: metadataDiff })\n```\n\n# Generator Config File Options\n\n_Note: Run with `yarn generate-types`/`npm run generate-types`_\n\nThe file `config.yaml` can be used to pass options to the program. It takes:\n\n- An input language target of either \"Typescript\" or \"JsonSchema\"\n- A single file/glob expression, or an array of file/glob expressions for the input files used to generates the types\n- The output directory can be set, and the output filename will be the name of the input file (with the new language extension)\n- Any language name that exists in `quicktype_config` will be generated, and the object keys are options passed to Quicktype's `rendererOptions` config\n\n```yaml\n# Accepts \"Typescript\" or \"JsonSchema\"\n# Override this with --typescript or --jsonschema from CLI\nselected_input_language: Typescript\n\n# Glob patterns for the target input files of selected language\n# Only the matching SELECTED INPUT LANGUAGE file expression will be used\ninput_files:\n  # Paths can be either a string, or an array of strings\n  JsonSchema: './src/types/**.schema.json'\n  Typescript: ['./src/types/**.ts', './src/otherfolder/**.ts']\n\n# Output file directory\noutput_directory: './generated'\n\n# Quicktype config per-language\n# Config is an object of type \"rendererOptions\"\n# See: https://github.com/quicktype/quicktype/blob/master/src/quicktype-core/language/TypeScriptFlow.ts#L20\nquicktype_config:\n  # c++: ~\n  # crystal: ~\n  # csharp: ~\n  # dart: ~\n  # elm: ~\n  # flow: ~\n  go:\n    package: hasura_metadata\n  haskell: ~\n  # java:\n  #   package: org.hasura.metadata\n  # kotlin:\n  #   framework: kotlinx\n  #   package: org.hasura.metadata\n  # objective-c: ~\n  # pike: ~\n  python:\n    python-version: '3.7'\n  # ruby: ~\n  # rust: ~\n  schema: ~\n  # swift: ~\n  typescript: ~\n  # rendererOptions:\n  #   just-types: true\n```\n\n# Test Config File Options\n\n_Note: Run with `yarn test`/`npm run test`_\n\nThe test config file is used to take sample input JSON files, and feed them to the generated Typescript SDK for automated testing. The idea is to have many samples of `metadata.json` (or even individual types, like a single `table.yaml` item as JSON) and have them be type-checked against the generated types for verification.\n\nThe input takes the location of a Typescript file containing the Metadata types, and then an array of `jsonInputTests` with `files` as a one or more file paths or glob expressions pointing to input JSON data to use as type inputs.\n\nFor example:\n\n```ts\n// myTypes.ts\ninterface MyType {\n  name: string\n  age: number\n}\n```\n\n```js\n// test-data1.json\n{\n  \"name\": \"John\",\n  \"age\": 30\n}\n```\n\nThis is what the definition looks like:\n\n```yaml\n---\n- typeDefinitionFile: './generated/HasuraMetadataV2.ts'\n  jsonInputTests:\n    - files: './src/tests/**.json'\n      # This gets called as \"Convert.to(expectType)\" -\u003e e.g \"Convert.toHasuraMetadataV2\" in generated TS SDK\n      expectType: HasuraMetadataV2\n```\n\n![](test-output-sample.png)\n\n# Programmatic Usage\n\nThe type generator can in theory run both as a CLI executable, and as a library.\nThis allows for customizing behavior, IE for CI/CD pipelines. Here is one example:\n\n```ts\ngenerateTypes()\n  .then((outputs) =\u003e {\n    console.log('Finished generateTypes(), outputs are', outputs)\n    for (let output of outputs) {\n      // This is the input file path\n      console.log('File:', output.file)\n      // This contains the generated text\n      console.log('Results:', output.results)\n    }\n  })\n  .catch((err) =\u003e {\n    console.log('Got error', err)\n  })\n  .finally(async () =\u003e {\n    // Convert the generated JSON Schema to YAML, for example\n    const generatedFolder = path.join(pathFromRoot, 'generated', '/')\n    const jsonSchemas = await glob(generatedFolder + '**.json')\n    jsonSchemas.forEach(jsonSchemaToYAML)\n  })\n```\n\n# Metadata IDE Type-Checking Integration\n\nEver tried (or wanted) to write Hasura Metadata YAML definitions by hand, but found yourself frequently pulled back to documentation for definitions, or fighting YAML's whitespace sensitivity? Well, no more!\n\n### VS Code\n\nVS Code has native support for supplying JSON Schemas when editing JSON files. The [YAML extension authored by Redhat](https://github.com/redhat-developer/vscode-yaml) extends identical support to YAML files.\n\nFollow the configuration below to enable type-checking, documentation, and auto-completion of Hasura metadata YAML files in your project:\n\n_Note: In the future, this may be refactored to point to hosted schema links on Github so that manual copying of schema files is not necessary._\n\n`.vscode/extensions.json`\n\n```json\n{\n  \"recommendations\": [\"redhat.vscode-yaml\"]\n}\n```\n\n`.vscode/settings.json`\n\n```json\n{\n  \"json.schemas\": [\n    {\n      \"fileMatch\": [\"**/metadata.json\"],\n      \"url\": \"./MetadataExport.schema.json\"\n    }\n  ],\n  \"yaml.schemas\": {\n    \"./ActionsYAML.schema.json\": \"**/actions.yaml\",\n    \"./AllowListYAML.schema.json\": \"**/allow_list.yaml\",\n    \"./CronTriggerYAML.schema.json\": \"**/cron_triggers.yaml\",\n    \"./FunctionsYAML.schema.json\": \"**/functions.yaml\",\n    \"./QueryCollectionsYAML.schema.json\": \"**/query_collections.yaml\",\n    \"./RemoteSchemasYAML.schema.json\": \"**/remote_schemas.yaml\",\n    \"./TablesYAML.schema.json\": \"**/tables.yaml\"\n  }\n}\n```\n\n`./MetadataExport.schema.json`\n\n```json\n{\n  \"type\": \"object\",\n  \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/HasuraMetadataV2\"\n}\n```\n\n`./ActionsYAML.schema.json`:\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"actions\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/Action\"\n      }\n    },\n    \"custom_types\": {\n      \"type\": \"object\",\n      \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/CustomTypes\"\n    }\n  }\n}\n```\n\n`./AllowListYAML.schema.json`:\n\n```json\n{\n  \"type\": \"array\",\n  \"items\": {\n    \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/AllowList\"\n  }\n}\n```\n\n`./CronTriggerYAML.schema.json`:\n\n```json\n{\n  \"type\": \"array\",\n  \"items\": {\n    \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/CronTrigger\"\n  }\n}\n```\n\n`./FunctionsYAML.schema.json`:\n\n```json\n{\n  \"type\": \"array\",\n  \"items\": {\n    \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/Function\"\n  }\n}\n```\n\n`./QueryCollectionsYAML.schema.json`:\n\n```json\n{\n  \"type\": \"array\",\n  \"items\": {\n    \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/QueryCollectionEntry\"\n  }\n}\n```\n\n`./RemoteSchemasYAML.schema.json`:\n\n```json\n{\n  \"type\": \"array\",\n  \"items\": {\n    \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/RemoteSchema\"\n  }\n}\n```\n\n`./TablesYAML.schema.json`\n\n```json\n{\n  \"type\": \"array\",\n  \"items\": {\n    \"$ref\": \"./HasuraMetadataV2.schema.json#definitions/TableEntry\"\n  }\n}\n```\n\n### Jetbrains\n\nYAML Instructions:\n\nhttps://www.jetbrains.com/help/ruby/yaml.html#remote_json\n\n![](https://resources.jetbrains.com/help/img/idea/2020.1/yaml_complete_json.png)\n\nJSON Instructions:\n\nhttps://www.jetbrains.com/help/idea/json.html#ws_json_schema_add_custom\n\n![Jetbrains JSON Schema Mapping in Editor](https://www.jetbrains.com/help/img/idea/2020.1/ws_json_schema_no_schema_status_bar.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgavinray97%2Fhasura-types","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgavinray97%2Fhasura-types","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgavinray97%2Fhasura-types/lists"}