quetch 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/errors/QueryError.d.ts +5 -0
- package/dist/errors/QueryError.js +23 -0
- package/dist/errors/QueryError.js.map +1 -0
- package/dist/errors/RequestError.d.ts +11 -0
- package/dist/errors/RequestError.js +33 -0
- package/dist/errors/RequestError.js.map +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +3 -0
- package/dist/errors.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -0
- package/dist/middlewares/aggregate.d.ts +19 -0
- package/dist/middlewares/aggregate.js +86 -0
- package/dist/middlewares/aggregate.js.map +1 -0
- package/dist/middlewares/branch.d.ts +18 -0
- package/dist/middlewares/branch.js +21 -0
- package/dist/middlewares/branch.js.map +1 -0
- package/dist/middlewares/cache.d.ts +37 -0
- package/dist/middlewares/cache.js +40 -0
- package/dist/middlewares/cache.js.map +1 -0
- package/dist/middlewares/combine.d.ts +318 -0
- package/dist/middlewares/combine.js +20 -0
- package/dist/middlewares/combine.js.map +1 -0
- package/dist/middlewares/concurrent.d.ts +2 -0
- package/dist/middlewares/concurrent.js +20 -0
- package/dist/middlewares/concurrent.js.map +1 -0
- package/dist/middlewares/fetch.d.ts +2 -0
- package/dist/middlewares/fetch.js +20 -0
- package/dist/middlewares/fetch.js.map +1 -0
- package/dist/middlewares/fetchExternal.d.ts +8 -0
- package/dist/middlewares/fetchExternal.js +29 -0
- package/dist/middlewares/fetchExternal.js.map +1 -0
- package/dist/middlewares/fetchLocal.d.ts +9 -0
- package/dist/middlewares/fetchLocal.js +10 -0
- package/dist/middlewares/fetchLocal.js.map +1 -0
- package/dist/middlewares/identity.d.ts +4 -0
- package/dist/middlewares/identity.js +8 -0
- package/dist/middlewares/identity.js.map +1 -0
- package/dist/middlewares/log.d.ts +8 -0
- package/dist/middlewares/log.js +30 -0
- package/dist/middlewares/log.js.map +1 -0
- package/dist/middlewares/logQuery.d.ts +2 -0
- package/dist/middlewares/logQuery.js +25 -0
- package/dist/middlewares/logQuery.js.map +1 -0
- package/dist/middlewares/retry.d.ts +14 -0
- package/dist/middlewares/retry.js +38 -0
- package/dist/middlewares/retry.js.map +1 -0
- package/dist/middlewares.d.ts +9 -0
- package/dist/middlewares.js +11 -0
- package/dist/middlewares.js.map +1 -0
- package/dist/tools/add.d.ts +8 -0
- package/dist/tools/add.js +11 -0
- package/dist/tools/add.js.map +1 -0
- package/dist/tools/add.test.d.ts +1 -0
- package/dist/tools/add.test.js +6 -0
- package/dist/tools/add.test.js.map +1 -0
- package/dist/tools/defineCheckQuery.d.ts +17 -0
- package/dist/tools/defineCheckQuery.js +7 -0
- package/dist/tools/defineCheckQuery.js.map +1 -0
- package/dist/tools/defineCustomFetch.d.ts +19 -0
- package/dist/tools/defineCustomFetch.js +8 -0
- package/dist/tools/defineCustomFetch.js.map +1 -0
- package/dist/tools/filterFromContext.d.ts +2 -0
- package/dist/tools/filterFromContext.js +12 -0
- package/dist/tools/filterFromContext.js.map +1 -0
- package/dist/tools/filterItem.d.ts +9 -0
- package/dist/tools/filterItem.js +101 -0
- package/dist/tools/filterItem.js.map +1 -0
- package/dist/tools/filterItem.test.d.ts +1 -0
- package/dist/tools/filterItem.test.js +86 -0
- package/dist/tools/filterItem.test.js.map +1 -0
- package/dist/tools/impasse.d.ts +2 -0
- package/dist/tools/impasse.js +2 -0
- package/dist/tools/impasse.js.map +1 -0
- package/dist/tools/normalizeOrder.d.ts +5 -0
- package/dist/tools/normalizeOrder.js +10 -0
- package/dist/tools/normalizeOrder.js.map +1 -0
- package/dist/tools/queryItemList.d.ts +4 -0
- package/dist/tools/queryItemList.js +77 -0
- package/dist/tools/queryItemList.js.map +1 -0
- package/dist/tools/queryItemList.test.d.ts +1 -0
- package/dist/tools/queryItemList.test.js +141 -0
- package/dist/tools/queryItemList.test.js.map +1 -0
- package/dist/tools/sortItemList.d.ts +9 -0
- package/dist/tools/sortItemList.js +28 -0
- package/dist/tools/sortItemList.js.map +1 -0
- package/dist/tools/sortItemList.test.d.ts +1 -0
- package/dist/tools/sortItemList.test.js +47 -0
- package/dist/tools/sortItemList.test.js.map +1 -0
- package/dist/tools.d.ts +8 -0
- package/dist/tools.js +10 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +396 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/doc/README.md +1472 -0
- package/lib/errors/RequestError.ts +16 -0
- package/lib/errors.ts +2 -0
- package/lib/main.ts +4 -0
- package/lib/middlewares/aggregate.ts +113 -0
- package/lib/middlewares/branch.ts +27 -0
- package/lib/middlewares/cache.ts +89 -0
- package/lib/middlewares/combine.ts +959 -0
- package/lib/middlewares/fetchExternal.ts +38 -0
- package/lib/middlewares/fetchLocal.ts +14 -0
- package/lib/middlewares/identity.ts +20 -0
- package/lib/middlewares/log.ts +31 -0
- package/lib/middlewares/retry.ts +45 -0
- package/lib/middlewares.ts +10 -0
- package/lib/tools/defineCheckQuery.ts +24 -0
- package/lib/tools/defineCustomFetch.ts +70 -0
- package/lib/tools/filterFromContext.ts +16 -0
- package/lib/tools/filterItem.test.ts +203 -0
- package/lib/tools/filterItem.ts +113 -0
- package/lib/tools/impasse.ts +3 -0
- package/lib/tools/normalizeOrder.ts +13 -0
- package/lib/tools/queryItemList.test.ts +169 -0
- package/lib/tools/queryItemList.ts +108 -0
- package/lib/tools/sortItemList.test.ts +63 -0
- package/lib/tools/sortItemList.ts +33 -0
- package/lib/tools.ts +9 -0
- package/lib/types.ts +554 -0
- package/package.json +72 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getGlobal } from "@davidbonnet/get-global";
|
|
2
|
+
|
|
3
|
+
import { RequestError } from "../errors/RequestError";
|
|
4
|
+
import type { Handler } from "../types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calls the provided `fetch` function, which defaults to the DOM `fetch` function, with the incoming `query`.
|
|
8
|
+
*
|
|
9
|
+
* @param fetch A standard `fetch` function.
|
|
10
|
+
* @returns Handler that returns a promise that resolves to the response.
|
|
11
|
+
*/
|
|
12
|
+
export function fetchExternal(
|
|
13
|
+
fetch = getGlobal().fetch,
|
|
14
|
+
): Handler<Request, Response, never, never> {
|
|
15
|
+
if (process.env.NODE_ENV !== "production" && !fetch) {
|
|
16
|
+
console.error("Could not find a global `fetch` function");
|
|
17
|
+
}
|
|
18
|
+
return async (request, _) => {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(request);
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
throw new RequestError(
|
|
23
|
+
response.statusText,
|
|
24
|
+
response.status,
|
|
25
|
+
undefined,
|
|
26
|
+
request,
|
|
27
|
+
response,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return response;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
throw new RequestError(error.message, 500, undefined, request);
|
|
34
|
+
}
|
|
35
|
+
throw new RequestError(String(error), 500, undefined, request);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { queryItemList } from "../tools/queryItemList";
|
|
2
|
+
import type { CustomFieldMap, Handler, Query } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Performs the fetch query on local data.
|
|
6
|
+
*
|
|
7
|
+
* @returns Promise that resolves to the response.
|
|
8
|
+
*/
|
|
9
|
+
export function fetchLocal<
|
|
10
|
+
T extends object,
|
|
11
|
+
C extends CustomFieldMap<T>,
|
|
12
|
+
>(): Handler<Query<T, C> & { type: T[] }, any, never, never> {
|
|
13
|
+
return async (query, _) => queryItemList<T, C>(query);
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NextHandler } from "../types";
|
|
2
|
+
|
|
3
|
+
export async function identity<I, O, In, On>(
|
|
4
|
+
input: I,
|
|
5
|
+
next: NextHandler<In, On>,
|
|
6
|
+
): Promise<O> {
|
|
7
|
+
return (await next(input as unknown as In)) as unknown as O;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const identity3 = async <I, O, In, On>(
|
|
11
|
+
input: I,
|
|
12
|
+
next: NextHandler<In, On>,
|
|
13
|
+
): Promise<O> => (await next(input as unknown as In)) as unknown as O;
|
|
14
|
+
|
|
15
|
+
export async function identity2<I, O, In extends I, On extends O>(
|
|
16
|
+
input: In,
|
|
17
|
+
next: NextHandler<In, On>,
|
|
18
|
+
): Promise<On> {
|
|
19
|
+
return await next(input);
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Handler } from "../types";
|
|
2
|
+
|
|
3
|
+
import { identity } from "./identity";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logs the outgoing query and the incoming result or the error.
|
|
7
|
+
*
|
|
8
|
+
* @param title
|
|
9
|
+
* @returns Query handler
|
|
10
|
+
*/
|
|
11
|
+
export function log<I, O, In, On>(title = "Query"): Handler<I, O, In, On> {
|
|
12
|
+
if (process.env.NODE_ENV === "production") {
|
|
13
|
+
return identity;
|
|
14
|
+
}
|
|
15
|
+
return async (input, next) => {
|
|
16
|
+
/* eslint-disable no-console */
|
|
17
|
+
console.group(title);
|
|
18
|
+
console.info("query", input);
|
|
19
|
+
try {
|
|
20
|
+
const result = await next(input as unknown as In);
|
|
21
|
+
console.log("result", result);
|
|
22
|
+
console.groupEnd();
|
|
23
|
+
return result as unknown as O;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.log("error", error);
|
|
26
|
+
console.groupEnd();
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
/* eslint-enable no-console */
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getGlobal } from "@davidbonnet/get-global";
|
|
2
|
+
import { sleep, untilOnline } from "futurise";
|
|
3
|
+
|
|
4
|
+
import { RequestError } from "../errors/RequestError";
|
|
5
|
+
import type { Handler } from "../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Retries a failed query call up to `amount` times, with a given `delay` in milliseconds at ±`delayDelta` milliseconds.
|
|
9
|
+
* Note that an `amount` set to `Infinity` results in indefinitely trying to resolve a query call.
|
|
10
|
+
* Only instances of `RequestError` that do not result in a `500` status error will result in new tries. Other errors will propagate immediately.
|
|
11
|
+
*
|
|
12
|
+
* @param options
|
|
13
|
+
* @returns Handler
|
|
14
|
+
*/
|
|
15
|
+
export function retry({
|
|
16
|
+
amount = 5,
|
|
17
|
+
delay = 1000,
|
|
18
|
+
delayDelta = 500,
|
|
19
|
+
} = {}): Handler<Request, Response, Request, Response> {
|
|
20
|
+
const { navigator } = getGlobal();
|
|
21
|
+
return (input, next) => {
|
|
22
|
+
let errorsLeft = amount;
|
|
23
|
+
const fetch = async (): Promise<Response> => {
|
|
24
|
+
try {
|
|
25
|
+
return await next(input);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (navigator !== undefined && !navigator.onLine) {
|
|
28
|
+
errorsLeft = amount;
|
|
29
|
+
await untilOnline();
|
|
30
|
+
} else if (!(error instanceof RequestError) || error.status < 500) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
if (--errorsLeft > 0) {
|
|
34
|
+
await sleep(
|
|
35
|
+
delay + delayDelta + ((Math.random() * delayDelta * 2) | 0),
|
|
36
|
+
input.signal,
|
|
37
|
+
);
|
|
38
|
+
return fetch();
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return fetch();
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// File automatically generated by `vite-plugin-module-list`
|
|
2
|
+
export { aggregate } from "./middlewares/aggregate";
|
|
3
|
+
export { branch } from "./middlewares/branch";
|
|
4
|
+
export { cache } from "./middlewares/cache";
|
|
5
|
+
export { combine } from "./middlewares/combine";
|
|
6
|
+
export { fetchExternal } from "./middlewares/fetchExternal";
|
|
7
|
+
export { fetchLocal } from "./middlewares/fetchLocal";
|
|
8
|
+
export { identity } from "./middlewares/identity";
|
|
9
|
+
export { log } from "./middlewares/log";
|
|
10
|
+
export { retry } from "./middlewares/retry";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CustomFieldMap, Query } from "../types";
|
|
2
|
+
|
|
3
|
+
export function defineCheckQuery<Entities extends Record<string, object>>() {
|
|
4
|
+
function checkQuery<
|
|
5
|
+
E extends object,
|
|
6
|
+
C extends CustomFieldMap<E>,
|
|
7
|
+
const Q extends Query<E, C>,
|
|
8
|
+
>(query: Q & { type: E[]; customFields?: C }): typeof query;
|
|
9
|
+
function checkQuery<
|
|
10
|
+
T extends keyof Entities,
|
|
11
|
+
E extends Entities[T],
|
|
12
|
+
C extends CustomFieldMap<E>,
|
|
13
|
+
const Q extends Query<E, C>,
|
|
14
|
+
>(query: Q & { type: T; customFields?: C }): typeof query;
|
|
15
|
+
function checkQuery<
|
|
16
|
+
T extends keyof Entities,
|
|
17
|
+
E extends Entities[T] | object,
|
|
18
|
+
C extends CustomFieldMap<E>,
|
|
19
|
+
const Q extends Query<E, C>,
|
|
20
|
+
>(query: Q & { type: T | E[]; customFields?: C }): typeof query {
|
|
21
|
+
return query;
|
|
22
|
+
}
|
|
23
|
+
return checkQuery;
|
|
24
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnyQuery,
|
|
3
|
+
CustomFieldMap,
|
|
4
|
+
EntityItem,
|
|
5
|
+
Handler,
|
|
6
|
+
Query,
|
|
7
|
+
} from "../types";
|
|
8
|
+
|
|
9
|
+
import { impasse } from "./impasse";
|
|
10
|
+
|
|
11
|
+
export function defineCustomFetch<M extends Record<string, object>>(
|
|
12
|
+
handler: Handler<AnyQuery, any, never, never>,
|
|
13
|
+
) {
|
|
14
|
+
function customFetch<
|
|
15
|
+
E extends object,
|
|
16
|
+
C extends CustomFieldMap<E>,
|
|
17
|
+
const Q extends Query<E, C>,
|
|
18
|
+
>(
|
|
19
|
+
input: Q & { type: E[]; customFields?: C },
|
|
20
|
+
): Promise<
|
|
21
|
+
Q["method"] extends "get"
|
|
22
|
+
? Q extends { multiple: true }
|
|
23
|
+
? EntityItem<E, C, Q>[]
|
|
24
|
+
: EntityItem<E, C, Q>
|
|
25
|
+
: Q["method"] extends "aggregate"
|
|
26
|
+
? number
|
|
27
|
+
: Q extends { multiple: true }
|
|
28
|
+
? EntityItem<E, C, Q>[]
|
|
29
|
+
: EntityItem<E, C, Q>
|
|
30
|
+
>;
|
|
31
|
+
function customFetch<
|
|
32
|
+
T extends keyof M,
|
|
33
|
+
E extends M[T],
|
|
34
|
+
C extends CustomFieldMap<E>,
|
|
35
|
+
const Q extends Query<E, C>,
|
|
36
|
+
>(
|
|
37
|
+
input: Q & { type: T; customFields?: C },
|
|
38
|
+
): Promise<
|
|
39
|
+
Q["method"] extends "get"
|
|
40
|
+
? Q extends { multiple: true }
|
|
41
|
+
? EntityItem<E, C, Q>[]
|
|
42
|
+
: EntityItem<E, C, Q>
|
|
43
|
+
: Q["method"] extends "aggregate"
|
|
44
|
+
? number
|
|
45
|
+
: Q extends { multiple: true }
|
|
46
|
+
? EntityItem<E, C, Q>[]
|
|
47
|
+
: EntityItem<E, C, Q>
|
|
48
|
+
>;
|
|
49
|
+
async function customFetch<
|
|
50
|
+
T extends keyof M,
|
|
51
|
+
E extends M[T] | object,
|
|
52
|
+
C extends CustomFieldMap<E>,
|
|
53
|
+
const Q extends Query<E, C>,
|
|
54
|
+
>(
|
|
55
|
+
input: Q & { type: T | E[]; customFields?: C },
|
|
56
|
+
): Promise<
|
|
57
|
+
Q["method"] extends "get"
|
|
58
|
+
? Q extends { multiple: true }
|
|
59
|
+
? EntityItem<E, C, Q>[]
|
|
60
|
+
: EntityItem<E, C, Q>
|
|
61
|
+
: Q["method"] extends "aggregate"
|
|
62
|
+
? number
|
|
63
|
+
: Q extends { multiple: true }
|
|
64
|
+
? EntityItem<E, C, Q>[]
|
|
65
|
+
: EntityItem<E, C, Q>
|
|
66
|
+
> {
|
|
67
|
+
return await handler(input, impasse);
|
|
68
|
+
}
|
|
69
|
+
return customFetch;
|
|
70
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context, Filter } from "../types";
|
|
2
|
+
|
|
3
|
+
const { entries } = Object;
|
|
4
|
+
|
|
5
|
+
export function filterFromContext<T extends object>(
|
|
6
|
+
context: Context<T>,
|
|
7
|
+
): Filter<T> {
|
|
8
|
+
return {
|
|
9
|
+
operator: "all",
|
|
10
|
+
value: entries(context).map(([field, value]) => ({
|
|
11
|
+
operator: "equal",
|
|
12
|
+
field,
|
|
13
|
+
value,
|
|
14
|
+
})) as Filter<T>[],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { filterItem } from "./filterItem";
|
|
4
|
+
|
|
5
|
+
test("tests filter lists", () => {
|
|
6
|
+
expect(
|
|
7
|
+
filterItem(
|
|
8
|
+
{
|
|
9
|
+
operator: "all",
|
|
10
|
+
value: [
|
|
11
|
+
{ operator: "equal", field: "a", value: "foo" },
|
|
12
|
+
{ operator: "equal", field: "b", value: "bar" },
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
{ a: "foo", b: "bar" },
|
|
16
|
+
),
|
|
17
|
+
).toBeTruthy();
|
|
18
|
+
expect(
|
|
19
|
+
filterItem(
|
|
20
|
+
{
|
|
21
|
+
operator: "all",
|
|
22
|
+
value: [
|
|
23
|
+
{ operator: "equal", field: "a", value: "foo" },
|
|
24
|
+
{ operator: "equal", field: "b", value: "baz" },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{ a: "foo", b: "bar" },
|
|
28
|
+
),
|
|
29
|
+
).toBeFalsy();
|
|
30
|
+
expect(
|
|
31
|
+
filterItem(
|
|
32
|
+
{
|
|
33
|
+
operator: "all",
|
|
34
|
+
value: [],
|
|
35
|
+
},
|
|
36
|
+
{ a: "foo", b: "bar" },
|
|
37
|
+
),
|
|
38
|
+
).toBeTruthy();
|
|
39
|
+
expect(
|
|
40
|
+
filterItem(
|
|
41
|
+
{
|
|
42
|
+
operator: "any",
|
|
43
|
+
},
|
|
44
|
+
{ a: "foo", b: "bar" },
|
|
45
|
+
),
|
|
46
|
+
).toBeTruthy();
|
|
47
|
+
expect(
|
|
48
|
+
filterItem(
|
|
49
|
+
{
|
|
50
|
+
operator: "none",
|
|
51
|
+
},
|
|
52
|
+
{ a: "foo", b: "bar" },
|
|
53
|
+
),
|
|
54
|
+
).toBeFalsy();
|
|
55
|
+
expect(
|
|
56
|
+
filterItem(
|
|
57
|
+
{
|
|
58
|
+
operator: "any",
|
|
59
|
+
value: [
|
|
60
|
+
{ operator: "equal", field: "a", value: "foo" },
|
|
61
|
+
{ operator: "equal", field: "b", value: "baz" },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{ a: "foo", b: "bar" },
|
|
65
|
+
),
|
|
66
|
+
).toBeTruthy();
|
|
67
|
+
expect(
|
|
68
|
+
filterItem(
|
|
69
|
+
{
|
|
70
|
+
operator: "none",
|
|
71
|
+
value: [
|
|
72
|
+
{ operator: "equal", field: "a", value: "a" },
|
|
73
|
+
{ operator: "equal", field: "b", value: "b" },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{ a: "foo", b: "bar" },
|
|
77
|
+
),
|
|
78
|
+
).toBeTruthy();
|
|
79
|
+
expect(
|
|
80
|
+
filterItem(
|
|
81
|
+
{
|
|
82
|
+
operator: "none",
|
|
83
|
+
value: [
|
|
84
|
+
{ operator: "equal", field: "a", value: "a" },
|
|
85
|
+
{ operator: "equal", field: "b", value: "b" },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{ a: "foo", b: "bar" },
|
|
89
|
+
),
|
|
90
|
+
).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("tests filter on string values", () => {
|
|
94
|
+
expect(
|
|
95
|
+
filterItem({ operator: "equal", field: "a", value: "foo" }, { a: "foo" }),
|
|
96
|
+
).toBeTruthy();
|
|
97
|
+
expect(
|
|
98
|
+
filterItem({ operator: "equal", field: "a", value: "bar" }, { a: "foo" }),
|
|
99
|
+
).toBeFalsy();
|
|
100
|
+
expect(
|
|
101
|
+
filterItem(
|
|
102
|
+
{ operator: "notEqual", field: "a", value: "bar" },
|
|
103
|
+
{ a: "foo" },
|
|
104
|
+
),
|
|
105
|
+
).toBeTruthy();
|
|
106
|
+
expect(
|
|
107
|
+
filterItem(
|
|
108
|
+
{ operator: "include", field: "a", value: "bar" },
|
|
109
|
+
{ a: "foobar" },
|
|
110
|
+
),
|
|
111
|
+
).toBeTruthy();
|
|
112
|
+
expect(
|
|
113
|
+
filterItem(
|
|
114
|
+
{ operator: "intersect", field: "a", value: ["foo", "bar", "foobar"] },
|
|
115
|
+
{ a: "foobar" },
|
|
116
|
+
),
|
|
117
|
+
).toBeTruthy();
|
|
118
|
+
expect(
|
|
119
|
+
filterItem(
|
|
120
|
+
{ operator: "intersect", field: "a", value: ["foo", "bar"] },
|
|
121
|
+
{ a: "foobar" },
|
|
122
|
+
),
|
|
123
|
+
).toBeFalsy();
|
|
124
|
+
expect(
|
|
125
|
+
filterItem(
|
|
126
|
+
{
|
|
127
|
+
operator: "match",
|
|
128
|
+
field: "a",
|
|
129
|
+
value: "^FOO",
|
|
130
|
+
options: { ignoreCase: true },
|
|
131
|
+
},
|
|
132
|
+
{ a: "foobar" },
|
|
133
|
+
),
|
|
134
|
+
).toBeTruthy();
|
|
135
|
+
expect(
|
|
136
|
+
filterItem(
|
|
137
|
+
{
|
|
138
|
+
operator: "match",
|
|
139
|
+
field: "a",
|
|
140
|
+
value: "FOO$",
|
|
141
|
+
options: { ignoreCase: true },
|
|
142
|
+
},
|
|
143
|
+
{ a: "foobar" },
|
|
144
|
+
),
|
|
145
|
+
).toBeFalsy();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("tests filter on number values", () => {
|
|
149
|
+
expect(
|
|
150
|
+
filterItem({ operator: "equal", field: "a", value: 1 }, { a: 1 }),
|
|
151
|
+
).toBeTruthy();
|
|
152
|
+
expect(
|
|
153
|
+
filterItem({ operator: "notEqual", field: "a", value: 2 }, { a: 1 }),
|
|
154
|
+
).toBeTruthy();
|
|
155
|
+
expect(
|
|
156
|
+
filterItem({ operator: "greaterThan", field: "a", value: 0 }, { a: 1 }),
|
|
157
|
+
).toBeTruthy();
|
|
158
|
+
expect(
|
|
159
|
+
filterItem(
|
|
160
|
+
{ operator: "greaterThanOrEqual", field: "a", value: 1 },
|
|
161
|
+
{ a: 1 },
|
|
162
|
+
),
|
|
163
|
+
).toBeTruthy();
|
|
164
|
+
expect(
|
|
165
|
+
filterItem({ operator: "lowerThan", field: "a", value: 2 }, { a: 1 }),
|
|
166
|
+
).toBeTruthy();
|
|
167
|
+
expect(
|
|
168
|
+
filterItem(
|
|
169
|
+
{ operator: "lowerThanOrEqual", field: "a", value: 1 },
|
|
170
|
+
{ a: 1 },
|
|
171
|
+
),
|
|
172
|
+
).toBeTruthy();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("tests filter on array values", () => {
|
|
176
|
+
expect(
|
|
177
|
+
filterItem({ operator: "equal", field: "a", value: [2, 1] }, { a: [1, 2] }),
|
|
178
|
+
).toBeTruthy();
|
|
179
|
+
expect(
|
|
180
|
+
filterItem({ operator: "equal", field: "a", value: [2] }, { a: [1, 2] }),
|
|
181
|
+
).toBeFalsy();
|
|
182
|
+
expect(
|
|
183
|
+
filterItem({ operator: "include", field: "a", value: [1] }, { a: [1, 2] }),
|
|
184
|
+
).toBeTruthy();
|
|
185
|
+
expect(
|
|
186
|
+
filterItem(
|
|
187
|
+
{ operator: "include", field: "a", value: [2, 3] },
|
|
188
|
+
{ a: [1, 2] },
|
|
189
|
+
),
|
|
190
|
+
).toBeFalsy();
|
|
191
|
+
expect(
|
|
192
|
+
filterItem(
|
|
193
|
+
{ operator: "intersect", field: "a", value: [2, 3] },
|
|
194
|
+
{ a: [1, 2] },
|
|
195
|
+
),
|
|
196
|
+
).toBeTruthy();
|
|
197
|
+
expect(
|
|
198
|
+
filterItem(
|
|
199
|
+
{ operator: "intersect", field: "a", value: [3, 4] },
|
|
200
|
+
{ a: [1, 2] },
|
|
201
|
+
),
|
|
202
|
+
).toBeFalsy();
|
|
203
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Any, Filter } from "../types";
|
|
2
|
+
|
|
3
|
+
const { isArray } = Array;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checks wether the provided `value` matches the `filter` or not.
|
|
7
|
+
*
|
|
8
|
+
* @param filter The filter to apply.
|
|
9
|
+
* @param value The value to check.
|
|
10
|
+
* @returns `true` if the `value` matches the `filter` and `false` otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export function filterItem<T extends object>(
|
|
13
|
+
filter: Filter<T> | undefined,
|
|
14
|
+
value: T | undefined,
|
|
15
|
+
): boolean {
|
|
16
|
+
if (value === undefined) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (filter === undefined) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
switch (filter.operator) {
|
|
23
|
+
case "all":
|
|
24
|
+
return filter.value.every((filter) => filterItem(filter, value));
|
|
25
|
+
case "any": {
|
|
26
|
+
if (filter.value === undefined || filter.value.length === 0) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return filter.value.some((filter) => filterItem(filter, value));
|
|
30
|
+
}
|
|
31
|
+
case "none": {
|
|
32
|
+
if (filter.value === undefined || filter.value.length === 0) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return filter.value.every((filter) => !filterItem(filter, value));
|
|
36
|
+
}
|
|
37
|
+
case "exist":
|
|
38
|
+
// TODO: Handle path
|
|
39
|
+
return filter.field in value;
|
|
40
|
+
case "equal": {
|
|
41
|
+
if (isArray(filter.value)) {
|
|
42
|
+
const item = value[filter.field] as Any[] | undefined;
|
|
43
|
+
if (!isArray(item)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (filter.value.length !== item.length) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return filter.value.every((value) => item.includes(value));
|
|
50
|
+
}
|
|
51
|
+
return value[filter.field] === filter.value;
|
|
52
|
+
}
|
|
53
|
+
case "notEqual":
|
|
54
|
+
return value[filter.field] !== filter.value;
|
|
55
|
+
case "startWith":
|
|
56
|
+
return (
|
|
57
|
+
(value[filter.field] as string | undefined)?.startsWith(filter.value) ??
|
|
58
|
+
false
|
|
59
|
+
);
|
|
60
|
+
case "endWith":
|
|
61
|
+
return (
|
|
62
|
+
(value[filter.field] as string | undefined)?.endsWith(filter.value) ??
|
|
63
|
+
false
|
|
64
|
+
);
|
|
65
|
+
case "include": {
|
|
66
|
+
const item = value[filter.field];
|
|
67
|
+
if (item === undefined) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (isArray(filter.value)) {
|
|
71
|
+
if (!isArray(item)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return filter.value.every((value) => item.includes(value));
|
|
75
|
+
}
|
|
76
|
+
return (item as string).includes?.(filter.value) ?? false;
|
|
77
|
+
}
|
|
78
|
+
case "greaterThan":
|
|
79
|
+
return value[filter.field] > filter.value;
|
|
80
|
+
case "greaterThanOrEqual":
|
|
81
|
+
return value[filter.field] >= filter.value;
|
|
82
|
+
case "lowerThan":
|
|
83
|
+
return value[filter.field] < filter.value;
|
|
84
|
+
case "lowerThanOrEqual":
|
|
85
|
+
return value[filter.field] <= filter.value;
|
|
86
|
+
case "match": {
|
|
87
|
+
const item = value[filter.field] as string | undefined;
|
|
88
|
+
if (item === undefined) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (filter.regularExpression === undefined) {
|
|
92
|
+
const { options = {} } = filter;
|
|
93
|
+
filter.regularExpression = new RegExp(
|
|
94
|
+
filter.value,
|
|
95
|
+
`${options.ignoreCase ? "i" : ""}${options.dotAll ? "s" : ""}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return filter.regularExpression.test(item);
|
|
99
|
+
}
|
|
100
|
+
case "intersect": {
|
|
101
|
+
const item = value[filter.field];
|
|
102
|
+
if (item === undefined) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (isArray(item)) {
|
|
106
|
+
return filter.value.some((value) => item?.includes(value));
|
|
107
|
+
}
|
|
108
|
+
return filter.value.includes(item as string);
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`Unknown filter operator '${(filter as any).operator}'`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Order } from "../types";
|
|
2
|
+
|
|
3
|
+
export function normalizeOrder<T extends object>(
|
|
4
|
+
order: Order<T>,
|
|
5
|
+
): { field: keyof T; descending?: boolean } {
|
|
6
|
+
if (typeof order === "object") {
|
|
7
|
+
return order;
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
field: order,
|
|
11
|
+
descending: false,
|
|
12
|
+
};
|
|
13
|
+
}
|