From 3e42da4e2aebc117d26d6f5ce33199031624620c Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 15 Apr 2024 13:25:48 +0200 Subject: [PATCH 1/3] refactor(client-content): revamp types of TypedEntitySelection for better TS code intel Instead of multiple signatures, use variadic union arg --- .../src/nodes/TypedEntitySelection.ts | 166 +++++++++--------- 1 file changed, 80 insertions(+), 86 deletions(-) diff --git a/packages/client-content/src/nodes/TypedEntitySelection.ts b/packages/client-content/src/nodes/TypedEntitySelection.ts index 95086cde3..aa1380085 100644 --- a/packages/client-content/src/nodes/TypedEntitySelection.ts +++ b/packages/client-content/src/nodes/TypedEntitySelection.ts @@ -1,13 +1,7 @@ -import { EntityTypeLike, SchemaTypeLike } from '../types/Schema' +import { EntityTypeLike, SchemaTypeLike } from '../types' import { ContentClientInput } from '../types' import { ContentEntitySelectionContext } from './ContentEntitySelection' -export type TypedEntitySelectionCallback< - TSchema extends SchemaTypeLike, - EntityName extends string, - TEntity extends EntityTypeLike, - TValue -> = (select: TypedEntitySelection) => TypedEntitySelection export interface TypedEntitySelection { @@ -19,93 +13,93 @@ export interface TypedEntitySelection $< - TKey extends (keyof TEntity['columns']) & string, + TNestedValue, + TKey extends (keyof TEntity['columns'] | keyof TEntity['hasMany'] | keyof TEntity['hasManyBy'] | keyof TEntity['hasOne']) & string, TAlias extends string | null = null >( name: TKey, - args?: {as?: TAlias}, + ...args: TypedEntitySelectionParams ): TypedEntitySelection }> +} - $< - TNestedValue, - TNestedKey extends keyof TEntity['hasMany'] & string, - TAlias extends string | null = null, - >( - name: TNestedKey, - args: ContentClientInput.HasManyRelationInput & { as?: TAlias }, - fields: - | TypedEntitySelectionCallback - | TypedEntitySelection, - ): TypedEntitySelection +export type TypedEntitySelectionCallback< + TSchema extends SchemaTypeLike, + EntityName extends string, + TEntity extends EntityTypeLike, + TValue, +> = (select: TypedEntitySelection) => TypedEntitySelection - $< - TNestedValue, - TNestedKey extends keyof TEntity['hasMany'] & string, - TAlias extends string | null = null, - >( - name: TNestedKey, - fields: - | TypedEntitySelectionCallback - | TypedEntitySelection, - ): TypedEntitySelection - $< - TNestedValue, - TNestedKey extends keyof TEntity['hasManyBy'] & string, - TAlias extends string | null = null, - >( - name: TNestedKey, - args: ContentClientInput.HasManyByRelationInput & { as?: TAlias }, - fields: - | TypedEntitySelectionCallback - | TypedEntitySelection, - ): TypedEntitySelection +export type TypedHasManyArgs = + & ContentClientInput.HasManyRelationInput + & { as?: TAlias } - $< - TNestedValue, - TNestedKey extends keyof TEntity['hasManyBy'] & string, - TAlias extends string | null = null, - >( - name: TNestedKey, - fields: - | TypedEntitySelectionCallback - | TypedEntitySelection, - ): TypedEntitySelection +export type TypedHasManyFields = + | TypedEntitySelectionCallback + | TypedEntitySelection - $< - TNestedValue extends { [K in string]: unknown }, - TNestedKey extends keyof TEntity['hasOne'] & string, - TAlias extends string | null = null, - >( - name: TNestedKey, - args: ContentClientInput.HasOneRelationInput & { as?: TAlias }, - fields: - | TypedEntitySelectionCallback - | TypedEntitySelection, - ): TypedEntitySelection +export type TypedHasManyParams = + | [ + args: TypedHasManyArgs, + fields: TypedHasManyFields, + ] + | [ + fields: TypedHasManyFields, + ] - $< - TNestedValue extends { [K in string]: unknown }, - TNestedKey extends keyof TEntity['hasOne'] & string, - TAlias extends string | null = null, - >( - name: TNestedKey, - fields: - | TypedEntitySelectionCallback - | TypedEntitySelection, - ): TypedEntitySelection -} + +export type TypedHasManyByArgs = + & ContentClientInput.HasManyByRelationInput + & { as?: TAlias } + +export type TypedHasManyByFields = + | TypedEntitySelectionCallback + | TypedEntitySelection + +export type TypedHasManyByParams = + | [ + args: TypedHasManyByArgs, + fields: TypedHasManyByFields + ] + + +export type TypedHasOneArgs = + & ContentClientInput.HasOneRelationInput + & { as?: TAlias } + +export type TypedHasOneFields = + | TypedEntitySelectionCallback + | TypedEntitySelection + +export type TypedHasOneParams = + | [ + args: TypedHasOneArgs, + fields: TypedHasOneFields + ] + | [ + fields: TypedHasOneFields + ] + + +export type TypedColumnArgs = { as?: TAlias } +export type TypedColumnParams = + | [ + args: TypedColumnArgs + ] + | [] + +export type TypedEntitySelectionParams = + TKey extends keyof TEntity['columns'] ? TypedColumnParams + : TKey extends keyof TEntity['hasMany'] ? TypedHasManyParams + : TKey extends keyof TEntity['hasManyBy'] ? TypedHasManyByParams + : TKey extends keyof TEntity['hasOne'] ? TypedHasOneParams + : never + +export type TypedEntitySelectionResult = + TKey extends keyof TEntity['columns'] ? TEntity['columns'][TKey] + : TKey extends keyof TEntity['hasMany'] ? TValue[] + : TKey extends keyof TEntity['hasManyBy'] ? null | TValue + : TKey extends keyof TEntity['hasOne'] ? null | TValue + : never From 1544de78cedeabb87143dd8eceebca7eb091e4c7 Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 15 Apr 2024 13:26:01 +0200 Subject: [PATCH 2/3] tests(client-content): more type tests --- .../tests/cases/unit/types.test.ts | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/packages/client-content/tests/cases/unit/types.test.ts b/packages/client-content/tests/cases/unit/types.test.ts index ff82a8f53..8c6c19950 100644 --- a/packages/client-content/tests/cases/unit/types.test.ts +++ b/packages/client-content/tests/cases/unit/types.test.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { assertType, describe, expectTypeOf, test } from 'vitest' +import { assertType, describe, expect, expectTypeOf, test } from 'vitest' import { ContentClientInput } from '../../../src' -import { ContemberClientEntities, FragmentOf, queryBuilder } from '../../client' +import { ContemberClientEntities, FragmentOf, FragmentType, queryBuilder } from '../../client' const qb = queryBuilder describe('ts types', () => { @@ -70,4 +70,75 @@ describe('ts types', () => { filter: where, }, it => it.$$()) }) + + test('has one field on fragment type', async () => { + const fragment = qb.fragment('Post', it => it.$('author', it => it.$$().$('posts', it => it.$('id')))) + type fragmentType = FragmentType + expectTypeOf().toEqualTypeOf<{ author: ({ id: string; name: string | null; email: string | null; } & { posts: { id: string; }[]; }) | null; }>() + }) + + + test('has one without arg', async () => { + qb.list('Post', {}, it => it.$('author', it => it.$$())) + }) + + test('has one with arg', async () => { + qb.list('Post', {}, it => it.$('author', { filter: { name: { eq: 'John' } } }, it => it.$$())) + }) + + test('has one with invalid arg', async () => { + qb.list('Post', {}, it => it + .$('author', + // @ts-expect-error + { filter: { foo: { eq: 'xx' } } }, + it => it.$$(), + ), + ) + }) + + test('has many without arg', async () => { + qb.list('Post', {}, it => it.$('locales', it => it.$$())) + }) + + test('has many with arg', async () => { + qb.list('Post', {}, it => it.$('locales', { filter: { locale: { code: { eq: 'cs' } } } }, it => it.$$())) + }) + + test('has many with invalid arg', async () => { + qb.list('Post', {}, it => it + .$('locales', + // @ts-expect-error + { filter: { locale: { eq: 'cs' } } }, + it => it.$$(), + ), + ) + }) + + test('reduced has may', async () => { + qb.list('Post', {}, it => it.$('localesByLocale', { by: { locale: { code: 'cs' } } }, it => it.$$())) + }) + + test('reduced has may - invalid "by"', async () => { + qb.list('Post', + {}, + it => it + .$('localesByLocale', + // @ts-expect-error + { by: { locale: {} } }, + qb.fragment('PostLocale'), + ), + ) + }) + + + test('invalid field', async () => { + expect(() => { + qb.list( + 'Post', + {}, + // @ts-expect-error + it => it.$('lorem', it => it.$$()), + ) + }).toThrow('Field Post.lorem does not exist.') + }) }) From c90c754e18e4acbe2586fe4f496c3fecc83a598f Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 15 Apr 2024 13:54:36 +0200 Subject: [PATCH 3/3] chore: ae up --- build/api/client-content.api.md | 102 ++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 38 deletions(-) diff --git a/build/api/client-content.api.md b/build/api/client-content.api.md index 06ee4f38d..f997b206b 100644 --- a/build/api/client-content.api.md +++ b/build/api/client-content.api.md @@ -451,6 +451,16 @@ export type TransactionResult = { readonly data: V; }; +// @public (undocumented) +export type TypedColumnArgs = { + as?: TAlias; +}; + +// @public (undocumented) +export type TypedColumnParams = [ +args: TypedColumnArgs +] | []; + // @public (undocumented) export type TypedContentEntitySelectionOrCallback = TypedEntitySelection | TypedEntitySelectionCallback; @@ -491,44 +501,8 @@ export interface TypedEntitySelection; // (undocumented) - $(name: TKey, args?: { - as?: TAlias; - }): TypedEntitySelection; - // (undocumented) - $(name: TNestedKey, args: ContentClientInput.HasManyRelationInput & { - as?: TAlias; - }, fields: TypedEntitySelectionCallback | TypedEntitySelection): TypedEntitySelection; - // (undocumented) - $(name: TNestedKey, fields: TypedEntitySelectionCallback | TypedEntitySelection): TypedEntitySelection; - // (undocumented) - $(name: TNestedKey, args: ContentClientInput.HasManyByRelationInput & { - as?: TAlias; - }, fields: TypedEntitySelectionCallback | TypedEntitySelection): TypedEntitySelection; - // (undocumented) - $(name: TNestedKey, fields: TypedEntitySelectionCallback | TypedEntitySelection): TypedEntitySelection; - // (undocumented) - $(name: TNestedKey, args: ContentClientInput.HasOneRelationInput & { - as?: TAlias; - }, fields: TypedEntitySelectionCallback | TypedEntitySelection): TypedEntitySelection; - // (undocumented) - $(name: TNestedKey, fields: TypedEntitySelectionCallback | TypedEntitySelection): TypedEntitySelection(name: TKey, ...args: TypedEntitySelectionParams): TypedEntitySelection; }>; // @internal (undocumented) readonly context: ContentEntitySelectionContext; @@ -537,6 +511,58 @@ export interface TypedEntitySelection = (select: TypedEntitySelection) => TypedEntitySelection; +// @public (undocumented) +export type TypedEntitySelectionParams = TKey extends keyof TEntity['columns'] ? TypedColumnParams : TKey extends keyof TEntity['hasMany'] ? TypedHasManyParams : TKey extends keyof TEntity['hasManyBy'] ? TypedHasManyByParams : TKey extends keyof TEntity['hasOne'] ? TypedHasOneParams : never; + +// @public (undocumented) +export type TypedEntitySelectionResult = TKey extends keyof TEntity['columns'] ? TEntity['columns'][TKey] : TKey extends keyof TEntity['hasMany'] ? TValue[] : TKey extends keyof TEntity['hasManyBy'] ? null | TValue : TKey extends keyof TEntity['hasOne'] ? null | TValue : never; + +// @public (undocumented) +export type TypedHasManyArgs = ContentClientInput.HasManyRelationInput & { + as?: TAlias; +}; + +// @public (undocumented) +export type TypedHasManyByArgs = ContentClientInput.HasManyByRelationInput & { + as?: TAlias; +}; + +// @public (undocumented) +export type TypedHasManyByFields = TypedEntitySelectionCallback | TypedEntitySelection; + +// @public (undocumented) +export type TypedHasManyByParams = [ +args: TypedHasManyByArgs, +fields: TypedHasManyByFields +]; + +// @public (undocumented) +export type TypedHasManyFields = TypedEntitySelectionCallback | TypedEntitySelection; + +// @public (undocumented) +export type TypedHasManyParams = [ +args: TypedHasManyArgs, +fields: TypedHasManyFields +] | [ +fields: TypedHasManyFields +]; + +// @public (undocumented) +export type TypedHasOneArgs = ContentClientInput.HasOneRelationInput & { + as?: TAlias; +}; + +// @public (undocumented) +export type TypedHasOneFields = TypedEntitySelectionCallback | TypedEntitySelection; + +// @public (undocumented) +export type TypedHasOneParams = [ +args: TypedHasOneArgs, +fields: TypedHasOneFields +] | [ +fields: TypedHasOneFields +]; + // @public (undocumented) export type ValidationError = { readonly path: Path;