Comments (6)
Could you elaborate a bit more on this? I never use/used a cursor but would offset not defeat the purpose of it?
from nestjs-query.
My understanding is, there are two different types of pagination. Cursor allows for scroll and "get more" kind of pagination. Whereas, offset also allows for showing how many pages are available and navigating better between them. The offset method is good for very large datasets and the need to navigate within those sets. Thing is, and again as I understand nestjs-query, the offset method is possible. Maybe I'm missing something (too), cause to be honest, I've not set up pagination at all yet. So, I too would like more elaboration for my understanding. :)
Scott
from nestjs-query.
offset paging is built in. (I'm using it most of the time to be honest)
here is a full exemple. I've seen you have a lot of vuejs fork on your account so here is a somewhat full example of what i came up with. There is a lot to improve but this should get you started. I'm open to suggestions if you have any.
this example use Quasar framework and urql
module def with
offset
strategy
import {
NestjsQueryGraphQLModule,
PagingStrategies,
} from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { Module } from '@nestjs/common';
import { EventEntity } from './event.entity';
import { EventDto } from './dto/event.dto';
import { CreateEventDto } from './dto/create-event.dto';
import { UpdateEventDto } from './dto/update-event.dto';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([EventEntity])],
resolvers: [
{
pagingStrategy: PagingStrategies.OFFSET,
enableTotalCount: true,
enableSubscriptions: true,
DTOClass: EventDto,
EntityClass: EventEntity,
CreateDTOClass: CreateEventDto,
UpdateDTOClass: UpdateEventDto,
},
],
}),
],
})
export class EventModule {}
import { FilterableField } from '@ptc-org/nestjs-query-graphql';
import { GraphQLISODateTime, ID, ObjectType } from '@nestjs/graphql';
@ObjectType('Event')
export class EventDto {
@FilterableField(() => ID)
id!: number;
@FilterableField()
name!: string;
@FilterableField({ nullable: true })
description?: string;
@FilterableField(() => GraphQLISODateTime, { filterOnly: true })
created!: Date;
@FilterableField(() => GraphQLISODateTime, { filterOnly: true })
updated!: Date;
}
table component
<template>
<q-table
class="sticky-header-table"
square
:style="{height}"
flat
@request="onRequest"
v-model:pagination="pagination"
:rows="data"
:columns="columns"
row-key="id"
virtual-scroll
>
<template #top-left>
<slot name="top"></slot>
</template>
<template #top-right>
<q-input
standout
dense
debounce="300"
v-model="search"
placeholder="Search"
>
<template v-slot:append>
<q-icon name="search"/>
</template>
</q-input>
</template>
</q-table>
</template>
<script lang="ts">
import {defineComponent, ref, watch, toRefs} from 'vue';
import {useQuery} from '@urql/vue';
import {useGqlPagination} from "../composables/pagination";
const FETCH_QUERY = `
query fetchMultipleEvent($paging: OffsetPaging!, $sorting: [EventSort!], $filter: EventFilter) {
events (paging: $paging, sorting: $sorting, filter: $filter) {
totalCount
nodes {
__typename
id
name
description
}
}
}
`;
export default defineComponent({
name: 'EventTable',
props: {height: {type: String, default: '100vh'}},
setup(props) {
const {height} = toRefs(props);
const columns = ref([
{name: 'id', label: 'id', field: 'id', sortable: true},
{name: 'name', label: 'name', field: 'name', sortable: true},
{name: 'description', label: 'description', field: 'description', sortable: true},
{name: 'actions', label: 'actions'},
]);
const data = ref([]);
const {
pagination,
search,
variables,
onRequest
} = useGqlPagination({filter: {fields: [['name', 'like'], ['description', 'like']]}})
const result = useQuery({
query: FETCH_QUERY,
variables,
requestPolicy: 'cache-and-network'
});
watch(result.data, () => {
const payload = result.data?.value?.events ?? {};
data.value = payload.nodes ?? [];
pagination.value.rowsNumber = payload.totalCount ?? 0;
}, {immediate: true});
return {
height,
data,
pagination,
columns,
search,
onRequest
};
}
});
</script>
composable/pagination.ts
import {computed, ref, Ref} from "vue";
type PaginationProps = {
sortBy: string,
descending: boolean,
page: number,
rowsPerPage: number,
rowsNumber: number
};
export type filterOptions = {
fields: [string, string?][],
minChar?: number,
};
type Options = {
pagination?: PaginationProps,
filter?: filterOptions
};
function formatPagination({
page,
rowsPerPage,
rowsNumber,
descending,
sortBy
}: Partial<PaginationProps> = <Partial<PaginationProps>>{}) {
return {
sortBy: sortBy ?? 'id',
descending: descending ?? false,
page: page ?? 1,
rowsPerPage: rowsPerPage ?? 15,
rowsNumber: rowsNumber ?? 0
};
}
function formatPage(pagination: Ref<PaginationProps>) {
const {page, rowsPerPage} = pagination.value
return {
offset: (page - 1) * rowsPerPage,
limit: rowsPerPage
};
}
function formatSort(pagination: Ref<PaginationProps>) {
const {sortBy, descending} = pagination.value
return sortBy ? [{
field: sortBy,
direction: descending ? 'ASC' : 'DESC'
}] : undefined;
}
function formatFilter(search: Ref<string>, {
fields = [],
minChar = 0
}: Partial<filterOptions> = <filterOptions>{}) {
const trimmed = search.value.trim()
if (
trimmed.length <= minChar
|| fields.length === 0
) {
return undefined;
}
return {
or: fields.map(([field, comparator = 'like']) => {
const value = ['like', 'ilike'].includes(comparator) ? `%${trimmed}%` : trimmed;
return {
[field]: {[comparator]: value}
};
})
}
}
export const useGqlPagination = (options: Options = <Options>{}) => {
const search = ref('');
const pagination = ref<PaginationProps>(formatPagination(options?.pagination));
const onRequest = (props: {pagination : PaginationProps}) => {
pagination.value.page = props.pagination.page
pagination.value.rowsPerPage = props.pagination.rowsPerPage
pagination.value.sortBy = props.pagination.sortBy
pagination.value.descending = props.pagination.descending
};
const variables = computed(() => ({
paging: formatPage(pagination),
sorting: formatSort(pagination),
filter: formatFilter(search, options?.filter)
}));
return {
pagination,
search,
variables,
onRequest
};
}
isomorphic urql client setup
// @ts-ignore
import 'isomorphic-unfetch';
import urql, {
dedupExchange,
errorExchange,
fetchExchange,
cacheExchange,
ssrExchange
} from '@urql/vue';
// @ts-ignore
import {SSRData} from '@urql/core/dist/types/exchanges/ssr';
import {ClientOptions} from "@urql/core/dist/types/client";
export type GraphqlOptions = {
app: any;
ssrContext?: any;
clientOptions: ClientOptions,
ssr?: boolean;
subscriptions?: boolean,
onError?: (error: unknown) => void
onUnauthorized?: (error: unknown) => void
}
export const createIsomorphicClient = (opts: GraphqlOptions) => {
const exchanges = [
dedupExchange,
cacheExchange,
errorExchange({
onError: error => {
if (
error.graphQLErrors.some(e => e.message === 'Unauthorized') &&
opts.onUnauthorized
) {
opts.onUnauthorized(error)
} else if (opts.onError) {
opts.onError(error)
}
}
}),
];
if (opts.subscriptions && process.env.CLIENT) {
const {createClient: createWSClient} = require('graphql-ws');
const {subscriptionExchange} = require('@urql/vue');
const wsClient = createWSClient({
url: opts.clientOptions.url.replace('http', 'ws'),
webSocketImpl: ws,
});
exchanges.push(
subscriptionExchange({
forwardSubscription: (operation: any) => ({
subscribe: (sink: any) => ({
unsubscribe: wsClient.subscribe(operation, sink),
}),
}),
}),
);
}
if (opts.ssr) {
const ssr = ssrExchange({
//@ts-ignore
isClient: process.env.CLIENT === true,
initialState: !process.env.SERVER ? deserializeStore() : undefined
});
exchanges.push(ssr);
if (opts.ssrContext) {
opts.ssrContext?.onRendered(() => {
opts.ssrContext.graphData = serializeStore(ssr.extractData());
})
}
}
exchanges.push(fetchExchange);
opts.app.use(urql, {
exchanges,
...opts.clientOptions,
});
}
const dataKey = '__GRAPH_DATA__';
declare global {
interface window {
[dataKey]: string | unknown; // SSRData | undefined;
}
}
// @ts-ignore
declare module '@quasar/app-webpack/types/ssr' {
interface QSsrContext {
graphData: string;
}
}
function serializeStore(data: SSRData): string {
return '"' + btoa(JSON.stringify(data)) + '"';
}
function deserializeStore(): SSRData | undefined {
if (process.env.CLIENT) {
// @ts-ignore
if (typeof window !== "undefined" && window[dataKey]) {
// @ts-ignore
return JSON.parse(atob(window[dataKey] as string));
} else {
return undefined;
}
}
return undefined;
}
boot file
import {boot} from 'quasar/wrappers'
import {createIsomorphicClient} from 'frontend-graphql-client';
export default boot(({app, ssrContext}) => {
createIsomorphicClient({
app,
ssr: true,
subscriptions: true,
ssrContext,
clientOptions: {
url: 'http://localhost:8080/graphql'
}
});
});
from nestjs-query.
OFFSET strategy already has totalCount it can be closed @TriPSs
and this is follow the suggestion https://graphql.org/learn/pagination/#pagination-and-edges
and this issue when it was first proposed there is no OFFSET strategy and the early OFFSET strategy has no totalCount
from nestjs-query.
@Goopil - Awesome. I didn't expect to get that. I'm bookmarking for the example to use in the future, as I have to first get going with a working nestjs-query-typgoose package and then some other things, but I'll be working on pagination too at some point. So thanks. 😁
Scott
from nestjs-query.
Well it's no big deal. We are not all obligated to struggle and figure out the same thing. I wouldn't be the dev I'm today without the great work of others. It's a normal thing to share back some knowledge.
Hope it helps you get started.
Regarding this issue. I thing, all we need is already included right ? Might as well close it.
from nestjs-query.
Related Issues (20)
- MikroORM support HOT 1
- Filterable nested object in mongoose HOT 9
- RelationsInput is not known HOT 5
- ResolveField can't be resolved properly HOT 3
- typeform prefix column with graphql filter HOT 2
- @Authorize decorator not returning unauthorized when a create request is made HOT 2
- Nested sorting HOT 3
- Cannot use 2 offset and cursor pagination at the same time HOT 8
- how to best import modules to leverage custom services? HOT 1
- Add Fuzzy Search Support using fuse.js HOT 1
- Custom Assembler: TypeORM module inconsistently calling async methods HOT 6
- Option to disable pluralization of relation name HOT 2
- Infinite depth in DTO but not in suscriptions HOT 3
- TypeORM - Simple-array doesn't work HOT 6
- Adding startsWith / endsWith / contains options in StringFieldComparisons
- Add support for `FULLTEXT` indexes
- Nest can't resolve dependencies of the MarkdownActivityAuthorizerInterceptor HOT 2
- Error: No fields found to create FilterType. HOT 2
- Will FilterableField decorator support array values? HOT 1
- Alias And Field Names In Generated SQL For Aggregate GroupBy Date Are Not Properly Escaped With Quotes HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from nestjs-query.