From 449d53167b373f75f59011be73ed232c7d4cb464 Mon Sep 17 00:00:00 2001 From: David Matejka Date: Thu, 22 Feb 2024 11:44:27 +0100 Subject: [PATCH] fix(client-content): omit backreferencing relation from an unique --- .eslintignore | 1 + .../src/EntityTypeSchemaGenerator.ts | 8 +- .../tests/generateEnittyTypes.test.ts | 54 ++--- packages/client-content/package.json | 3 + .../client-content/tests/cases/unit/lib.ts | 140 ------------- .../tests/cases/unit/mutation.test.ts | 185 +++++++++++++----- .../tests/cases/unit/query.test.ts | 31 +-- .../tests/cases/unit/types.test.ts | 17 +- .../client-content/tests/client/entities.ts | 108 ++++++++++ packages/client-content/tests/client/enums.ts | 1 + packages/client-content/tests/client/index.ts | 17 ++ packages/client-content/tests/client/names.ts | 116 +++++++++++ packages/client-content/tests/generate.ts | 13 ++ packages/client-content/tests/lib.ts | 16 ++ packages/client-content/tests/schema.ts | 37 ++++ packages/client-content/tests/tsconfig.json | 6 +- 16 files changed, 512 insertions(+), 241 deletions(-) delete mode 100644 packages/client-content/tests/cases/unit/lib.ts create mode 100644 packages/client-content/tests/client/entities.ts create mode 100644 packages/client-content/tests/client/enums.ts create mode 100644 packages/client-content/tests/client/index.ts create mode 100644 packages/client-content/tests/client/names.ts create mode 100644 packages/client-content/tests/generate.ts create mode 100644 packages/client-content/tests/lib.ts create mode 100644 packages/client-content/tests/schema.ts diff --git a/.eslintignore b/.eslintignore index 016acb7c6b..51a16a1bee 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,3 +6,4 @@ /ee/*/temp /ee/*/node_modules /packages/playground/api/client +/packages/client-content/tests/client diff --git a/packages/client-content-generator/src/EntityTypeSchemaGenerator.ts b/packages/client-content-generator/src/EntityTypeSchemaGenerator.ts index 2a12c5ea94..2f1c37ab9f 100644 --- a/packages/client-content-generator/src/EntityTypeSchemaGenerator.ts +++ b/packages/client-content-generator/src/EntityTypeSchemaGenerator.ts @@ -32,7 +32,7 @@ export type JSONArray = readonly JSONValue[] } private generateTypeEntityCode(model: Model.Schema, entity: Model.Entity): string { - let code = `export type ${entity.name} = {\n` + let code = `export type ${entity.name} = {\n` code += '\tname: \'' + entity.name + '\'\n' code += '\tunique:\n' code += this.formatUniqueFields(model, entity) @@ -41,7 +41,7 @@ export type JSONArray = readonly JSONValue[] let hasManyCode = '' acceptEveryFieldVisitor(model, entity, { visitHasMany: ctx => { - hasManyCode += `\t\t${ctx.relation.name}: ${ctx.targetEntity.name}\n` + hasManyCode += `\t\t${ctx.relation.name}: ${ctx.targetEntity.name}${ctx.targetRelation?.type === Model.RelationType.ManyHasOne ? `<'${ctx.targetRelation.name}'>` : ''}\n` }, visitHasOne: ctx => { hasOneCode += `\t\t${ctx.relation.name}: ${ctx.targetEntity.name}\n` @@ -117,9 +117,9 @@ export type JSONArray = readonly JSONValue[] const fields = getFieldsForUniqueWhere(model, entity) let code = '' for (const field of fields) { - code += '\t\t| { ' + code += '\t\t| Omit<{ ' code += field.map(it => `${it}: ${uniqueType(model, entity, entity.fields[it])}`).join(', ') - code += ' }\n' + code += '}, OverRelation>\n' } return code } diff --git a/packages/client-content-generator/tests/generateEnittyTypes.test.ts b/packages/client-content-generator/tests/generateEnittyTypes.test.ts index 231e43345f..7a8265fdb2 100644 --- a/packages/client-content-generator/tests/generateEnittyTypes.test.ts +++ b/packages/client-content-generator/tests/generateEnittyTypes.test.ts @@ -15,10 +15,10 @@ describe('generate entities', () => { export type JSONObject = { readonly [K in string]?: JSONValue } export type JSONArray = readonly JSONValue[] - export type Foo = { + export type Foo = { name: 'Foo' unique: - | { id: string } + | Omit<{ id: string}, OverRelation> columns: { id: string stringCol: string | null @@ -59,10 +59,10 @@ describe('generate entities', () => { export type JSONObject = { readonly [K in string]?: JSONValue } export type JSONArray = readonly JSONValue[] - export type Foo = { + export type Foo = { name: 'Foo' unique: - | { id: string } + | Omit<{ id: string}, OverRelation> columns: { id: string enumCol: FooEnumCol | null @@ -94,11 +94,11 @@ describe('generate entities', () => { export type JSONObject = { readonly [K in string]?: JSONValue } export type JSONArray = readonly JSONValue[] - export type Foo = { + export type Foo = { name: 'Foo' unique: - | { id: string } - | { oneHasOneInverseRel: Bar['unique'] } + | Omit<{ id: string}, OverRelation> + | Omit<{ oneHasOneInverseRel: Bar['unique']}, OverRelation> columns: { id: string } @@ -110,11 +110,11 @@ describe('generate entities', () => { hasManyBy: { } } - export type Bar = { + export type Bar = { name: 'Bar' unique: - | { id: string } - | { oneHasOneOwningRel: Foo['unique'] } + | Omit<{ id: string}, OverRelation> + | Omit<{ oneHasOneOwningRel: Foo['unique']}, OverRelation> columns: { id: string } @@ -148,26 +148,26 @@ describe('generate entities', () => { export type JSONObject = { readonly [K in string]?: JSONValue } export type JSONArray = readonly JSONValue[] - export type Foo = { + export type Foo = { name: 'Foo' unique: - | { id: string } - | { oneHasManyRel: Bar['unique'] } + | Omit<{ id: string}, OverRelation> + | Omit<{ oneHasManyRel: Bar['unique']}, OverRelation> columns: { id: string } hasOne: { } hasMany: { - oneHasManyRel: Bar + oneHasManyRel: Bar<'manyHasOneRel'> } hasManyBy: { } } - export type Bar = { + export type Bar = { name: 'Bar' unique: - | { id: string } + | Omit<{ id: string}, OverRelation> columns: { id: string } @@ -201,10 +201,10 @@ describe('generate entities', () => { export type JSONObject = { readonly [K in string]?: JSONValue } export type JSONArray = readonly JSONValue[] - export type Foo = { + export type Foo = { name: 'Foo' unique: - | { id: string } + | Omit<{ id: string}, OverRelation> columns: { id: string } @@ -216,10 +216,10 @@ describe('generate entities', () => { hasManyBy: { } } - export type Bar = { + export type Bar = { name: 'Bar' unique: - | { id: string } + | Omit<{ id: string}, OverRelation> columns: { id: string } @@ -253,28 +253,28 @@ describe('generate entities', () => { export type JSONObject = { readonly [K in string]?: JSONValue } export type JSONArray = readonly JSONValue[] - export type Foo = { + export type Foo = { name: 'Foo' unique: - | { id: string } - | { locales: FooLocale['unique'] } + | Omit<{ id: string}, OverRelation> + | Omit<{ locales: FooLocale['unique']}, OverRelation> columns: { id: string } hasOne: { } hasMany: { - locales: FooLocale + locales: FooLocale<'foo'> } hasManyBy: { localesByLocale: { entity: FooLocale; by: {locale: string} } } } - export type FooLocale = { + export type FooLocale = { name: 'FooLocale' unique: - | { id: string } - | { locale: string, foo: Foo['unique'] } + | Omit<{ id: string}, OverRelation> + | Omit<{ locale: string, foo: Foo['unique']}, OverRelation> columns: { id: string locale: string diff --git a/packages/client-content/package.json b/packages/client-content/package.json index a0f8d68b6d..2fa32a742d 100644 --- a/packages/client-content/package.json +++ b/packages/client-content/package.json @@ -39,6 +39,9 @@ "@contember/graphql-client": "workspace:*", "@contember/schema": "^1.3.6" }, + "devDependencies": { + "@contember/client-content-generator": "workspace:*" + }, "repository": { "type": "git", "url": "https://github.com/contember/interface.git", diff --git a/packages/client-content/tests/cases/unit/lib.ts b/packages/client-content/tests/cases/unit/lib.ts deleted file mode 100644 index 933f6addd2..0000000000 --- a/packages/client-content/tests/cases/unit/lib.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { ContentClient, ContentQueryBuilder, TypedContentQueryBuilder, TypedEntitySelection } from '../../../src' - -export namespace Schema { - export type Author = { - name: 'Author' - unique: { id: number } - columns: { - id: number - name: string - email: string - } - hasMany: { - posts: Post - } - hasOne: {} - hasManyBy: {} - } - - - export type Post = { - name: 'Post' - unique: { id: number } - columns: { - id: number - title: string - content: string - } - hasMany: { - tags: Tag - } - hasOne: { - author: Author - } - hasManyBy: {} - } - - export type Tag = { - name: 'Tag' - unique: { id: number } - columns: { - id: number - name: string - } - hasMany: { - posts: Post - } - hasOne: {} - hasManyBy: {} - } - -} - -export type ContemberClientSchema = { - entities: { - Post: Schema.Post, - Author: Schema.Author, - Tag: Schema.Tag, - }, -}; -export const qb = new ContentQueryBuilder({ - entities: { - Author: { - name: 'Author', - fields: { - id: { - type: 'column', - }, - posts: { - type: 'many', - entity: 'Post', - }, - name: { - type: 'column', - }, - email: { - type: 'column', - }, - }, - scalars: ['name', 'email'], - }, - Post: { - name: 'Post', - - fields: { - id: { - type: 'column', - }, - author: { - type: 'one', - entity: 'Author', - }, - tags: { - type: 'many', - entity: 'Tag', - }, - title: { - type: 'column', - }, - content: { - type: 'column', - }, - }, - scalars: ['title', 'content'], - }, - Tag: { - name: 'Tag', - fields: { - id: { - type: 'column', - }, - posts: { - type: 'many', - entity: 'Post', - }, - name: { - type: 'column', - }, - }, - scalars: ['name'], - }, - }, -}) as unknown as TypedContentQueryBuilder - -export const createClient = (result?: any) => { - const calls: { query: string, variables: Record }[] = [] - const client = new ContentClient({ - execute: (query: string, options: any): Promise => { - calls.push({ - query, - ...options, - }) - return Promise.resolve(result ?? {}) - }, - }) - return [client, calls] as const -} - - -export type FragmentOf = - TypedEntitySelection diff --git a/packages/client-content/tests/cases/unit/mutation.test.ts b/packages/client-content/tests/cases/unit/mutation.test.ts index 15bead7011..6df49f67b1 100644 --- a/packages/client-content/tests/cases/unit/mutation.test.ts +++ b/packages/client-content/tests/cases/unit/mutation.test.ts @@ -1,5 +1,8 @@ import { describe, expect, test } from 'vitest' -import { createClient, qb } from './lib' +import { createClient } from '../../lib' +import { queryBuilder } from '../../client' + +const qb = queryBuilder describe('mutations in trx', () => { test('create trx', async () => { const [client, calls] = createClient({ @@ -93,7 +96,7 @@ describe('mutations in trx', () => { ok: true, mut: { ok: true, - node: { id: 123 }, + node: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, }, }, }) @@ -104,7 +107,7 @@ describe('mutations in trx', () => { }, }, it => it.$('id')))) expect(result.ok).toBe(true) - expect(result.data?.node?.id).toBe(123) + expect(result.data?.node?.id).toBe('ca7a9b84-efbb-435d-a063-da11f205335a') expect(calls).toHaveLength(1) expect(calls[0].query).toMatchInlineSnapshot(` "mutation($MutationTransactionOptions_0: MutationTransactionOptions, $AuthorCreateInput_1: AuthorCreateInput!) { @@ -185,7 +188,7 @@ describe('mutations in trx', () => { }, }) await client.mutate(qb.transaction(qb.update('Author', { - by: { id: 1 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, data: { name: 'John', email: 'xx@localhost', @@ -250,7 +253,7 @@ describe('mutations in trx', () => { expect(calls[0].variables).toMatchInlineSnapshot(` { "AuthorUniqueWhere_1": { - "id": 1, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", }, "AuthorUpdateInput_2": { "email": "xx@localhost", @@ -271,7 +274,7 @@ describe('mutations in trx', () => { }, }) await client.mutate(qb.transaction(qb.delete('Author', { - by: { id: 1 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, }))) expect(calls).toHaveLength(1) expect(calls[0].query).toMatchInlineSnapshot(` @@ -329,7 +332,7 @@ describe('mutations in trx', () => { expect(calls[0].variables).toMatchInlineSnapshot(` { "AuthorUniqueWhere_1": { - "id": 1, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", }, "MutationTransactionOptions_0": {}, } @@ -346,7 +349,7 @@ describe('mutations in trx', () => { }, }) await client.mutate(qb.transaction(qb.upsert('Author', { - by: { id: 1 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, create: { name: 'John', email: 'xx@localhost', @@ -419,7 +422,7 @@ describe('mutations in trx', () => { "name": "John", }, "AuthorUniqueWhere_1": { - "id": 1, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", }, "AuthorUpdateInput_3": { "email": "xx@localhost", @@ -562,8 +565,7 @@ describe('mutations in trx', () => { }), createPost: qb.create('Post', { data: { - title: 'Hello', - content: 'World', + publishedAt: 'now', }, }), })) @@ -643,8 +645,7 @@ describe('mutations in trx', () => { }, "MutationTransactionOptions_0": {}, "PostCreateInput_2": { - "content": "World", - "title": "Hello", + "publishedAt": "now", }, } `) @@ -659,9 +660,8 @@ describe('mutations in trx', () => { }, post: { value: { - id: 1, - title: 'Foo bar', - content: 'Hello world', + id: 'ca7a9b84-efbb-435d-a063-da11f205335a', + publishedAt: 'now', }, }, }, @@ -670,17 +670,16 @@ describe('mutations in trx', () => { const trx = qb.transaction({ createPost: qb.create('Post', { data: { - title: 'Hello', - content: 'World', + publishedAt: 'now', }, }), post: qb.get('Post', { - by: { id: 1 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, }, it => it.$$()), }) const result = await client.mutate(trx) expect(result.data.createPost.ok).toBe(true) - expect(result.data.post?.title).toBe('Foo bar') + expect(result.data.post?.publishedAt).toBe('now') expect(calls).toHaveLength(1) expect(calls[0].query).toMatchInlineSnapshot(` @@ -706,8 +705,8 @@ describe('mutations in trx', () => { } post: query { value: getPost(by: $PostUniqueWhere_2) { - title - content + id + publishedAt } } } @@ -748,11 +747,10 @@ describe('mutations in trx', () => { { "MutationTransactionOptions_0": {}, "PostCreateInput_1": { - "content": "World", - "title": "Hello", + "publishedAt": "now", }, "PostUniqueWhere_2": { - "id": 1, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", }, } `) @@ -836,7 +834,7 @@ describe('mutations without trx', () => { const [client, calls] = createClient({ mut: { ok: true, - node: { id: 123 }, + node: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, }, }) const result = await client.mutate(qb.create('Author', { @@ -846,7 +844,7 @@ describe('mutations without trx', () => { }, }, it => it.$('id'))) expect(result.ok).toBe(true) - expect(result.node?.id).toBe(123) + expect(result.node?.id).toBe('ca7a9b84-efbb-435d-a063-da11f205335a') expect(calls).toHaveLength(1) expect(calls[0].query).toMatchInlineSnapshot(` "mutation($AuthorCreateInput_0: AuthorCreateInput!) { @@ -913,7 +911,7 @@ describe('mutations without trx', () => { }, }) await client.mutate(qb.update('Author', { - by: { id: 1 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, data: { name: 'John', email: 'xx@localhost', @@ -968,7 +966,7 @@ describe('mutations without trx', () => { expect(calls[0].variables).toMatchInlineSnapshot(` { "AuthorUniqueWhere_0": { - "id": 1, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", }, "AuthorUpdateInput_1": { "email": "xx@localhost", @@ -985,7 +983,7 @@ describe('mutations without trx', () => { }, }) await client.mutate(qb.delete('Author', { - by: { id: 1 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, })) expect(calls).toHaveLength(1) expect(calls[0].query).toMatchInlineSnapshot(` @@ -1016,7 +1014,7 @@ describe('mutations without trx', () => { expect(calls[0].variables).toMatchInlineSnapshot(` { "AuthorUniqueWhere_0": { - "id": 1, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", }, } `) @@ -1029,7 +1027,7 @@ describe('mutations without trx', () => { }, }) await client.mutate(qb.upsert('Author', { - by: { id: 1 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, create: { name: 'John', email: 'xx@localhost', @@ -1092,7 +1090,7 @@ describe('mutations without trx', () => { "name": "John", }, "AuthorUniqueWhere_0": { - "id": 1, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", }, "AuthorUpdateInput_2": { "email": "xx@localhost", @@ -1216,8 +1214,7 @@ describe('mutations without trx', () => { }), createPost: qb.create('Post', { data: { - title: 'Hello', - content: 'World', + publishedAt: 'now', }, }), }) @@ -1286,8 +1283,7 @@ describe('mutations without trx', () => { "name": "John", }, "PostCreateInput_1": { - "content": "World", - "title": "Hello", + "publishedAt": "now", }, } `) @@ -1300,9 +1296,8 @@ describe('mutations without trx', () => { }, post: { value: { - id: 1, - title: 'Foo bar', - content: 'Hello world', + id: 'ca7a9b84-efbb-435d-a063-da11f205335a', + publishedAt: 'now', }, }, }) @@ -1310,16 +1305,15 @@ describe('mutations without trx', () => { const result = await client.mutate({ createPost: qb.create('Post', { data: { - title: 'Hello', - content: 'World', + publishedAt: 'now', }, }), post: qb.get('Post', { - by: { id: 1 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, }, it => it.$$()), }) expect(result.createPost.ok).toBe(true) - expect(result.post?.title).toBe('Foo bar') + expect(result.post?.publishedAt).toBe('now') expect(calls).toHaveLength(1) expect(calls[0].query).toMatchInlineSnapshot(` @@ -1336,8 +1330,8 @@ describe('mutations without trx', () => { } post: query { value: getPost(by: $PostUniqueWhere_1) { - title - content + id + publishedAt } } } @@ -1376,11 +1370,104 @@ describe('mutations without trx', () => { expect(calls[0].variables).toMatchInlineSnapshot(` { "PostCreateInput_0": { - "content": "World", - "title": "Hello", + "publishedAt": "now", }, "PostUniqueWhere_1": { - "id": 1, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", + }, + } + `) + }) + + test('update locale relation', async () => { + const [client, calls] = createClient({ + mut: { + ok: true, + }, + }) + + const result = await client.mutate(qb.update('Post', { + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, + data: { + publishedAt: 'now', + locales: [{ + update: { + by: { locale: { code: 'cs' } }, + data: { + title: 'cs title', + }, + }, + }], + }, + })) + expect(result.ok).toBe(true) + expect(calls).toHaveLength(1) + expect(calls[0].query).toMatchInlineSnapshot(` + "mutation($PostUniqueWhere_0: PostUniqueWhere!, $PostUpdateInput_1: PostUpdateInput!) { + mut: updatePost(by: $PostUniqueWhere_0, data: $PostUpdateInput_1) { + ok + errorMessage + errors { + ... MutationError + } + validation { + ... ValidationResult + } + } + } + fragment MutationError on _MutationError { + paths { + ... on _FieldPathFragment { + field + } + ... on _IndexPathFragment { + index + alias + } + } + message + type + } + fragment ValidationResult on _ValidationResult { + valid + errors { + path { + ... on _FieldPathFragment { + field + } + ... on _IndexPathFragment { + index + alias + } + } + message { + text + } + } + } + " + `) + expect(calls[0].variables).toMatchInlineSnapshot(` + { + "PostUniqueWhere_0": { + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", + }, + "PostUpdateInput_1": { + "locales": [ + { + "update": { + "by": { + "locale": { + "code": "cs", + }, + }, + "data": { + "title": "cs title", + }, + }, + }, + ], + "publishedAt": "now", }, } `) diff --git a/packages/client-content/tests/cases/unit/query.test.ts b/packages/client-content/tests/cases/unit/query.test.ts index ecd7156935..e666a03c6f 100644 --- a/packages/client-content/tests/cases/unit/query.test.ts +++ b/packages/client-content/tests/cases/unit/query.test.ts @@ -1,10 +1,12 @@ import { describe, expect, test } from 'vitest' -import { createClient, qb } from './lib' +import { createClient } from '../../lib' import { Input } from '@contember/schema' import OrderDirection = Input.OrderDirection; +import { queryBuilder } from '../../client' describe('queries', () => { + const qb = queryBuilder test('list', async () => { const [client, calls] = createClient({ authors: [ @@ -37,6 +39,7 @@ describe('queries', () => { expect(calls[0].query).toMatchInlineSnapshot(` "query { authors: listAuthor { + id name email } @@ -99,12 +102,13 @@ describe('queries', () => { expect(calls[0].query).toMatchInlineSnapshot(` "query { authors: listAuthor { + id name email } posts: listPost { - title - content + id + publishedAt } } " @@ -140,8 +144,8 @@ describe('queries', () => { expect(calls[0].query).toMatchInlineSnapshot(` "query { value: listPost { - title - content + id + publishedAt } } " @@ -159,12 +163,14 @@ describe('queries', () => { expect(calls[0].query).toMatchInlineSnapshot(` "query { authors: listAuthor { + id name email posts { - title - content + id + publishedAt tags { + id name } } @@ -186,11 +192,12 @@ describe('queries', () => { expect(calls[0].query).toMatchInlineSnapshot(` "query($PostWhere_0: PostWhere, $Int_1: Int) { authors: listAuthor { + id name email posts(filter: $PostWhere_0, limit: $Int_1) { - title - content + id + publishedAt } } } @@ -225,6 +232,7 @@ describe('queries', () => { expect(calls[0].query).toMatchInlineSnapshot(` "query($AuthorWhere_0: AuthorWhere, $AuthorOrderBy_1: [AuthorOrderBy!], $Int_2: Int, $Int_3: Int) { authors: listAuthor(filter: $AuthorWhere_0, orderBy: $AuthorOrderBy_1, limit: $Int_2, offset: $Int_3) { + id name email } @@ -254,13 +262,14 @@ describe('queries', () => { const [client, calls] = createClient() const result = await client.query({ authors: qb.get('Author', { - by: { id: 123 }, + by: { id: 'ca7a9b84-efbb-435d-a063-da11f205335a' }, }, it => it.$$()), }) expect(calls).toHaveLength(1) expect(calls[0].query).toMatchInlineSnapshot(` "query($AuthorUniqueWhere_0: AuthorUniqueWhere!) { authors: getAuthor(by: $AuthorUniqueWhere_0) { + id name email } @@ -270,7 +279,7 @@ describe('queries', () => { expect(calls[0].variables).toMatchInlineSnapshot(` { "AuthorUniqueWhere_0": { - "id": 123, + "id": "ca7a9b84-efbb-435d-a063-da11f205335a", }, } `) diff --git a/packages/client-content/tests/cases/unit/types.test.ts b/packages/client-content/tests/cases/unit/types.test.ts index 18b6a2e75e..ff82a8f53e 100644 --- a/packages/client-content/tests/cases/unit/types.test.ts +++ b/packages/client-content/tests/cases/unit/types.test.ts @@ -1,8 +1,9 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { assertType, describe, expectTypeOf, test } from 'vitest' -import { FragmentOf, qb, Schema } from './lib' import { ContentClientInput } from '../../../src' +import { ContemberClientEntities, FragmentOf, queryBuilder } from '../../client' +const qb = queryBuilder describe('ts types', () => { test('fragment - valid', async () => { expectTypeOf( @@ -21,7 +22,7 @@ describe('ts types', () => { test('where - valid', async () => { qb.list('Post', { filter: { - title: { eq: 'foo' }, + publishedAt: { eq: 'foo' }, }, }, it => it.$$()) }) @@ -36,7 +37,7 @@ describe('ts types', () => { }) test('where - invalid assignment to type', async () => { - const where: ContentClientInput.Where = { + const where: ContentClientInput.Where = { // @ts-expect-error caption: { eq: 'foo' }, } @@ -44,7 +45,7 @@ describe('ts types', () => { test('where - valid extracted without a type', async () => { const where = { - title: { eq: 'foo' }, + publishedAt: { eq: 'foo' }, } qb.list('Post', { filter: where, @@ -52,8 +53,8 @@ describe('ts types', () => { }) test('where - valid extracted with a type', async () => { - const where: ContentClientInput.Where = { - title: { eq: 'foo' }, + const where: ContentClientInput.Where = { + publishedAt: { eq: 'foo' }, } qb.list('Post', { filter: where, @@ -61,8 +62,8 @@ describe('ts types', () => { }) test('where - incompatible types', async () => { - const where: ContentClientInput.Where = { - title: { eq: 'foo' }, + const where: ContentClientInput.Where = { + publishedAt: { eq: 'foo' }, } qb.list('Author', { // @ts-expect-error diff --git a/packages/client-content/tests/client/entities.ts b/packages/client-content/tests/client/entities.ts new file mode 100644 index 0000000000..978d061db4 --- /dev/null +++ b/packages/client-content/tests/client/entities.ts @@ -0,0 +1,108 @@ + +export type JSONPrimitive = string | number | boolean | null +export type JSONValue = JSONPrimitive | JSONObject | JSONArray +export type JSONObject = { readonly [K in string]?: JSONValue } +export type JSONArray = readonly JSONValue[] + +export type Locale = { + name: 'Locale' + unique: + | Omit<{ id: string}, OverRelation> + | Omit<{ code: string}, OverRelation> + columns: { + id: string + code: string + } + hasOne: { + } + hasMany: { + } + hasManyBy: { + } +} +export type Author = { + name: 'Author' + unique: + | Omit<{ id: string}, OverRelation> + | Omit<{ posts: Post['unique']}, OverRelation> + columns: { + id: string + name: string | null + email: string | null + } + hasOne: { + } + hasMany: { + posts: Post<'author'> + } + hasManyBy: { + postsByLocales: { entity: Post; by: {locales: PostLocale['unique']} } + } +} +export type Post = { + name: 'Post' + unique: + | Omit<{ id: string}, OverRelation> + | Omit<{ locales: PostLocale['unique']}, OverRelation> + columns: { + id: string + publishedAt: string | null + } + hasOne: { + author: Author + } + hasMany: { + tags: Tag + locales: PostLocale<'post'> + } + hasManyBy: { + localesByLocale: { entity: PostLocale; by: {locale: Locale['unique']} } + } +} +export type PostLocale = { + name: 'PostLocale' + unique: + | Omit<{ id: string}, OverRelation> + | Omit<{ locale: Locale['unique'], post: Post['unique']}, OverRelation> + columns: { + id: string + title: string | null + content: string | null + } + hasOne: { + locale: Locale + post: Post + } + hasMany: { + } + hasManyBy: { + } +} +export type Tag = { + name: 'Tag' + unique: + | Omit<{ id: string}, OverRelation> + columns: { + id: string + name: string | null + } + hasOne: { + } + hasMany: { + posts: Post + } + hasManyBy: { + } +} + +export type ContemberClientEntities = { + Locale: Locale + Author: Author + Post: Post + PostLocale: PostLocale + Tag: Tag +} + +export type ContemberClientSchema = { + entities: ContemberClientEntities +} diff --git a/packages/client-content/tests/client/enums.ts b/packages/client-content/tests/client/enums.ts new file mode 100644 index 0000000000..336ce12bb9 --- /dev/null +++ b/packages/client-content/tests/client/enums.ts @@ -0,0 +1 @@ +export {} diff --git a/packages/client-content/tests/client/index.ts b/packages/client-content/tests/client/index.ts new file mode 100644 index 0000000000..286908655f --- /dev/null +++ b/packages/client-content/tests/client/index.ts @@ -0,0 +1,17 @@ + +import { ContemberClientNames } from './names' +import type { ContemberClientSchema } from './entities' +import { ContentQueryBuilder, TypedContentQueryBuilder, TypedEntitySelection } from '@contember/client-content' +export * from './names' +export * from './enums' +export * from './entities' + +export const queryBuilder = new ContentQueryBuilder(ContemberClientNames) as unknown as TypedContentQueryBuilder + +export type FragmentOf = +TypedEntitySelection + +export type FragmentType = any> = +T extends TypedEntitySelection + ? TFields + : never diff --git a/packages/client-content/tests/client/names.ts b/packages/client-content/tests/client/names.ts new file mode 100644 index 0000000000..d64cf1b1c8 --- /dev/null +++ b/packages/client-content/tests/client/names.ts @@ -0,0 +1,116 @@ +import { SchemaNames } from '@contember/client-content' +export const ContemberClientNames: SchemaNames = { + "entities": { + "Locale": { + "name": "Locale", + "fields": { + "id": { + "type": "column" + }, + "code": { + "type": "column" + } + }, + "scalars": [ + "id", + "code" + ] + }, + "Author": { + "name": "Author", + "fields": { + "id": { + "type": "column" + }, + "name": { + "type": "column" + }, + "email": { + "type": "column" + }, + "posts": { + "type": "many", + "entity": "Post" + } + }, + "scalars": [ + "id", + "name", + "email" + ] + }, + "Post": { + "name": "Post", + "fields": { + "id": { + "type": "column" + }, + "publishedAt": { + "type": "column" + }, + "tags": { + "type": "many", + "entity": "Tag" + }, + "author": { + "type": "one", + "entity": "Author" + }, + "locales": { + "type": "many", + "entity": "PostLocale" + } + }, + "scalars": [ + "id", + "publishedAt" + ] + }, + "PostLocale": { + "name": "PostLocale", + "fields": { + "id": { + "type": "column" + }, + "locale": { + "type": "one", + "entity": "Locale" + }, + "title": { + "type": "column" + }, + "content": { + "type": "column" + }, + "post": { + "type": "one", + "entity": "Post" + } + }, + "scalars": [ + "id", + "title", + "content" + ] + }, + "Tag": { + "name": "Tag", + "fields": { + "id": { + "type": "column" + }, + "name": { + "type": "column" + }, + "posts": { + "type": "many", + "entity": "Post" + } + }, + "scalars": [ + "id", + "name" + ] + } + } +} \ No newline at end of file diff --git a/packages/client-content/tests/generate.ts b/packages/client-content/tests/generate.ts new file mode 100644 index 0000000000..0092cab344 --- /dev/null +++ b/packages/client-content/tests/generate.ts @@ -0,0 +1,13 @@ +import { ContemberClientGenerator } from '@contember/client-content-generator' +import { schema } from './schema' +import fs from 'node:fs/promises' +import { join } from 'node:path' + +const generator = new ContemberClientGenerator() +const result = await generator.generate(schema.model) +const dir = join(process.cwd(), process.argv[2]) +await fs.mkdir(dir, { recursive: true }) +for (const [name, content] of Object.entries(result)) { + await fs.writeFile(join(dir, name), content, 'utf8') +} + diff --git a/packages/client-content/tests/lib.ts b/packages/client-content/tests/lib.ts new file mode 100644 index 0000000000..e0310bdc32 --- /dev/null +++ b/packages/client-content/tests/lib.ts @@ -0,0 +1,16 @@ +import { ContentClient } from '../src' + +export const createClient = (result?: any) => { + const calls: { query: string, variables: Record }[] = [] + const client = new ContentClient({ + execute: (query: string, options: any): Promise => { + calls.push({ + query, + ...options, + }) + return Promise.resolve(result ?? {}) + }, + }) + return [client, calls] as const +} + diff --git a/packages/client-content/tests/schema.ts b/packages/client-content/tests/schema.ts new file mode 100644 index 0000000000..7db4d1778c --- /dev/null +++ b/packages/client-content/tests/schema.ts @@ -0,0 +1,37 @@ +import { c, createSchema } from '@contember/schema-definition' + +namespace Schema { + export class Locale { + code = c.stringColumn().unique().notNull() + } + + + export class Author { + name = c.stringColumn() + email = c.stringColumn() + posts = c.oneHasMany(Post, 'author') + } + + export class Post { + publishedAt = c.dateTimeColumn() + tags = c.manyHasMany(Tag, 'posts') + author = c.manyHasOne(Author, 'posts') + locales = c.oneHasMany(PostLocale, 'post') + } + + @c.Unique('locale', 'post') + export class PostLocale { + locale = c.manyHasOne(Locale, 'posts') + title = c.stringColumn() + content = c.stringColumn() + post = c.manyHasOne(Post, 'locales') + } + + export class Tag { + name = c.stringColumn() + posts = c.manyHasManyInverse(Post, 'tags') + } +} + + +export const schema = createSchema(Schema) diff --git a/packages/client-content/tests/tsconfig.json b/packages/client-content/tests/tsconfig.json index 833dd61fbe..1afedb882e 100644 --- a/packages/client-content/tests/tsconfig.json +++ b/packages/client-content/tests/tsconfig.json @@ -2,9 +2,11 @@ "extends": "../../../tsconfig.settings.json", "compilerOptions": { "noEmit": true, - "emitDeclarationOnly": false + "emitDeclarationOnly": false, + "experimentalDecorators": true }, "references": [ - { "path": "../src" } + { "path": "../src" }, + { "path": "../../client-content-generator/src" }, ] }