Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add order query parameter #1789

Merged
merged 2 commits into from Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
68 changes: 68 additions & 0 deletions lib/types/query/order.ts
@@ -0,0 +1,68 @@
import { FieldsType } from './util'
import { EntryFields, EntrySys } from '../entry'
import { AssetSys } from '../asset'
import { ConditionalPick } from 'type-fest'
import { TagSys } from '../tag'

type SupportedTypes =
| EntryFields.Symbol
| EntryFields.Integer
| EntryFields.Number
| EntryFields.Date
| EntryFields.Boolean
| undefined

type SupportedLinkTypes = EntryFields.AssetLink | EntryFields.EntryLink<any> | undefined

export type OrderFilterPaths<Fields extends FieldsType, Prefix extends string> =
| `${Prefix}.${keyof ConditionalPick<Fields, SupportedTypes> & string}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the & string here? Isn't keyof FieldsType always a string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases it’s just string. Here we get string | number so we have to remove number to make TS happy.

| `-${Prefix}.${keyof ConditionalPick<Fields, SupportedTypes> & string}`

/**
* @desc order for entries
* @see [documentation]{@link https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/order}
*/
export type EntryOrderFilterWithFields<Fields extends FieldsType> = {
order?: (
| OrderFilterPaths<Fields, 'fields'>
| `fields.${keyof ConditionalPick<Fields, SupportedLinkTypes> & string}.sys.id`
| `-fields.${keyof ConditionalPick<Fields, SupportedLinkTypes> & string}.sys.id`
| OrderFilterPaths<EntrySys, 'sys'>
| 'sys.contentType.sys.id'
| '-sys.contentType.sys.id'
)[]
}

/**
* @desc order for entries
* @see [documentation]{@link https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/order}
*/
export type EntryOrderFilter = {
order?: (
| OrderFilterPaths<EntrySys, 'sys'>
| 'sys.contentType.sys.id'
| '-sys.contentType.sys.id'
)[]
}

/**
* @desc order for assets
* @see [documentation]{@link https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/order}
*/
export type AssetOrderFilter = {
order?: (
| OrderFilterPaths<AssetSys, 'sys'>
| 'fields.file.contentType'
| '-fields.file.contentType'
| 'fields.file.fileName'
| '-fields.file.fileName'
)[]
}

/**
* @desc order for tags
* @see [documentation]{@link https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/order}
*/
export type TagOrderFilter = {
order?: (OrderFilterPaths<TagSys, 'sys'> | 'name' | '-name')[]
}
16 changes: 13 additions & 3 deletions lib/types/query/query.ts
Expand Up @@ -12,6 +12,12 @@ import { ReferenceSearchFilters } from './reference'
import { TagSys } from '../sys'
import { Metadata } from '../metadata'
import { TagLink } from '../link'
import {
AssetOrderFilter,
EntryOrderFilter,
EntryOrderFilterWithFields,
TagOrderFilter,
} from './order'

type FixedPagedOptions = {
skip?: number
Expand Down Expand Up @@ -45,6 +51,7 @@ export type MetadataTagsQueries =

export type EntryFieldsQueries<Fields extends FieldsType> =
| EntrySelectFilterWithFields<Fields>
| EntryOrderFilterWithFields<Fields>
| ExistenceFilter<Fields, 'fields'>
| EqualityFilter<Fields, 'fields'>
| InequalityFilter<Fields, 'fields'>
Expand All @@ -59,9 +66,10 @@ export type EntriesQueries<Fields extends FieldsType> =
| (SysQueries<Pick<EntrySys, 'createdAt' | 'updatedAt' | 'revision' | 'id' | 'type'>> &
MetadataTagsQueries &
EntrySelectFilter &
EntryOrderFilter &
FixedQueryOptions &
FixedPagedOptions &
FixedLinkOptions & { order?: string })
FixedLinkOptions)

export type EntryQueries = Omit<FixedQueryOptions, 'query'>

Expand All @@ -70,14 +78,15 @@ export type AssetFieldsQueries<Fields extends FieldsType> = ExistenceFilter<Fiel
InequalityFilter<Fields, 'fields'> &
FullTextSearchFilters<Fields, 'fields'> &
AssetSelectFilter<Fields> &
AssetOrderFilter &
RangeFilters<Fields, 'fields'> &
SubsetFilters<Fields, 'fields'>

export type AssetQueries<Fields extends FieldsType> = AssetFieldsQueries<Fields> &
SysQueries<Pick<AssetSys, 'createdAt' | 'updatedAt' | 'revision' | 'id' | 'type'>> &
MetadataTagsQueries &
FixedQueryOptions &
FixedPagedOptions & { mimetype_group?: AssetMimeType } & { order?: string }
FixedPagedOptions & { mimetype_group?: AssetMimeType }

export type TagNameFilters = {
'name[exists]'?: boolean
Expand All @@ -90,4 +99,5 @@ export type TagNameFilters = {

export type TagQueries = TagNameFilters &
SysQueries<Pick<TagSys, 'createdAt' | 'updatedAt' | 'visibility' | 'id' | 'type'>> &
FixedPagedOptions & { order?: string }
TagOrderFilter &
FixedPagedOptions
6 changes: 3 additions & 3 deletions test/integration/getEntries.test.ts
Expand Up @@ -241,7 +241,7 @@ describe('getEntries via chained clients', () => {

test('Gets entries by creation order', async () => {
const response = await client.getEntries({
order: 'sys.createdAt',
order: ['sys.createdAt'],
})

expect(new Date(response.items[0].sys.createdAt).getTime()).toBeLessThan(
Expand All @@ -251,7 +251,7 @@ describe('getEntries via chained clients', () => {

test('Gets entries by inverse creation order', async () => {
const response = await client.getEntries({
order: '-sys.createdAt',
order: ['-sys.createdAt'],
})

expect(new Date(response.items[0].sys.createdAt).getTime()).toBeGreaterThan(
Expand All @@ -270,7 +270,7 @@ describe('getEntries via chained clients', () => {
*/
test('Gets entries by creation order and id order', async () => {
const response = await client.getEntries({
order: 'sys.contentType.sys.id,sys.id',
order: ['sys.contentType.sys.id', 'sys.id'],
})

const contentTypeOrder = response.items
Expand Down
15 changes: 15 additions & 0 deletions test/types/queries/asset-queries.test-d.ts
Expand Up @@ -123,6 +123,21 @@ expectNotAssignable<DefaultAssetQueries>({
'sys.unknownProp[nin]': mocks.anyValue,
})

// order operator

expectAssignable<DefaultAssetQueries>({ order: ['sys.createdAt'] })
veu marked this conversation as resolved.
Show resolved Hide resolved
expectNotAssignable<DefaultAssetQueries>({ order: ['sys.unknownProperty'] })

expectAssignable<DefaultAssetQueries>({
order: [
'fields.file.contentType',
'-fields.file.contentType',
'fields.file.fileName',
'-fields.file.fileName',
],
})
expectNotAssignable<DefaultAssetQueries>({ order: ['fields.unknownField'] })

// select operator

expectAssignable<DefaultAssetQueries>({ select: ['sys'] })
Expand Down
28 changes: 28 additions & 0 deletions test/types/queries/entry-queries.test-d.ts
Expand Up @@ -232,6 +232,34 @@ expectNotAssignable<EntriesQueries<{ numberField: number; stringArrayField: stri
'fields.unknownField[nin]': mocks.anyValue,
})

// order operator

expectAssignable<EntriesQueries<{ someField: string }>>({
order: ['sys.createdAt', '-sys.createdAt'],
})
expectNotAssignable<EntriesQueries<{ someField: string }>>({ order: ['sys.unknownProperty'] })

expectNotAssignable<EntriesQueries<{ someField: string }>>({ order: ['fields.someField'] })
expectAssignable<EntriesQueries<{ someField: string }>>({
content_type: 'id',
order: ['fields.someField', '-fields.someField'],
})
expectAssignable<
EntriesQueries<{ mediaField: EntryFields.AssetLink; referenceField: EntryFields.EntryLink<any> }>
>({
content_type: 'id',
order: [
'fields.mediaField.sys.id',
'-fields.mediaField.sys.id',
'fields.referenceField.sys.id',
'-fields.referenceField.sys.id',
],
})
expectNotAssignable<EntriesQueries<{ someField: string }>>({
content_type: 'id',
order: ['fields.unknownField'],
})

// select operator

expectAssignable<EntriesQueries<{ someField: string }>>({ select: ['sys'] })
Expand Down
11 changes: 10 additions & 1 deletion test/types/queries/tag-queries.test-d.ts
Expand Up @@ -115,7 +115,16 @@ expectNotAssignable<TagQueries>({ 'sys.updatedBy[nin]': mocks.anyValue })
expectNotAssignable<TagQueries>({ 'sys.version[nin]': mocks.anyValue })
expectNotAssignable<TagQueries>({ 'sys.revision[nin]': mocks.anyValue })

// order operator

expectAssignable<TagQueries>({
order: ['sys.id', 'sys.createdAt', 'sys.updatedAt', 'sys.visibility', 'sys.type'],
})
expectAssignable<TagQueries>({
order: ['-sys.id', '-sys.createdAt', '-sys.updatedAt', '-sys.visibility', '-sys.type'],
})

// Fixed query filters

expectAssignable<TagQueries>({ skip: 1, limit: 1, order: 'sys.updatedAt' })
expectAssignable<TagQueries>({ skip: 1, limit: 1 })
expectNotAssignable<TagQueries>({ locale: 'en' })
6 changes: 6 additions & 0 deletions test/types/query-types/boolean.test-d.ts
Expand Up @@ -9,6 +9,7 @@ import { EntrySelectFilterWithFields } from '../../../lib/types/query/select'
import { SubsetFilters } from '../../../lib/types/query/subset'
// @ts-ignore
import * as mocks from '../mocks'
import { EntryOrderFilterWithFields } from '../../../lib/types/query/order'

expectAssignable<EqualityFilter<{ testField: EntryFields.Boolean }, 'fields'>>({})
expectType<Required<EqualityFilter<{ testField: EntryFields.Boolean }, 'fields'>>>({
Expand All @@ -31,6 +32,11 @@ expectAssignable<Required<RangeFilters<{ testField: EntryFields.Boolean }, 'fiel

expectAssignable<Required<FullTextSearchFilters<{ testField: EntryFields.Boolean }, 'fields'>>>({})

expectAssignable<EntryOrderFilterWithFields<{ testField: EntryFields.Boolean }>>({})
expectAssignable<Required<EntryOrderFilterWithFields<{ testField: EntryFields.Boolean }>>>({
order: ['fields.testField', '-fields.testField'],
})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.Boolean }>>({})
expectAssignable<Required<EntrySelectFilterWithFields<{ testField: EntryFields.Boolean }>>>({
select: ['fields.testField'],
Expand Down
6 changes: 6 additions & 0 deletions test/types/query-types/date.test-d.ts
Expand Up @@ -9,6 +9,7 @@ import { EntrySelectFilterWithFields } from '../../../lib/types/query/select'
import { SubsetFilters } from '../../../lib/types/query/subset'
// @ts-ignore
import * as mocks from '../mocks'
import { EntryOrderFilterWithFields } from '../../../lib/types/query/order'

expectAssignable<EqualityFilter<{ testField: EntryFields.Date }, 'fields'>>({})
expectType<Required<EqualityFilter<{ testField: EntryFields.Date }, 'fields'>>>({
Expand Down Expand Up @@ -40,6 +41,11 @@ expectType<Required<FullTextSearchFilters<{ testField: EntryFields.Date }, 'fiel
'fields.testField[match]': mocks.stringValue,
})

expectAssignable<EntryOrderFilterWithFields<{ testField: EntryFields.Date }>>({})
expectAssignable<Required<EntryOrderFilterWithFields<{ testField: EntryFields.Date }>>>({
order: ['fields.testField', '-fields.testField'],
})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.Date }>>({})
expectAssignable<Required<EntrySelectFilterWithFields<{ testField: EntryFields.Date }>>>({
select: ['fields.testField'],
Expand Down
9 changes: 8 additions & 1 deletion test/types/query-types/integer.test-d.ts
Expand Up @@ -9,6 +9,7 @@ import { EntrySelectFilterWithFields } from '../../../lib/types/query/select'
import { SubsetFilters } from '../../../lib/types/query/subset'
// @ts-ignore
import * as mocks from '../mocks'
import { EntryOrderFilterWithFields } from '../../../lib/types/query/order'

expectAssignable<EqualityFilter<{ testField: EntryFields.Integer }, 'fields'>>({})
expectType<Required<EqualityFilter<{ testField: EntryFields.Integer }, 'fields'>>>({
Expand Down Expand Up @@ -37,7 +38,13 @@ expectType<Required<RangeFilters<{ testField: EntryFields.Integer }, 'fields'>>>

expectAssignable<Required<FullTextSearchFilters<{ testField: EntryFields.Integer }, 'fields'>>>({})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.Integer }>>({
expectAssignable<EntryOrderFilterWithFields<{ testField: EntryFields.Integer }>>({})
expectAssignable<Required<EntryOrderFilterWithFields<{ testField: EntryFields.Integer }>>>({
order: ['fields.testField', '-fields.testField'],
})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.Integer }>>({})
expectAssignable<Required<EntrySelectFilterWithFields<{ testField: EntryFields.Integer }>>>({
select: ['fields.testField'],
})

Expand Down
8 changes: 7 additions & 1 deletion test/types/query-types/location.test-d.ts
Expand Up @@ -9,6 +9,7 @@ import { EntrySelectFilterWithFields } from '../../../lib/types/query/select'
import { SubsetFilters } from '../../../lib/types/query/subset'
// @ts-ignore
import * as mocks from '../mocks'
import { EntryOrderFilterWithFields } from '../../../lib/types/query/order'

expectAssignable<Required<EqualityFilter<{ testField: EntryFields.Location }, 'fields'>>>({})

Expand Down Expand Up @@ -42,7 +43,12 @@ expectAssignable<Required<RangeFilters<{ testField: EntryFields.Location }, 'fie

expectAssignable<Required<FullTextSearchFilters<{ testField: EntryFields.Location }, 'fields'>>>({})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.Location }>>({
expectNotAssignable<Required<EntryOrderFilterWithFields<{ testField: EntryFields.Location }>>>({
order: ['fields.testField'],
})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.Location }>>({})
expectAssignable<Required<EntrySelectFilterWithFields<{ testField: EntryFields.Location }>>>({
select: ['fields.testField'],
})

Expand Down
6 changes: 6 additions & 0 deletions test/types/query-types/number.test-d.ts
Expand Up @@ -9,6 +9,7 @@ import { EntrySelectFilterWithFields } from '../../../lib/types/query/select'
import { SubsetFilters } from '../../../lib/types/query/subset'
// @ts-ignore
import * as mocks from '../mocks'
import { EntryOrderFilterWithFields } from '../../../lib/types/query/order'

expectAssignable<EqualityFilter<{ testField: EntryFields.Number }, 'fields'>>({})
expectType<Required<EqualityFilter<{ testField: EntryFields.Number }, 'fields'>>>({
Expand Down Expand Up @@ -37,6 +38,11 @@ expectType<Required<RangeFilters<{ testField: EntryFields.Number }, 'fields'>>>(

expectAssignable<Required<FullTextSearchFilters<{ testField: EntryFields.Number }, 'fields'>>>({})

expectAssignable<EntryOrderFilterWithFields<{ testField: EntryFields.Number }>>({})
expectAssignable<Required<EntryOrderFilterWithFields<{ testField: EntryFields.Number }>>>({
order: ['fields.testField', '-fields.testField'],
})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.Number }>>({})
expectAssignable<Required<EntrySelectFilterWithFields<{ testField: EntryFields.Number }>>>({
select: ['fields.testField'],
Expand Down
7 changes: 6 additions & 1 deletion test/types/query-types/object.test-d.ts
@@ -1,4 +1,4 @@
import { expectAssignable, expectType } from 'tsd'
import { expectAssignable, expectNotAssignable, expectType } from 'tsd'
import { EntryFields } from '../../../lib'
import { EqualityFilter, InequalityFilter } from '../../../lib/types/query/equality'
import { ExistenceFilter } from '../../../lib/types/query/existence'
Expand All @@ -7,6 +7,7 @@ import { RangeFilters } from '../../../lib/types/query/range'
import { FullTextSearchFilters } from '../../../lib/types/query/search'
import { EntrySelectFilterWithFields } from '../../../lib/types/query/select'
import { SubsetFilters } from '../../../lib/types/query/subset'
import { EntryOrderFilterWithFields } from '../../../lib/types/query/order'
// @ts-ignore
import * as mocks from '../mocks'

Expand All @@ -25,6 +26,10 @@ expectAssignable<Required<RangeFilters<{ testField: EntryFields.Object }, 'field

expectAssignable<Required<FullTextSearchFilters<{ testField: EntryFields.Object }, 'fields'>>>({})

expectNotAssignable<EntryOrderFilterWithFields<{ testField: EntryFields.Object }>>({
order: ['fields.testField'],
})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.Object }>>({})
expectAssignable<Required<EntrySelectFilterWithFields<{ testField: EntryFields.Object }>>>({
select: ['fields.testField'],
Expand Down
7 changes: 6 additions & 1 deletion test/types/query-types/richtext.test-d.ts
@@ -1,4 +1,4 @@
import { expectAssignable, expectType } from 'tsd'
import { expectAssignable, expectNotAssignable, expectType } from 'tsd'
import { EntryFields } from '../../../lib'
import { EqualityFilter, InequalityFilter } from '../../../lib/types/query/equality'
import { ExistenceFilter } from '../../../lib/types/query/existence'
Expand All @@ -9,6 +9,7 @@ import { EntrySelectFilterWithFields } from '../../../lib/types/query/select'
import { SubsetFilters } from '../../../lib/types/query/subset'
// @ts-ignore
import * as mocks from '../mocks'
import { EntryOrderFilterWithFields } from '../../../lib/types/query/order'

expectAssignable<Required<EqualityFilter<{ testField: EntryFields.RichText }, 'fields'>>>({})

Expand All @@ -28,6 +29,10 @@ expectType<Required<FullTextSearchFilters<{ testField: EntryFields.RichText }, '
'fields.testField[match]': mocks.stringValue,
})

expectNotAssignable<EntryOrderFilterWithFields<{ testField: EntryFields.Object }>>({
order: ['fields.testField'],
})

expectAssignable<EntrySelectFilterWithFields<{ testField: EntryFields.RichText }>>({})
expectAssignable<Required<EntrySelectFilterWithFields<{ testField: EntryFields.RichText }>>>({
select: ['fields.testField'],
Expand Down