quetch 0.11.2 → 0.13.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.
@@ -32,4 +32,4 @@
32
32
 
33
33
  #### Defined in
34
34
 
35
- [lib/types/CustomFetch.ts:5](https://github.com/nevoland/quetch/blob/6055b33/lib/types/CustomFetch.ts#L5)
35
+ [lib/types/CustomFetch.ts:5](https://github.com/nevoland/quetch/blob/fbf3307/lib/types/CustomFetch.ts#L5)
@@ -16,7 +16,7 @@ export function fetchExternal(
16
16
  console.error("Could not find a global `fetch` function");
17
17
  }
18
18
  return async (request, _) => {
19
- let response;
19
+ let response: Response | undefined;
20
20
  try {
21
21
  response = await fetch(request);
22
22
  } catch (error) {
@@ -17,7 +17,7 @@ test("tests filter lists", () => {
17
17
  },
18
18
  { a: "foo", b: "bar" },
19
19
  ),
20
- ).toBeTruthy();
20
+ ).toBe(true);
21
21
  expect(
22
22
  filterItem(
23
23
  {
@@ -29,7 +29,7 @@ test("tests filter lists", () => {
29
29
  },
30
30
  { a: "foo", b: "bar" },
31
31
  ),
32
- ).toBeFalsy();
32
+ ).toBe(false);
33
33
  expect(
34
34
  filterItem(
35
35
  {
@@ -38,7 +38,7 @@ test("tests filter lists", () => {
38
38
  },
39
39
  { a: "foo", b: "bar" },
40
40
  ),
41
- ).toBeTruthy();
41
+ ).toBe(true);
42
42
  expect(
43
43
  filterItem(
44
44
  {
@@ -46,7 +46,7 @@ test("tests filter lists", () => {
46
46
  },
47
47
  { a: "foo", b: "bar" },
48
48
  ),
49
- ).toBeTruthy();
49
+ ).toBe(true);
50
50
  expect(
51
51
  filterItem(
52
52
  {
@@ -54,7 +54,7 @@ test("tests filter lists", () => {
54
54
  },
55
55
  { a: "foo", b: "bar" },
56
56
  ),
57
- ).toBeFalsy();
57
+ ).toBe(false);
58
58
  expect(
59
59
  filterItem(
60
60
  {
@@ -66,7 +66,7 @@ test("tests filter lists", () => {
66
66
  },
67
67
  { a: "foo", b: "bar" },
68
68
  ),
69
- ).toBeTruthy();
69
+ ).toBe(true);
70
70
  expect(
71
71
  filterItem(
72
72
  {
@@ -78,7 +78,7 @@ test("tests filter lists", () => {
78
78
  },
79
79
  { a: "foo", b: "bar" },
80
80
  ),
81
- ).toBeTruthy();
81
+ ).toBe(true);
82
82
  expect(
83
83
  filterItem(
84
84
  {
@@ -90,40 +90,84 @@ test("tests filter lists", () => {
90
90
  },
91
91
  { a: "foo", b: "bar" },
92
92
  ),
93
- ).toBeTruthy();
93
+ ).toBe(true);
94
94
  });
95
95
 
96
96
  test("tests filter on string values", () => {
97
97
  expect(
98
98
  filterItem({ field: "a", operator: "equal", value: "foo" }, { a: "foo" }),
99
- ).toBeTruthy();
99
+ ).toBe(true);
100
+ expect(
101
+ filterItem(
102
+ {
103
+ field: "a",
104
+ operator: "equal",
105
+ options: { sensitivity: "base" },
106
+ value: "FOO",
107
+ },
108
+ { a: "foo" },
109
+ ),
110
+ ).toBe(true);
100
111
  expect(
101
112
  filterItem({ field: "a", operator: "equal", value: "bar" }, { a: "foo" }),
102
- ).toBeFalsy();
113
+ ).toBe(false);
114
+ expect(
115
+ filterItem(
116
+ {
117
+ field: "a",
118
+ operator: "equal",
119
+ options: { sensitivity: "accent" },
120
+ value: "föo",
121
+ },
122
+ { a: "foo" },
123
+ ),
124
+ ).toBe(false);
103
125
  expect(
104
126
  filterItem(
105
127
  { field: "a", operator: "notEqual", value: "bar" },
106
128
  { a: "foo" },
107
129
  ),
108
- ).toBeTruthy();
130
+ ).toBe(true);
109
131
  expect(
110
132
  filterItem(
111
133
  { field: "a", operator: "include", value: "bar" },
112
134
  { a: "foobar" },
113
135
  ),
114
- ).toBeTruthy();
136
+ ).toBe(true);
137
+ expect(
138
+ filterItem(
139
+ {
140
+ field: "a",
141
+ operator: "include",
142
+ options: { sensitivity: "base" },
143
+ value: "BAR",
144
+ },
145
+ { a: "foobar" },
146
+ ),
147
+ ).toBe(true);
115
148
  expect(
116
149
  filterItem(
117
150
  { field: "a", operator: "intersect", value: ["foo", "bar", "foobar"] },
118
151
  { a: "foobar" },
119
152
  ),
120
- ).toBeTruthy();
153
+ ).toBe(true);
154
+ expect(
155
+ filterItem(
156
+ {
157
+ field: "a",
158
+ operator: "intersect",
159
+ options: { sensitivity: "base" },
160
+ value: ["FOO", "BAR", "FOOBAR"],
161
+ },
162
+ { a: "foobar" },
163
+ ),
164
+ ).toBe(true);
121
165
  expect(
122
166
  filterItem(
123
167
  { field: "a", operator: "intersect", value: ["foo", "bar"] },
124
168
  { a: "foobar" },
125
169
  ),
126
- ).toBeFalsy();
170
+ ).toBe(false);
127
171
  expect(
128
172
  filterItem(
129
173
  {
@@ -134,7 +178,7 @@ test("tests filter on string values", () => {
134
178
  },
135
179
  { a: "foobar" },
136
180
  ),
137
- ).toBeTruthy();
181
+ ).toBe(true);
138
182
  expect(
139
183
  filterItem(
140
184
  {
@@ -145,70 +189,70 @@ test("tests filter on string values", () => {
145
189
  },
146
190
  { a: "foobar" },
147
191
  ),
148
- ).toBeFalsy();
192
+ ).toBe(false);
149
193
  });
150
194
 
151
195
  test("tests filter on number values", () => {
152
196
  expect(
153
197
  filterItem({ field: "a", operator: "equal", value: 1 }, { a: 1 }),
154
- ).toBeTruthy();
198
+ ).toBe(true);
155
199
  expect(
156
200
  filterItem({ field: "a", operator: "notEqual", value: 2 }, { a: 1 }),
157
- ).toBeTruthy();
201
+ ).toBe(true);
158
202
  expect(
159
203
  filterItem({ field: "a", operator: "greaterThan", value: 0 }, { a: 1 }),
160
- ).toBeTruthy();
204
+ ).toBe(true);
161
205
  expect(
162
206
  filterItem(
163
207
  { field: "a", operator: "greaterThanOrEqual", value: 1 },
164
208
  { a: 1 },
165
209
  ),
166
- ).toBeTruthy();
210
+ ).toBe(true);
167
211
  expect(
168
212
  filterItem({ field: "a", operator: "lowerThan", value: 2 }, { a: 1 }),
169
- ).toBeTruthy();
213
+ ).toBe(true);
170
214
  expect(
171
215
  filterItem(
172
216
  { field: "a", operator: "lowerThanOrEqual", value: 1 },
173
217
  { a: 1 },
174
218
  ),
175
- ).toBeTruthy();
219
+ ).toBe(true);
176
220
  });
