Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,110 @@ wServices['article'].some({

This only returns the articleNumber property of all articles.

#### Ordering

With the some function you can order requested data. You can either merely order by fields or by complex expressions.

```ts
/**
* Order by createdDate in ascending order.
*
* ?orderBy=createdDate asc
*/
wServices['article'].some({
orderBy: [{ FIELD: 'createdDate', SORT: 'asc' }]
});
```

```ts
/**
* First order by createdDate in ascending order, then by articleNumber in descending order
* If you omit SORT the default ordering will be set to ascending.
*
* ?orderBy=createdDate asc, articleNumber desc
*/
wServices['article'].some({
orderBy: [{ FIELD: 'createdDate' }, { FIELD: 'articleNumber', SORT: 'desc' }]
});
```

```ts
/**
* Order with conditional sorting: if internalNote is not null then 1,
* if packagingQuantity > 400 then 2, otherwise 3.
* Then order by articleNumber in ascending order.
*
* ?orderBy=(not internalNote null) ? 1 : (packagingQuantity > 400) ? 2 : 3 asc, articleNumber asc
*/
wServices['article'].some({
orderBy: [
{
CASE: [
{ WHEN: { internalNote: { NULL: false } }, THEN: 1 },
{ WHEN: { packagingQuantity: { GT: 400 } }, THEN: 2 }
],
ELSE: 3,
SORT: 'asc'
},
{ FIELD: 'articleNumber' }
]
});
```

The `THEN` and `ELSE` values can also be a `FieldOrderBy` to fall back to a field-based ordering:

```ts
/**
* Order with conditional sorting: if internalNote is not null, order by articleNumber,
* otherwise order by createdDate.
*
* ?orderBy=(not internalNote null) ? articleNumber : createdDate asc
*/
wServices['article'].some({
orderBy: [
{
CASE: [{ WHEN: { internalNote: { NULL: false } }, THEN: { FIELD: 'articleNumber' } }],
ELSE: { FIELD: 'createdDate' },
SORT: 'asc'
}
]
});
```

For properties that are objects or arrays of objects, use dot-notation to reference nested fields:

```ts
/**
* Order by a nested property of an array/object field.
*
* ?orderBy=articlePrices.price asc
*/
wServices['article'].some({
orderBy: [{ FIELD: 'articlePrices.price', SORT: 'asc' }]
});
```

There are three modifier functions, `TRIM`, `LOWER` and `LENGTH`, which can be used to adjust the expessions:

```ts
/**
* First order by the length of the trimmed lastName in descending order, then by firstName in ascending order
*
* ?orderBy=length(trim(lastName)) desc, firstName asc
*/
wServices['party'].some({
orderBy: [
{
FIELD: 'lastName',
LENGTH: true,
TRIM: true,
SORT: 'desc'
},
{ FIELD: 'firstName' }
]
});
```

### Aborting a request

To abort a request an AbortController has to be instantiated and its signal has to be passed to the request. The controller can
Expand Down
153 changes: 123 additions & 30 deletions src/generator/01-base/static/queriesWithQueryLanguage.ts.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,56 @@
const flattenOrderBy = (orderBy: OrderBy<any>[] = []): string => {

const applyModifiers = (property: string, modifier: { TRIM?: boolean; LOWER?: boolean; LENGTH?: boolean }) => {
let result = property;
if (modifier.TRIM) {
result = `trim(${result})`;
}
if (modifier.LOWER) {
result = `lower(${result})`;
}
if (modifier.LENGTH) {
result = `length(${result})`;
}
return result;
};

const isConditionalOrderBy = (
orderByItem: OrderBy<any>
): orderByItem is ConditionalOrderBy<any> =>
!!orderByItem && typeof orderByItem === 'object' && 'CASE' in orderByItem;

const flattenValue = (value: number | FieldOrderBy<any>): string => {
if (typeof value === 'number') {
return value.toString();
}
return applyModifiers(value.FIELD, value);
};

const flattenConditional = (item: ConditionalOrderBy<any>): string => {
const cases = item.CASE.map((c) => {
const whenStr = flattenWhere(c.WHEN as QueryFilter<any>, []).join(' and ');
return `(${whenStr}) ? ${flattenValue(c.THEN)}`;
});

return cases.reduceRight((acc, caseStr) => `${caseStr} : ${acc}`, flattenValue(item.ELSE));
};

const flattenSingle = (orderByItem: OrderBy<any>): string => {
if (isConditionalOrderBy(orderByItem)) {
const expr = flattenConditional(orderByItem);
const sort = orderByItem.SORT === 'desc' ? 'desc' : 'asc';
return `${expr} ${sort}`;
}

const field = orderByItem as FieldOrderBy<any>;
const property = applyModifiers(field.FIELD, field);
const sort = field.SORT === 'desc' ? 'desc' : 'asc';
return `${property} ${sort}`;
};

return orderBy.map(flattenSingle).join(', ');
}

export type ComparisonOperator =
| 'EQ'
| 'NE'
Expand Down Expand Up @@ -68,20 +121,53 @@ export type QueryFilter<T> = SingleFilterExpr<T> & {
NOT?: QueryFilter<T>;
};

export type ConditionalOrderByCase<E> = {
WHEN: QueryFilter<E>;
THEN: number | FieldOrderBy<E>;
};

export type ConditionalOrderBy<E> = {
CASE: ConditionalOrderByCase<E>[];
ELSE: number | FieldOrderBy<E>;
SORT?: 'asc' | 'desc';
};

export type FieldPath<T> = {
[K in keyof T & string]: NonNullable<T[K]> extends Array<infer U>
? U extends Record<any, any>
? `${K}.${FieldPath<U>}`
: never
: NonNullable<T[K]> extends Record<any, any>
? K | `${K}.${FieldPath<NonNullable<T[K]>>}`
: K;
}[keyof T & string];

export type FieldOrderBy<E> = {
FIELD: FieldPath<E>;
SORT?: 'asc' | 'desc';
LOWER?: boolean;
TRIM?: boolean;
LENGTH?: boolean;
};

export type OrderBy<E> = FieldOrderBy<E> | ConditionalOrderBy<E>;

export type CountQuery<F> = {
where?: QueryFilter<F>;
};

export type SomeQuery<E, F, I, P> = {
type SomeQueryBase<E, F, I, P> = {
serializeNulls?: boolean;
include?: QuerySelect<I>;
properties?: P;
where?: QueryFilter<F>;
select?: QuerySelect<E>;
sort?: Sort<E>[];
pagination?: Pagination;
};

export type SomeQuery<E, F, I, P> = SomeQueryBase<E, F, I, P> &
({ sort?: Sort<E>[]; orderBy?: never } | { sort?: never; orderBy?: OrderBy<E>[] });

const comparisonOperatorList: ComparisonOperator[] = [
'EQ',
'NE',
Expand Down Expand Up @@ -209,6 +295,14 @@ const flattenWhere = (
return entries;
};

const assembleOrderBy = (orderBy: OrderBy<any>[] = []): Record<string, string> => {
if(!orderBy.length) {
return {}
}
const flattedOrderBy = flattenOrderBy(orderBy);
return flattedOrderBy.length ? { orderBy: flattedOrderBy } : {};
}

const assembleFilterParam = (
obj: QueryFilter<any> = {}
): Record<string, string> => {
Expand Down Expand Up @@ -256,32 +350,31 @@ const _some = (
endpoint: string,
query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> },
requestOptions?: RequestOptions
) =>
{
const usePost = cfg?.usePost ?? globalConfig?.usePost
const payload = {
serializeNulls: query?.serializeNulls,
additionalProperties: query?.properties?.join(','),
properties: query?.select
? flattenSelect(query.select).join(',')
: undefined,
includeReferencedEntities: query?.include
? Object.keys(query.include).join(',')
: undefined,
...assembleFilterParam(query?.where),
...flattenSort(query?.sort),
...query?.params,
...query?.pagination
}

return wrapResponse(() =>
raw(cfg, usePost ? `${endpoint}/query` : endpoint, {
method: usePost ? 'POST' : 'GET',
...(usePost ? { body: payload } : { query: payload })
}, requestOptions).then((data) => ({
entities: data.result,
references: data.referencedEntities ?? {},
properties: data.additionalProperties ?? {}
}))
)
) => {
const usePost = cfg?.usePost ?? globalConfig?.usePost;
const payload = {
serializeNulls: query?.serializeNulls,
additionalProperties: query?.properties?.join(','),
properties: query?.select
? flattenSelect(query.select).join(',')
: undefined,
includeReferencedEntities: query?.include
? Object.keys(query.include).join(',')
: undefined,
...assembleFilterParam(query?.where),
...flattenSort(query?.sort),
...assembleOrderBy(query?.orderBy),
...query?.params,
...query?.pagination
};
return wrapResponse(() =>
raw(cfg, usePost ? `${endpoint}/query` : endpoint, {
method: usePost ? 'POST' : 'GET',
...(usePost ? { body: payload } : { query: payload })
}, requestOptions).then((data) => ({
entities: data.result,
references: data.referencedEntities ?? {},
properties: data.additionalProperties ?? {}
}))
);
};
Loading