Skip to content

Commit

Permalink
feat(hydra-cli): support variant relations v3 (#380)
Browse files Browse the repository at this point in the history
* chore(docs): [skip ci] doc update (#357)

* GitBook: [master] 2 pages modified

* GitBook: [master] one page modified

* chore(docs): small interfaces docs fix

* feat(hydra-cli): add variant relations

affects: @dzlzv/hydra-cli

* fix(hydra-cli): add support for otm relations

affects: @dzlzv/hydra-cli

* docs(hydra-cli): add docs for variant relations

* fix(hydra-cli): duplicate function name

affects: @dzlzv/hydra-cli

* test(hydra-cli): variant relation tests

affects: @dzlzv/hydra-cli

Co-authored-by: dzhelezov <dzhelezov@gmail.com>
  • Loading branch information
metmirr and dzhelezov committed May 7, 2021
1 parent 04f2dca commit 8ab5222
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 44 deletions.
59 changes: 30 additions & 29 deletions SUMMARY.md
@@ -1,31 +1,32 @@
# Table of contents

* [Hydra](README.md)
* [Hydra CLI](packages/hydra-cli/README.md)
* [Hydra Indexer](packages/hydra-indexer/README.md)
* [Hydra Indexer Gateway](packages/hydra-indexer-gateway/README.md)
* [Hydra Processor](packages/hydra-processor/README.md)
* [Hydra Typegen](packages/hydra-typegen/README.md)
* [Overview](docs/README.md)
* [Query Node Manifest](docs/manifest-spec.md)
* [Query Node Queries](docs/queries.md)
* [Pagination](docs/paginate-query-results.md)
* [Sorting](docs/sort-query-results.md)
* [Mappings](docs/mappings/README.md)
* [DatabaseManager](docs/mappings/databasemanager.md)
* [SubstrateEvent](docs/mappings/substrateevent.md)
* [Schema](docs/schema-spec/README.md)
* [The Goodies](docs/schema-spec/the-query-goodies.md)
* [Entities](docs/schema-spec/entities.md)
* [Enums](docs/schema-spec/enums.md)
* [Interfaces](docs/schema-spec/interfaces.md)
* [Algebraic types](docs/schema-spec/variant-types.md)
* [Full-text queries](docs/schema-spec/full-text-queries.md)
* [Entity Relationships](docs/schema-spec/entity-relationship.md)
* [Install Hydra](docs/install-hydra.md)
* [Tutorial](docs/quick-start.md)
* [GraphQL Entity Relationships](docs/graphql-entity-relationships.md)
* [Architecture](docs/architecture.md)
* [Migration to Hydra v2](migration-to-hydra-v2.md)
* [What's new in Hydra v3](announcing-hydra-v3.md)

- [Hydra](README.md)
- [Hydra CLI](packages/hydra-cli/README.md)
- [Hydra Indexer](packages/hydra-indexer/README.md)
- [Hydra Indexer Gateway](packages/hydra-indexer-gateway/README.md)
- [Hydra Processor](packages/hydra-processor/README.md)
- [Hydra Typegen](packages/hydra-typegen/README.md)
- [Overview](docs/README.md)
- [Query Node Manifest](docs/manifest-spec.md)
- [Graphql Queries](docs/queries.md)
- [Pagination](docs/paginate-query-results.md)
- [Sorting](docs/sort-query-results.md)
- [Mappings](docs/mappings/README.md)
- [DatabaseManager](docs/mappings/databasemanager.md)
- [SubstrateEvent](docs/mappings/substrateevent.md)
- [Schema](docs/schema-spec/README.md)
- [The Goodies](docs/schema-spec/the-query-goodies.md)
- [Entities](docs/schema-spec/entities.md)
- [Enums](docs/schema-spec/enums.md)
- [Interfaces](docs/schema-spec/interfaces.md)
- [Algebraic types](docs/schema-spec/variant-types.md)
- [Full-text queries](docs/schema-spec/full-text-queries.md)
- [Entity Relationships](docs/schema-spec/entity-relationship.md)
- [Cross filtering](docs/schema-spec/cross-filters.md)
- [Variant relations](docs/schema-spec/variant-relations.md)
- [Install Hydra](docs/install-hydra.md)
- [Tutorial](docs/quick-start.md)
- [GraphQL Entity Relationships](docs/graphql-entity-relationships.md)
- [Architecture](docs/architecture.md)
- [Migration to Hydra v2](migration-to-hydra-v2.md)
- [What's new in Hydra v3](announcing-hydra-v3.md)
13 changes: 6 additions & 7 deletions announcing-hydra-v3.md
Expand Up @@ -2,12 +2,12 @@

Hydra v3 is already in the works. Here are the main features planned:

* Support for filtering by relations
* Support for adding relations to variant types
* Ordering by multiple fields
* Multiple filters in the same query \(`AND` and `OR`\)
* Filters for mapping handlers: specify heights and runtime spec version range
* Import all model files from a single library. Instead of the cumbersome
- Support for filtering by relations
- Support for adding relations to variant types
- Ordering by multiple fields
- Multiple filters in the same query \(`AND` and `OR`\)
- Filters for mapping handlers: specify heights and runtime spec version range
- Import all model files from a single library. Instead of the cumbersome

```typescript
import { MyEntity1 } from '../generated/graphql-server/my-entity2/my-entity2.model'
Expand All @@ -19,4 +19,3 @@ write
```typescript
import { MyEntity1, MyEntity2 } from '../generated/graphql-server/model'
```

86 changes: 86 additions & 0 deletions docs/schema-spec/variant-relations.md
@@ -0,0 +1,86 @@
---
description: Define variant types with relations
---

# Variant Relations

Variant types support entity relationship in a different way unlike the normal entity relationship. There are some limitations with variant relations:

- Only one-to-many and many-to-one relations are supported
- Reverse lookup is not supported

Let's take a look an example:

1. Schema: in the schema below there are two variant types with the relations

```graphql
type BoughtMemberEvent @entity {
id: ID!
name: String
handle: String!
}

type InvitedMemberEvent @entity {
id: ID!
name: String
handle: String!
}

type MemberInvitation @variant {
event: InvitedMemberEvent!
}

type MemberPurchase @variant {
event: BoughtMemberEvent!
}

union MemberSource = MemberInvitation | MemberPurchase

type Member @entity {
id: ID!
isVerified: Boolean!
handle: String!
source: MemberSource!
}
```

2. Mappings: insert data into database

For variant relations to work an additional field is added to variant type which is db only field (which means it is not visible in the graphql API). This field is will be generated from relation field name + 'id' ie. in the schema above relation name is `event` so the auto generated field name is `eventId`. This field is not optional and mapping author must set it properly.

```ts
async function handle_Member(db: DB, event: SubstrateEvent) {
// Create an event from BoughtMemberEvent or MemberInvitation and save to db
let event = new InvitedMemberEvent({ handle: 'joy' })
event = await db.save<InvitedMemberEvent>(event)

// Create variant instance and set eventId property
const invitation = new MemberInvitation()
// Auto generated property, it holds primary key of the related entity
invitation.eventId = event.id

// Create new member and set the source property
const member = new Member({ handle: 'hydra', isVerified: true })
member.source = invitation
await db.save<Member>(member)
}
```

3. Query: fetch all members' `source`:

```graphql
query {
members {
source {
__typename
... on MemberInvitation {
event {
id
name
handle
}
}
}
}
}
```
26 changes: 26 additions & 0 deletions packages/hydra-cli/src/generate/ModelRenderer.ts
Expand Up @@ -6,6 +6,7 @@ import * as utils from './utils'
import { GraphQLEnumType } from 'graphql'
import { AbstractRenderer } from './AbstractRenderer'
import { withEnum } from './enum-context'
import { camelCase } from 'lodash'
import { getRelationType } from '../model/Relation'

const debug = Debug('qnode-cli:model-renderer')
Expand Down Expand Up @@ -170,6 +171,30 @@ export class ModelRenderer extends AbstractRenderer {
}
}

/**
* Provides variant names for the fields that have union type, we need variant names for the union
* in order to fetch the data for variant relations and it is used by service.mst template
* @returns GeneratorContext
*/
withVariantNames(): GeneratorContext {
const variantNames = new Set<string>()
const fieldVariantMap: { field: string; type: string }[] = []

for (const field of this.objType.fields.filter((f) => f.isUnion())) {
const union = this.model.lookupUnion(field.type)

for (const type of union.types) {
type.fields.forEach((f) => {
if (f.isEntity()) {
variantNames.add(type.name)
fieldVariantMap.push({ field: camelCase(f.name), type: type.name })
}
})
}
}
return { variantNames: Array.from(variantNames), fieldVariantMap }
}

transform(): GeneratorContext {
return {
...this.context, // this.getGeneratedFolderRelativePath(objType.name),
Expand All @@ -183,6 +208,7 @@ export class ModelRenderer extends AbstractRenderer {
...this.withImportProps(),
...this.withFieldResolvers(),
...utils.withNames(this.objType),
...this.withVariantNames(),
}
}
}
13 changes: 8 additions & 5 deletions packages/hydra-cli/src/generate/VariantsRenderer.ts
Expand Up @@ -3,6 +3,7 @@ import { GeneratorContext } from './SourcesGenerator'
import { AbstractRenderer } from './AbstractRenderer'
import { ModelRenderer } from './ModelRenderer'
import { withUnionType } from './union-context'
import { generateEntityImport } from './utils'

export class VariantsRenderer extends AbstractRenderer {
constructor(model: WarthogModel, context: GeneratorContext = {}) {
Expand All @@ -11,11 +12,13 @@ export class VariantsRenderer extends AbstractRenderer {

withImports(): { imports: string[] } {
const moduleImports = new Set<string>()
this.model.variants.forEach((v) => {
if (v.fields.find((f) => f.type === 'BigInt')) {
moduleImports.add(`import BN from 'bn.js'\n`)
}
})
for (const variant of this.model.variants) {
variant.fields.map((f) => {
if (f.isEntity()) {
moduleImports.add(generateEntityImport(f.type))
}
})
}
return { imports: Array.from(moduleImports) }
}

Expand Down
8 changes: 8 additions & 0 deletions packages/hydra-cli/src/generate/field-context.ts
Expand Up @@ -86,6 +86,7 @@ export function buildFieldContext(
...withDerivedNames(f, entity),
...withDescription(f),
...withTransformer(f),
...withArrayProp(f),
}
}

Expand All @@ -95,6 +96,7 @@ export function withFieldTypeGuardProps(f: Field): GeneratorContext {
is.scalar = f.isScalar()
is.enum = f.isEnum()
is.union = f.isUnion()
is.entity = f.isEntity()
;['mto', 'oto', 'otm', 'mtm'].map((s) => (is[s] = f.relation?.type === s))
return {
is: is,
Expand Down Expand Up @@ -180,6 +182,12 @@ export function withRelation(f: Field): GeneratorContext {
}
}

export function withArrayProp(f: Field): GeneratorContext {
return {
array: f.isList,
}
}

export function withTransformer(f: Field): GeneratorContext {
if (
TYPE_FIELDS[f.columnType()] &&
Expand Down
4 changes: 4 additions & 0 deletions packages/hydra-cli/src/model/Field.ts
Expand Up @@ -75,4 +75,8 @@ export class Field {
isUnion(): boolean {
return this.modelType === ModelType.UNION
}

isEntity(): boolean {
return this.modelType === ModelType.ENTITY
}
}
20 changes: 18 additions & 2 deletions packages/hydra-cli/src/templates/entities/service.ts.mst
Expand Up @@ -5,6 +5,8 @@ import { BaseService, WhereInput } from 'warthog';

import { {{className}} } from './{{kebabName}}.model';

import { {{#variantNames}} {{.}}, {{/variantNames}} } from '../variants/variants.model'

@Service('{{className}}Service')
export class {{className}}Service extends BaseService<{{className}}> {
constructor(
Expand Down Expand Up @@ -32,8 +34,22 @@ export class {{className}}Service extends BaseService<{{className}}> {
}
{{/is.union}}
{{/fields}}

return super.find<W>(where, orderBy, limit, offset, f);

{{#has.union}}
let records = await super.find<W>(where, orderBy, limit, offset, f);
if (records.length) {
{{#fields}}
{{#is.union}}
{{#fieldVariantMap}}
records = await {{type}}.fetchData{{field}}(records, '{{camelName}}')
{{/fieldVariantMap}}
{{/is.union}}
{{/fields}}
}
return records;
{{/has.union}}

{{^has.union}} return super.find<W>(where, orderBy, limit, offset, f); {{/has.union}}
}

}

0 comments on commit 8ab5222

Please sign in to comment.