177
221
 
178
222
  test("tests filter on array values", () => {
179
223
  expect(
180
224
  filterItem({ field: "a", operator: "equal", value: [2, 1] }, { a: [1, 2] }),
181
- ).toBeTruthy();
225
+ ).toBe(true);
182
226
  expect(
183
227
  filterItem({ field: "a", operator: "equal", value: [2] }, { a: [1, 2] }),
184
- ).toBeFalsy();
228
+ ).toBe(false);
185
229
  expect(
186
230
  filterItem({ field: "a", operator: "include", value: [1] }, { a: [1, 2] }),
187
- ).toBeTruthy();
231
+ ).toBe(true);
188
232
  expect(
189
233
  filterItem(
190
234
  { field: "a", operator: "include", value: [2, 3] },
191
235
  { a: [1, 2] },
192
236
  ),
193
- ).toBeFalsy();
237
+ ).toBe(false);
194
238
  expect(
195
239
  filterItem(
196
240
  { field: "a", operator: "intersect", value: [2, 3] },
197
241
  { a: [1, 2] },
198
242
  ),
199
- ).toBeTruthy();
243
+ ).toBe(true);
200
244
  expect(
201
245
  filterItem(
202
246
  { field: "a", operator: "intersect", value: [3, 4] },
203
247
  { a: [1, 2] },
204
248
  ),
205
- ).toBeFalsy();
249
+ ).toBe(false);
206
250
  });
207
251
 
208
252
  test("tests filter with children predicates", () => {
209
- expect(
210
- filterItem({ operator: "children", value: "a" }, { id: "a/b" }),
211
- ).toBeTruthy();
253
+ expect(filterItem({ operator: "children", value: "a" }, { id: "a/b" })).toBe(
254
+ true,
255
+ );
212
256
  expect(
213
257
  filterItem(
214
258
  { operator: "children", value: ".a" },
@@ -218,7 +262,7 @@ test("tests filter with children predicates", () => {
218
262
  pathFieldSeparator: ".",
219
263
  },
220
264
  ),
221
- ).toBeTruthy();
265
+ ).toBe(true);
222
266
  const filterChildren: FilterChildren<any> = {
223
267
  operator: "children",
224
268
  value: "a",
@@ -237,11 +281,11 @@ test("tests filter with children predicates", () => {
237
281
  },
238
282
  },
239
283
  ),
240
- ).toBeTruthy();
284
+ ).toBe(true);
241
285
  expect(filterChildren[SymbolCache]).toBeDefined();
242
- expect(
243
- filterItem({ operator: "children", value: "b" }, { id: "a/b" }),
244
- ).toBeFalsy();
286
+ expect(filterItem({ operator: "children", value: "b" }, { id: "a/b" })).toBe(
287
+ false,
288
+ );
245
289
  expect(
246
290
  filterItem(
247
291
  { operator: "children", value: "ba" },
@@ -251,5 +295,5 @@ test("tests filter with children predicates", () => {
251
295
  pathFieldSeparator: ".",
252
296
  },
253
297
  ),
254
- ).toBeFalsy();
298
+ ).toBe(false);
255
299
  });
@@ -54,10 +54,44 @@ export function filterItem<T extends object>(
54
54
  }
55
55
  return filter.value.every((value) => item.includes(value));
56
56
  }
57
- return value[filter.field] === filter.value;
57
+ const item = value[filter.field];
58
+ if (item === filter.value) {
59
+ return true;
60
+ }
61
+ if (
62
+ ("options" in filter && filter.options) ||
63
+ ("locale" in filter && filter.locale)
64
+ ) {
65
+ return (
66
+ item !== undefined &&
67
+ filter.value.localeCompare(
68
+ item as string,
69
+ filter.locale,
70
+ filter.options,
71
+ ) === 0
72
+ );
73
+ }
74
+ return false;
75
+ }
76
+ case "notEqual": {
77
+ const item = value[filter.field];
78
+ if (item === filter.value) {
79
+ return false;
80
+ }
81
+ if (
82
+ ("options" in filter && filter.options) ||
83
+ ("locale" in filter && filter.locale)
84
+ ) {
85
+ return (
86
+ filter.value.localeCompare(
87
+ value[filter.field] as string,
88
+ filter.locale,
89
+ filter.options,
90
+ ) !== 0
91
+ );
92
+ }
93
+ return true;
58
94
  }
59
- case "notEqual":
60
- return value[filter.field] !== filter.value;
61
95
  case "children": {
62
96
  if (filter[SymbolCache] === undefined) {
63
97
  switch (true) {
@@ -83,58 +117,179 @@ export function filterItem<T extends object>(
83
117
  case "custom": {
84
118
  return filter.value(value);
85
119
  }
86
- case "startWith":
87
- return (
88
- (value[filter.field] as string | undefined)?.startsWith(filter.value) ??
89
- false
90
- );
91
- case "endWith":
92
- return (
93
- (value[filter.field] as string | undefined)?.endsWith(filter.value) ??
94
- false
95
- );
120
+ case "startWith": {
121
+ const item = value[filter.field] as string | undefined;
122
+ if (item === undefined || item.length < filter.value.length) {
123
+ return false;
124
+ }
125
+ if (filter.options !== undefined || filter.locale !== undefined) {
126
+ return (
127
+ filter.value.localeCompare(
128
+ item.slice(0, filter.value.length),
129
+ filter.locale,
130
+ filter.options,
131
+ ) !== 0
132
+ );
133
+ }
134
+ return item.startsWith(filter.value) ?? false;
135
+ }
136
+ case "endWith": {
137
+ const item = value[filter.field] as string | undefined;
138
+ if (item === undefined || item.length < filter.value.length) {
139
+ return false;
140
+ }
141
+ if (filter.options !== undefined || filter.locale !== undefined) {
142
+ return (
143
+ filter.value.localeCompare(
144
+ item.slice(-filter.value.length),
145
+ filter.locale,
146
+ filter.options,
147
+ ) !== 0
148
+ );
149
+ }
150
+ return item.endsWith(filter.value) ?? false;
151
+ }
96
152
  case "include": {
97
- const item = value[filter.field];
98
- if (item === undefined) {
153
+ const item = value[filter.field] as string | any[];
154
+ if (item == null) {
99
155
  return false;
100
156
  }
101
157
  if (isArray(filter.value)) {
102
- if (!isArray(item)) {
158
+ // FIXME: Get unique values
159
+ if (!isArray(item) || item.length < filter.value.length) {
103
160
  return false;
104
161
  }
105
162
  return filter.value.every((value) => item.includes(value));
106
163
  }
107
- return (item as string).includes?.(filter.value) ?? false;
164
+ if (isArray(item) || item.length < filter.value.length) {
165
+ return false;
166
+ }
167
+ if (
168
+ ("options" in filter && filter.options) ||
169
+ ("locale" in filter && filter.locale)
170
+ ) {
171
+ const { length } = filter.value;
172
+ const end = item.length - length + 1;
173
+ for (let i = 0; i < end; i++) {
174
+ if (
175
+ filter.value.localeCompare(
176
+ item.slice(i, i + length),
177
+ filter.locale,
178
+ filter.options,
179
+ ) === 0
180
+ ) {
181
+ return true;
182
+ }
183
+ }
184
+ return false;
185
+ }
186
+ return item.includes?.(filter.value) ?? false;
187
+ }
188
+ case "greaterThan": {
189
+ const item = value[filter.field];
190
+ if (
191
+ ("options" in filter && filter.options) ||
192
+ ("locale" in filter && filter.locale)
193
+ ) {
194
+ return (
195
+ filter.value.localeCompare(
196
+ item as string,
197
+ filter.locale,
198
+ filter.options,
199
+ ) < 0
200
+ );
201
+ }
202
+ return item > filter.value;
203
+ }
204
+ case "greaterThanOrEqual": {
205
+ const item = value[filter.field];
206
+ if (
207
+ ("options" in filter && filter.options) ||
208
+ ("locale" in filter && filter.locale)
209
+ ) {
210
+ return (
211
+ filter.value.localeCompare(
212
+ item as string,
213
+ filter.locale,
214
+ filter.options,
215
+ ) <= 0
216
+ );
217
+ }
218
+ return item >= filter.value;
219
+ }
220
+ case "lowerThan": {
221
+ const item = value[filter.field];
222
+ if (
223
+ ("options" in filter && filter.options) ||
224
+ ("locale" in filter && filter.locale)
225
+ ) {
226
+ return (
227
+ filter.value.localeCompare(
228
+ item as string,
229
+ filter.locale,
230
+ filter.options,
231
+ ) > 0
232
+ );
233
+ }
234
+ return item < filter.value;
235
+ }
236
+ case "lowerThanOrEqual": {
237
+ const item = value[filter.field];
238
+ if (
239
+ ("options" in filter && filter.options) ||
240
+ ("locale" in filter && filter.locale)
241
+ ) {
242
+ return (
243
+ filter.value.localeCompare(
244
+ item as string,
245
+ filter.locale,
246
+ filter.options,
247
+ ) >= 0
248
+ );
249
+ }
250
+ return item <= filter.value;
108
251
  }
109
- case "greaterThan":
110
- return value[filter.field] > filter.value;
111
- case "greaterThanOrEqual":
112
- return value[filter.field] >= filter.value;
113
- case "lowerThan":
114
- return value[filter.field] < filter.value;
115
- case "lowerThanOrEqual":
116
- return value[filter.field] <= filter.value;
117
252
  case "match": {
118
253
  const item = value[filter.field] as string | undefined;
119
254
  if (item === undefined) {
120
255
  return false;
121
256
  }
122
257
  if (filter[SymbolCache] === undefined) {
123
- const { options = {} } = filter;
124
258
  filter[SymbolCache] = new RegExp(
125
259
  filter.value,
126
- `${options.ignoreCase ? "i" : ""}${options.dotAll ? "s" : ""}`,
260
+ `${filter.options?.ignoreCase ? "i" : ""}${
261
+ filter.options?.dotAll ? "s" : ""
262
+ }`,
127
263
  );
128
264
  }
129
265
  return filter[SymbolCache].test(item);
130
266
  }
131
267
  case "intersect": {
132
268
  const item = value[filter.field];
133
- if (item === undefined) {
269
+ if (item == null) {
134
270
  return false;
135
271
  }
136
272
  if (isArray(item)) {
137
- return filter.value.some((value) => item?.includes(value));
273
+ return (
274
+ isArray(filter.value) &&
275
+ filter.value.some((value) => item.includes(value))
276
+ );
277
+ }
278
+ if (
279
+ ("options" in filter && filter.options) ||
280
+ ("locale" in filter && filter.locale)
281
+ ) {
282
+ return (
283
+ item !== undefined &&
284
+ filter.value.some(
285
+ (value) =>
286
+ value.localeCompare(
287
+ item as string,
288
+ filter.locale,
289
+ filter.options,
290
+ ) === 0,
291
+ )
292
+ );
138
293
  }
139
294
  return filter.value.includes(item as string);
140
295
  }
@@ -1,4 +1,5 @@
1
1
  import type { FilterKeys } from "./FilterKeys";
2
+ import type { Locale } from "./Locale";
2
3
 
3
4
  /**
4
5
  * Checks if a given string field matches a given string value according to a given operator.
@@ -16,4 +17,6 @@ export type FilterString<T extends object> = {
16
17
  | "lowerThanOrEqual";
17
18
  field: FilterKeys<T, string>;
18
19
  value: string;
20
+ options?: Intl.CollatorOptions;
21
+ locale?: Locale;
19
22
  };
@@ -1,4 +1,5 @@
1
1
  import type { FilterKeys } from "./FilterKeys";
2
+ import type { Locale } from "./Locale";
2
3
 
3
4
  /**
4
5
  * Checks if a given string field has any of the provided values.
@@ -7,4 +8,6 @@ export type FilterStringIntersect<T extends object> = {
7
8
  operator: "intersect";
8
9
  field: FilterKeys<T, string>;
9
10
  value: string[];
11
+ options?: Intl.CollatorOptions;
12
+ locale?: Locale;
10
13
  };
@@ -17,11 +17,11 @@ export type FilterStringMatch<T extends object> = {
17
17
  */
18
18
  options?: {
19
19
  /**
20
- * When matching, casing differences are ignored.
20
+ * Ignore casing differences if `true`.
21
21
  */
22
22
  ignoreCase?: boolean;
23
23
  /**
24
- * Allows . to match newlines.
24
+ * Allow `.` to match newlines.
25
25
  */
26
26
  dotAll?: boolean;
27
27
  };
@@ -0,0 +1 @@
1
+ export type Locale = string | string[];
@@ -27,4 +27,8 @@ export type QuerySettings<T extends object> = {
27
27
  * Abort signal to abort the query.
28
28
  */
29
29
  signal?: AbortSignal;
30
+ /**
31
+ * Abort controller to abort the query.
32
+ */
33
+ abortController?: AbortController;
30
34
  };
package/lib/types.ts CHANGED
@@ -30,6 +30,7 @@ export type { Immutable } from "./types/Immutable";
30
30
  export type { InjectCustomFields } from "./types/InjectCustomFields";
31
31
  export type { Item } from "./types/Item";
32
32
  export type { Key } from "./types/Key";
33
+ export type { Locale } from "./types/Locale";
33
34
  export type { Mutable } from "./types/Mutable";
34
35
  export type { NextHandler } from "./types/NextHandler";
35
36
  export type { Order } from "./types/Order";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quetch",
3
- "version": "0.11.2",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "main": "./dist/main.js",
6
6
  "exports": {