@clipboard-health/json-api 0.17.3 → 0.18.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 +25 -19
- package/package.json +2 -1
- package/src/index.d.ts +2 -2
- package/src/index.js +2 -2
- package/src/index.js.map +1 -1
- package/src/lib/query/{toServerJsonApiQuery.d.ts → parseQuery.d.ts} +11 -9
- package/src/lib/query/parseQuery.js +55 -0
- package/src/lib/query/parseQuery.js.map +1 -0
- package/src/lib/query/{toClientSearchParams.d.ts → stringifyQuery.d.ts} +17 -11
- package/src/lib/query/stringifyQuery.js +51 -0
- package/src/lib/query/stringifyQuery.js.map +1 -0
- package/src/lib/types.d.ts +56 -24
- package/src/lib/query/toClientSearchParams.js +0 -76
- package/src/lib/query/toClientSearchParams.js.map +0 -1
- package/src/lib/query/toServerJsonApiQuery.js +0 -93
- package/src/lib/query/toServerJsonApiQuery.js.map +0 -1
package/README.md
CHANGED
|
@@ -19,14 +19,14 @@ npm install @clipboard-health/json-api
|
|
|
19
19
|
|
|
20
20
|
### Query helpers
|
|
21
21
|
|
|
22
|
-
From the client, call `
|
|
22
|
+
From the client, call `stringifyQuery` to convert from `JsonApiQuery` to `URLSearchParams`:
|
|
23
23
|
|
|
24
|
-
<embedex source="packages/json-api/examples/
|
|
24
|
+
<embedex source="packages/json-api/examples/stringifyQuery.ts">
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
27
|
import { deepEqual } from "node:assert/strict";
|
|
28
28
|
|
|
29
|
-
import {
|
|
29
|
+
import { stringifyQuery } from "@clipboard-health/json-api";
|
|
30
30
|
|
|
31
31
|
import { type ClientJsonApiQuery } from "../src/lib/types";
|
|
32
32
|
|
|
@@ -34,19 +34,22 @@ const [date1, date2] = ["2024-01-01", "2024-01-02"];
|
|
|
34
34
|
const query: ClientJsonApiQuery = {
|
|
35
35
|
fields: { user: ["age", "dateOfBirth"] },
|
|
36
36
|
filter: {
|
|
37
|
-
age:
|
|
38
|
-
dateOfBirth: {
|
|
39
|
-
|
|
37
|
+
age: 2,
|
|
38
|
+
dateOfBirth: {
|
|
39
|
+
gt: date1,
|
|
40
|
+
lt: date2,
|
|
41
|
+
},
|
|
42
|
+
isActive: true,
|
|
40
43
|
},
|
|
41
|
-
include:
|
|
44
|
+
include: "article",
|
|
42
45
|
page: {
|
|
43
|
-
size:
|
|
46
|
+
size: 10,
|
|
44
47
|
},
|
|
45
|
-
sort:
|
|
48
|
+
sort: "-age",
|
|
46
49
|
};
|
|
47
50
|
|
|
48
51
|
deepEqual(
|
|
49
|
-
|
|
52
|
+
stringifyQuery(query),
|
|
50
53
|
new URLSearchParams(
|
|
51
54
|
`fields[user]=age,dateOfBirth&filter[age]=2&filter[dateOfBirth][gt]=${date1}&filter[dateOfBirth][lt]=${date2}&filter[isActive]=true&include=article&page[size]=10&sort=-age`,
|
|
52
55
|
).toString(),
|
|
@@ -54,14 +57,17 @@ deepEqual(
|
|
|
54
57
|
```
|
|
55
58
|
|
|
56
59
|
</embedex>
|
|
57
|
-
From the server, call `toServerJsonApiQuery` to convert from `URLSearchParams` to `ServerJsonApiQuery`:
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
From the server, call `parseQuery` to convert from `URLSearchParams` to `ServerJsonApiQuery`:
|
|
62
|
+
|
|
63
|
+
<embedex source="packages/json-api/examples/parseQuery.ts">
|
|
60
64
|
|
|
61
65
|
```ts
|
|
62
66
|
import { deepEqual } from "node:assert/strict";
|
|
63
67
|
|
|
64
|
-
import {
|
|
68
|
+
import { parseQuery } from "@clipboard-health/json-api";
|
|
69
|
+
|
|
70
|
+
import { type ServerJsonApiQuery } from "../src/lib/types";
|
|
65
71
|
|
|
66
72
|
const [date1, date2] = ["2024-01-01", "2024-01-02"];
|
|
67
73
|
// The URLSearchParams constructor also supports URL-encoded strings
|
|
@@ -69,20 +75,20 @@ const searchParams = new URLSearchParams(
|
|
|
69
75
|
`fields[user]=age,dateOfBirth&filter[age]=2&filter[dateOfBirth][gt]=${date1}&filter[dateOfBirth][lt]=${date2}&filter[isActive]=true&include=article&page[size]=10&sort=-age`,
|
|
70
76
|
);
|
|
71
77
|
|
|
72
|
-
const query: ServerJsonApiQuery =
|
|
78
|
+
const query: ServerJsonApiQuery = parseQuery(searchParams.toString());
|
|
73
79
|
|
|
74
80
|
deepEqual(query, {
|
|
75
81
|
fields: { user: ["age", "dateOfBirth"] },
|
|
76
82
|
filter: {
|
|
77
|
-
age:
|
|
78
|
-
dateOfBirth: { gt:
|
|
79
|
-
isActive:
|
|
83
|
+
age: "2",
|
|
84
|
+
dateOfBirth: { gt: date1, lt: date2 },
|
|
85
|
+
isActive: "true",
|
|
80
86
|
},
|
|
81
|
-
include:
|
|
87
|
+
include: "article",
|
|
82
88
|
page: {
|
|
83
89
|
size: "10",
|
|
84
90
|
},
|
|
85
|
-
sort:
|
|
91
|
+
sort: "-age",
|
|
86
92
|
});
|
|
87
93
|
```
|
|
88
94
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/json-api",
|
|
3
3
|
"description": "TypeScript-friendly utilities for adhering to the JSON:API specification.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"bugs": "https://github.com/ClipboardHealth/core-utils/issues",
|
|
6
6
|
"dependencies": {
|
|
7
|
+
"qs": "6.14.0",
|
|
7
8
|
"tslib": "2.8.0"
|
|
8
9
|
},
|
|
9
10
|
"keywords": [
|
package/src/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from "./lib/query/
|
|
2
|
-
export * from "./lib/query/
|
|
1
|
+
export * from "./lib/query/parseQuery";
|
|
2
|
+
export * from "./lib/query/stringifyQuery";
|
|
3
3
|
export * from "./lib/types";
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
-
tslib_1.__exportStar(require("./lib/query/
|
|
5
|
-
tslib_1.__exportStar(require("./lib/query/
|
|
4
|
+
tslib_1.__exportStar(require("./lib/query/parseQuery"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./lib/query/stringifyQuery"), exports);
|
|
6
6
|
tslib_1.__exportStar(require("./lib/types"), exports);
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/json-api/src/index.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/json-api/src/index.ts"],"names":[],"mappings":";;;AAAA,iEAAuC;AACvC,qEAA2C;AAC3C,sDAA4B"}
|
|
@@ -3,12 +3,14 @@ import { type ServerJsonApiQuery } from "../types";
|
|
|
3
3
|
* Call this function from servers to convert from {@link URLSearchParams} to {@link ServerJsonApiQuery}.
|
|
4
4
|
*
|
|
5
5
|
* @example
|
|
6
|
-
* <embedex source="packages/json-api/examples/
|
|
6
|
+
* <embedex source="packages/json-api/examples/parseQuery.ts">
|
|
7
7
|
*
|
|
8
8
|
* ```ts
|
|
9
9
|
* import { deepEqual } from "node:assert/strict";
|
|
10
10
|
*
|
|
11
|
-
* import {
|
|
11
|
+
* import { parseQuery } from "@clipboard-health/json-api";
|
|
12
|
+
*
|
|
13
|
+
* import { type ServerJsonApiQuery } from "../src/lib/types";
|
|
12
14
|
*
|
|
13
15
|
* const [date1, date2] = ["2024-01-01", "2024-01-02"];
|
|
14
16
|
* // The URLSearchParams constructor also supports URL-encoded strings
|
|
@@ -16,23 +18,23 @@ import { type ServerJsonApiQuery } from "../types";
|
|
|
16
18
|
* `fields[user]=age,dateOfBirth&filter[age]=2&filter[dateOfBirth][gt]=${date1}&filter[dateOfBirth][lt]=${date2}&filter[isActive]=true&include=article&page[size]=10&sort=-age`,
|
|
17
19
|
* );
|
|
18
20
|
*
|
|
19
|
-
* const query: ServerJsonApiQuery =
|
|
21
|
+
* const query: ServerJsonApiQuery = parseQuery(searchParams.toString());
|
|
20
22
|
*
|
|
21
23
|
* deepEqual(query, {
|
|
22
24
|
* fields: { user: ["age", "dateOfBirth"] },
|
|
23
25
|
* filter: {
|
|
24
|
-
* age:
|
|
25
|
-
* dateOfBirth: { gt:
|
|
26
|
-
* isActive:
|
|
26
|
+
* age: "2",
|
|
27
|
+
* dateOfBirth: { gt: date1, lt: date2 },
|
|
28
|
+
* isActive: "true",
|
|
27
29
|
* },
|
|
28
|
-
* include:
|
|
30
|
+
* include: "article",
|
|
29
31
|
* page: {
|
|
30
32
|
* size: "10",
|
|
31
33
|
* },
|
|
32
|
-
* sort:
|
|
34
|
+
* sort: "-age",
|
|
33
35
|
* });
|
|
34
36
|
* ```
|
|
35
37
|
*
|
|
36
38
|
* </embedex>
|
|
37
39
|
*/
|
|
38
|
-
export declare function
|
|
40
|
+
export declare function parseQuery(query: string): ServerJsonApiQuery;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseQuery = parseQuery;
|
|
4
|
+
const qs_1 = require("qs");
|
|
5
|
+
/**
|
|
6
|
+
* Call this function from servers to convert from {@link URLSearchParams} to {@link ServerJsonApiQuery}.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <embedex source="packages/json-api/examples/parseQuery.ts">
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { deepEqual } from "node:assert/strict";
|
|
13
|
+
*
|
|
14
|
+
* import { parseQuery } from "@clipboard-health/json-api";
|
|
15
|
+
*
|
|
16
|
+
* import { type ServerJsonApiQuery } from "../src/lib/types";
|
|
17
|
+
*
|
|
18
|
+
* const [date1, date2] = ["2024-01-01", "2024-01-02"];
|
|
19
|
+
* // The URLSearchParams constructor also supports URL-encoded strings
|
|
20
|
+
* const searchParams = new URLSearchParams(
|
|
21
|
+
* `fields[user]=age,dateOfBirth&filter[age]=2&filter[dateOfBirth][gt]=${date1}&filter[dateOfBirth][lt]=${date2}&filter[isActive]=true&include=article&page[size]=10&sort=-age`,
|
|
22
|
+
* );
|
|
23
|
+
*
|
|
24
|
+
* const query: ServerJsonApiQuery = parseQuery(searchParams.toString());
|
|
25
|
+
*
|
|
26
|
+
* deepEqual(query, {
|
|
27
|
+
* fields: { user: ["age", "dateOfBirth"] },
|
|
28
|
+
* filter: {
|
|
29
|
+
* age: "2",
|
|
30
|
+
* dateOfBirth: { gt: date1, lt: date2 },
|
|
31
|
+
* isActive: "true",
|
|
32
|
+
* },
|
|
33
|
+
* include: "article",
|
|
34
|
+
* page: {
|
|
35
|
+
* size: "10",
|
|
36
|
+
* },
|
|
37
|
+
* sort: "-age",
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* </embedex>
|
|
42
|
+
*/
|
|
43
|
+
function parseQuery(query) {
|
|
44
|
+
return (0, qs_1.parse)(query, {
|
|
45
|
+
decoder: (item, defaultDecoder, charset, type) => {
|
|
46
|
+
const decoded = decodeURIComponent(item);
|
|
47
|
+
if (type === "value") {
|
|
48
|
+
return decoded.includes(",") ? decoded.split(",") : decoded;
|
|
49
|
+
}
|
|
50
|
+
return defaultDecoder(decoded, charset, type);
|
|
51
|
+
},
|
|
52
|
+
ignoreQueryPrefix: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=parseQuery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseQuery.js","sourceRoot":"","sources":["../../../../../../packages/json-api/src/lib/query/parseQuery.ts"],"names":[],"mappings":";;AA0CA,gCAYC;AAtDD,2BAA2B;AAI3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,OAAO,IAAA,UAAK,EAAC,KAAK,EAAE;QAClB,OAAO,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;YAC/C,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAC9D,CAAC;YAED,OAAO,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAChD,CAAC;QACD,iBAAiB,EAAE,IAAI;KACxB,CAAuB,CAAC;AAC3B,CAAC"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { type ClientJsonApiQuery } from "../types";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Converts from an ergonomic query format to URLSearchParams, providing a more user-friendly API
|
|
4
|
+
* than {@link toClientSearchParams} while maintaining JSON:API compliance.
|
|
4
5
|
*
|
|
5
6
|
* @example
|
|
6
|
-
* <embedex source="packages/json-api/examples/
|
|
7
|
+
* <embedex source="packages/json-api/examples/stringifyQuery.ts">
|
|
7
8
|
*
|
|
8
9
|
* ```ts
|
|
9
10
|
* import { deepEqual } from "node:assert/strict";
|
|
10
11
|
*
|
|
11
|
-
* import {
|
|
12
|
+
* import { stringifyQuery } from "@clipboard-health/json-api";
|
|
12
13
|
*
|
|
13
14
|
* import { type ClientJsonApiQuery } from "../src/lib/types";
|
|
14
15
|
*
|
|
@@ -16,19 +17,22 @@ import { type ClientJsonApiQuery } from "../types";
|
|
|
16
17
|
* const query: ClientJsonApiQuery = {
|
|
17
18
|
* fields: { user: ["age", "dateOfBirth"] },
|
|
18
19
|
* filter: {
|
|
19
|
-
* age:
|
|
20
|
-
* dateOfBirth: {
|
|
21
|
-
*
|
|
20
|
+
* age: 2,
|
|
21
|
+
* dateOfBirth: {
|
|
22
|
+
* gt: date1,
|
|
23
|
+
* lt: date2,
|
|
24
|
+
* },
|
|
25
|
+
* isActive: true,
|
|
22
26
|
* },
|
|
23
|
-
* include:
|
|
27
|
+
* include: "article",
|
|
24
28
|
* page: {
|
|
25
|
-
* size:
|
|
29
|
+
* size: 10,
|
|
26
30
|
* },
|
|
27
|
-
* sort:
|
|
31
|
+
* sort: "-age",
|
|
28
32
|
* };
|
|
29
33
|
*
|
|
30
34
|
* deepEqual(
|
|
31
|
-
*
|
|
35
|
+
* stringifyQuery(query),
|
|
32
36
|
* new URLSearchParams(
|
|
33
37
|
* `fields[user]=age,dateOfBirth&filter[age]=2&filter[dateOfBirth][gt]=${date1}&filter[dateOfBirth][lt]=${date2}&filter[isActive]=true&include=article&page[size]=10&sort=-age`,
|
|
34
38
|
* ).toString(),
|
|
@@ -37,4 +41,6 @@ import { type ClientJsonApiQuery } from "../types";
|
|
|
37
41
|
*
|
|
38
42
|
* </embedex>
|
|
39
43
|
*/
|
|
40
|
-
export declare function
|
|
44
|
+
export declare function stringifyQuery(query: ClientJsonApiQuery, options?: {
|
|
45
|
+
encode?: boolean;
|
|
46
|
+
}): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stringifyQuery = stringifyQuery;
|
|
4
|
+
const qs_1 = require("qs");
|
|
5
|
+
/**
|
|
6
|
+
* Converts from an ergonomic query format to URLSearchParams, providing a more user-friendly API
|
|
7
|
+
* than {@link toClientSearchParams} while maintaining JSON:API compliance.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <embedex source="packages/json-api/examples/stringifyQuery.ts">
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { deepEqual } from "node:assert/strict";
|
|
14
|
+
*
|
|
15
|
+
* import { stringifyQuery } from "@clipboard-health/json-api";
|
|
16
|
+
*
|
|
17
|
+
* import { type ClientJsonApiQuery } from "../src/lib/types";
|
|
18
|
+
*
|
|
19
|
+
* const [date1, date2] = ["2024-01-01", "2024-01-02"];
|
|
20
|
+
* const query: ClientJsonApiQuery = {
|
|
21
|
+
* fields: { user: ["age", "dateOfBirth"] },
|
|
22
|
+
* filter: {
|
|
23
|
+
* age: 2,
|
|
24
|
+
* dateOfBirth: {
|
|
25
|
+
* gt: date1,
|
|
26
|
+
* lt: date2,
|
|
27
|
+
* },
|
|
28
|
+
* isActive: true,
|
|
29
|
+
* },
|
|
30
|
+
* include: "article",
|
|
31
|
+
* page: {
|
|
32
|
+
* size: 10,
|
|
33
|
+
* },
|
|
34
|
+
* sort: "-age",
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* deepEqual(
|
|
38
|
+
* stringifyQuery(query),
|
|
39
|
+
* new URLSearchParams(
|
|
40
|
+
* `fields[user]=age,dateOfBirth&filter[age]=2&filter[dateOfBirth][gt]=${date1}&filter[dateOfBirth][lt]=${date2}&filter[isActive]=true&include=article&page[size]=10&sort=-age`,
|
|
41
|
+
* ).toString(),
|
|
42
|
+
* );
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* </embedex>
|
|
46
|
+
*/
|
|
47
|
+
function stringifyQuery(query, options) {
|
|
48
|
+
const { encode = true } = options ?? {};
|
|
49
|
+
return (0, qs_1.stringify)(query, { arrayFormat: "comma", encode });
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=stringifyQuery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stringifyQuery.js","sourceRoot":"","sources":["../../../../../../packages/json-api/src/lib/query/stringifyQuery.ts"],"names":[],"mappings":";;AA8CA,wCAIC;AAlDD,2BAA+B;AAI/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,SAAgB,cAAc,CAAC,KAAyB,EAAE,OAA8B;IACtF,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;IAExC,OAAO,IAAA,cAAS,EAAC,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAC5D,CAAC"}
|
package/src/lib/types.d.ts
CHANGED
|
@@ -1,52 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filter
|
|
3
|
-
* - eq: Equal to; the default when no filter
|
|
2
|
+
* Filter operators to build complex filter queries.
|
|
3
|
+
* - eq: Equal to; the default when no filter operator is provided, do not explicitly include it.
|
|
4
4
|
* - ne: Not equal to.
|
|
5
5
|
* - gt: Greater than.
|
|
6
6
|
* - gte: Greater than or equal to.
|
|
7
7
|
* - lt: Less than.
|
|
8
8
|
* - lte: Less than or equal to.
|
|
9
9
|
*/
|
|
10
|
-
type
|
|
11
|
-
type PageKey = "cursor" | "limit" | "number" | "offset" | "size";
|
|
10
|
+
export type FilterOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte";
|
|
11
|
+
export type PageKey = "cursor" | "limit" | "number" | "offset" | "size";
|
|
12
12
|
export { type URLSearchParams } from "node:url";
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
export type FilterValue = Date | number | string | boolean;
|
|
14
|
+
export type FilterObject = {
|
|
15
|
+
[K in FilterOperator]?: FilterValue | FilterValue[];
|
|
16
|
+
};
|
|
17
|
+
export interface NestedFilter {
|
|
18
|
+
[key: string]: FilterValue | FilterValue[] | FilterObject | NestedFilter | undefined;
|
|
16
19
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
/**
|
|
21
|
+
* A JSON:API URL query.
|
|
22
|
+
*/
|
|
23
|
+
export interface ClientJsonApiQuery {
|
|
24
|
+
/**
|
|
25
|
+
* Fields to include in the response.
|
|
26
|
+
*
|
|
27
|
+
* @see {@link https://jsonapi.org/format/#fetching-sparse-fieldsets JSON:API sparse fieldsets}
|
|
28
|
+
*/
|
|
29
|
+
fields?: Record<string, string | string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Filters to apply to the query.
|
|
32
|
+
*
|
|
33
|
+
* @see {@link https://jsonapi.org/recommendations/#filtering JSON:API filtering}
|
|
34
|
+
* @see {@link https://discuss.jsonapi.org/t/share-propose-a-filtering-strategy/257 JSON:API filtering strategy}
|
|
35
|
+
*/
|
|
36
|
+
filter?: Record<string, FilterValue | FilterValue[] | FilterObject | NestedFilter | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* Relationships to include in the response.
|
|
39
|
+
*
|
|
40
|
+
* @see {@link https://jsonapi.org/format/#fetching-includes JSON:API fetching includes}
|
|
41
|
+
*/
|
|
42
|
+
include?: string | string[];
|
|
43
|
+
/**
|
|
44
|
+
* Pagination data.
|
|
45
|
+
*
|
|
46
|
+
* @see {@link https://jsonapi.org/format/#fetching-pagination JSON:API pagination}
|
|
47
|
+
* @see {@link https://jsonapi.org/examples/#pagination JSON:API pagination examples}
|
|
48
|
+
*/
|
|
49
|
+
page?: {
|
|
50
|
+
[K in PageKey]?: number | string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Sorting data. Include the "-" prefix for descending order.
|
|
54
|
+
*
|
|
55
|
+
* @see {@link https://jsonapi.org/format/#fetching-sorting JSON:API sorting}
|
|
56
|
+
*/
|
|
57
|
+
sort?: string | string[];
|
|
24
58
|
}
|
|
25
59
|
/**
|
|
26
|
-
* A JSON:API URL query
|
|
60
|
+
* A JSON:API URL query.
|
|
27
61
|
*/
|
|
28
|
-
export interface
|
|
62
|
+
export interface ServerJsonApiQuery {
|
|
29
63
|
/**
|
|
30
64
|
* Fields to include in the response.
|
|
31
65
|
*
|
|
32
66
|
* @see {@link https://jsonapi.org/format/#fetching-sparse-fieldsets JSON:API sparse fieldsets}
|
|
33
67
|
*/
|
|
34
|
-
fields?: Record<string, string[]>;
|
|
68
|
+
fields?: Record<string, string | string[]>;
|
|
35
69
|
/**
|
|
36
70
|
* Filters to apply to the query.
|
|
37
71
|
*
|
|
38
72
|
* @see {@link https://jsonapi.org/recommendations/#filtering JSON:API filtering}
|
|
39
73
|
* @see {@link https://discuss.jsonapi.org/t/share-propose-a-filtering-strategy/257 JSON:API filtering strategy}
|
|
40
74
|
*/
|
|
41
|
-
filter?: Record<string, {
|
|
42
|
-
[K in
|
|
75
|
+
filter?: Record<string, string | string[] | {
|
|
76
|
+
[K in FilterOperator]?: string | string[];
|
|
43
77
|
}>;
|
|
44
78
|
/**
|
|
45
79
|
* Relationships to include in the response.
|
|
46
80
|
*
|
|
47
81
|
* @see {@link https://jsonapi.org/format/#fetching-includes JSON:API fetching includes}
|
|
48
82
|
*/
|
|
49
|
-
include?: string[];
|
|
83
|
+
include?: string | string[];
|
|
50
84
|
/**
|
|
51
85
|
* Pagination data.
|
|
52
86
|
*
|
|
@@ -54,14 +88,12 @@ export interface JsonApiQuery<T extends JsonApiQueryTypes> {
|
|
|
54
88
|
* @see {@link https://jsonapi.org/examples/#pagination JSON:API pagination examples}
|
|
55
89
|
*/
|
|
56
90
|
page?: {
|
|
57
|
-
[K in PageKey]?:
|
|
91
|
+
[K in PageKey]?: string;
|
|
58
92
|
};
|
|
59
93
|
/**
|
|
60
94
|
* Sorting data. Include the "-" prefix for descending order.
|
|
61
95
|
*
|
|
62
96
|
* @see {@link https://jsonapi.org/format/#fetching-sorting JSON:API sorting}
|
|
63
97
|
*/
|
|
64
|
-
sort?: string[];
|
|
98
|
+
sort?: string | string[];
|
|
65
99
|
}
|
|
66
|
-
export type ClientJsonApiQuery = JsonApiQuery<ClientTypes>;
|
|
67
|
-
export type ServerJsonApiQuery = JsonApiQuery<ServerTypes>;
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toClientSearchParams = toClientSearchParams;
|
|
4
|
-
function filterValueString(value) {
|
|
5
|
-
return value instanceof Date ? value.toISOString() : String(value);
|
|
6
|
-
}
|
|
7
|
-
function join(values) {
|
|
8
|
-
return values.join(",");
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Call this function from clients to convert from {@link ClientJsonApiQuery} to {@link URLSearchParams}.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* <embedex source="packages/json-api/examples/toClientSearchParams.ts">
|
|
15
|
-
*
|
|
16
|
-
* ```ts
|
|
17
|
-
* import { deepEqual } from "node:assert/strict";
|
|
18
|
-
*
|
|
19
|
-
* import { toClientSearchParams } from "@clipboard-health/json-api";
|
|
20
|
-
*
|
|
21
|
-
* import { type ClientJsonApiQuery } from "../src/lib/types";
|
|
22
|
-
*
|
|
23
|
-
* const [date1, date2] = ["2024-01-01", "2024-01-02"];
|
|
24
|
-
* const query: ClientJsonApiQuery = {
|
|
25
|
-
* fields: { user: ["age", "dateOfBirth"] },
|
|
26
|
-
* filter: {
|
|
27
|
-
* age: { eq: ["2"] },
|
|
28
|
-
* dateOfBirth: { gt: [date1], lt: [date2] },
|
|
29
|
-
* isActive: { eq: ["true"] },
|
|
30
|
-
* },
|
|
31
|
-
* include: ["article"],
|
|
32
|
-
* page: {
|
|
33
|
-
* size: "10",
|
|
34
|
-
* },
|
|
35
|
-
* sort: ["-age"],
|
|
36
|
-
* };
|
|
37
|
-
*
|
|
38
|
-
* deepEqual(
|
|
39
|
-
* toClientSearchParams(query).toString(),
|
|
40
|
-
* new URLSearchParams(
|
|
41
|
-
* `fields[user]=age,dateOfBirth&filter[age]=2&filter[dateOfBirth][gt]=${date1}&filter[dateOfBirth][lt]=${date2}&filter[isActive]=true&include=article&page[size]=10&sort=-age`,
|
|
42
|
-
* ).toString(),
|
|
43
|
-
* );
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* </embedex>
|
|
47
|
-
*/
|
|
48
|
-
function toClientSearchParams(query) {
|
|
49
|
-
const searchParams = new URLSearchParams();
|
|
50
|
-
if (query.fields) {
|
|
51
|
-
Object.entries(query.fields).forEach(([type, fields]) => {
|
|
52
|
-
searchParams.append(`fields[${type}]`, join(fields));
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
if (query.filter) {
|
|
56
|
-
Object.entries(query.filter).forEach(([field, values]) => {
|
|
57
|
-
const filterField = `filter[${field}]`;
|
|
58
|
-
Object.entries(values).forEach(([fieldType, value]) => {
|
|
59
|
-
searchParams.append(fieldType === "eq" ? filterField : `${filterField}[${fieldType}]`, join(value.map((value) => filterValueString(value))));
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
if (query.include) {
|
|
64
|
-
searchParams.append("include", join(query.include));
|
|
65
|
-
}
|
|
66
|
-
if (query.page) {
|
|
67
|
-
Object.entries(query.page).forEach(([key, value]) => {
|
|
68
|
-
searchParams.append(`page[${key}]`, String(value));
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
if (query.sort) {
|
|
72
|
-
searchParams.append("sort", join(query.sort));
|
|
73
|
-
}
|
|
74
|
-
return searchParams;
|
|
75
|
-
}
|
|
76
|
-
//# sourceMappingURL=toClientSearchParams.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"toClientSearchParams.js","sourceRoot":"","sources":["../../../../../../packages/json-api/src/lib/query/toClientSearchParams.ts"],"names":[],"mappings":";;AAgDA,oDAoCC;AAlFD,SAAS,iBAAiB,CAAC,KAAyC;IAClE,OAAO,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,IAAI,CAAC,MAAgB;IAC5B,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,SAAgB,oBAAoB,CAAC,KAAyB;IAC5D,MAAM,YAAY,GAAG,IAAI,eAAe,EAAE,CAAC;IAE3C,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE;YACtD,YAAY,CAAC,MAAM,CAAC,UAAU,IAAI,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE;YACvD,MAAM,WAAW,GAAG,UAAU,KAAK,GAAG,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE;gBACpD,YAAY,CAAC,MAAM,CACjB,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,SAAS,GAAG,EACjE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CACrD,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,YAAY,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;YAClD,YAAY,CAAC,MAAM,CAAC,QAAQ,GAAG,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toServerJsonApiQuery = toServerJsonApiQuery;
|
|
4
|
-
const REGEX = {
|
|
5
|
-
fields: /^fields\[(.*?)]$/i,
|
|
6
|
-
filter: /^filter\[([^\]]*?)]$/i,
|
|
7
|
-
filterType: /^filter\[(.*?)]\[(.*?)]$/i,
|
|
8
|
-
include: /^include$/i,
|
|
9
|
-
page: /^page\[(.*?)]$/i,
|
|
10
|
-
sort: /^sort$/i,
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Call this function from servers to convert from {@link URLSearchParams} to {@link ServerJsonApiQuery}.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* <embedex source="packages/json-api/examples/toServerJsonApiQuery.ts">
|
|
17
|
-
*
|
|
18
|
-
* ```ts
|
|
19
|
-
* import { deepEqual } from "node:assert/strict";
|
|
20
|
-
*
|
|
21
|
-
* import { type ServerJsonApiQuery, toServerJsonApiQuery } from "@clipboard-health/json-api";
|
|
22
|
-
*
|
|
23
|
-
* const [date1, date2] = ["2024-01-01", "2024-01-02"];
|
|
24
|
-
* // The URLSearchParams constructor also supports URL-encoded strings
|
|
25
|
-
* const searchParams = new URLSearchParams(
|
|
26
|
-
* `fields[user]=age,dateOfBirth&filter[age]=2&filter[dateOfBirth][gt]=${date1}&filter[dateOfBirth][lt]=${date2}&filter[isActive]=true&include=article&page[size]=10&sort=-age`,
|
|
27
|
-
* );
|
|
28
|
-
*
|
|
29
|
-
* const query: ServerJsonApiQuery = toServerJsonApiQuery(searchParams);
|
|
30
|
-
*
|
|
31
|
-
* deepEqual(query, {
|
|
32
|
-
* fields: { user: ["age", "dateOfBirth"] },
|
|
33
|
-
* filter: {
|
|
34
|
-
* age: { eq: ["2"] },
|
|
35
|
-
* dateOfBirth: { gt: [date1], lt: [date2] },
|
|
36
|
-
* isActive: { eq: ["true"] },
|
|
37
|
-
* },
|
|
38
|
-
* include: ["article"],
|
|
39
|
-
* page: {
|
|
40
|
-
* size: "10",
|
|
41
|
-
* },
|
|
42
|
-
* sort: ["-age"],
|
|
43
|
-
* });
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* </embedex>
|
|
47
|
-
*/
|
|
48
|
-
function toServerJsonApiQuery(searchParams) {
|
|
49
|
-
return [...searchParams].reduce((accumulator, [key, value]) => {
|
|
50
|
-
const match = Object.entries(REGEX).find(([, regex]) => regex.test(key));
|
|
51
|
-
if (!match) {
|
|
52
|
-
return accumulator;
|
|
53
|
-
}
|
|
54
|
-
const [type, regex] = match;
|
|
55
|
-
const groups = regex.exec(key)?.slice(1);
|
|
56
|
-
if (type === "fields" && groups?.[0]) {
|
|
57
|
-
return {
|
|
58
|
-
...accumulator,
|
|
59
|
-
fields: {
|
|
60
|
-
...accumulator.fields,
|
|
61
|
-
[groups[0]]: value.split(","),
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
if ((type === "filter" || type === "filterType") && groups?.length) {
|
|
66
|
-
const [field, fieldType] = groups;
|
|
67
|
-
if (field) {
|
|
68
|
-
return {
|
|
69
|
-
...accumulator,
|
|
70
|
-
filter: {
|
|
71
|
-
...accumulator.filter,
|
|
72
|
-
[field]: {
|
|
73
|
-
...accumulator.filter?.[field],
|
|
74
|
-
[fieldType ?? "eq"]: value.split(","),
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (type === "include" || type === "sort") {
|
|
81
|
-
return { ...accumulator, [type]: value.split(",") };
|
|
82
|
-
}
|
|
83
|
-
if (type === "page" && groups?.[0]) {
|
|
84
|
-
return {
|
|
85
|
-
...accumulator,
|
|
86
|
-
page: { ...accumulator.page, [groups[0]]: value },
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
/* istanbul ignore next */
|
|
90
|
-
return accumulator;
|
|
91
|
-
}, {});
|
|
92
|
-
}
|
|
93
|
-
//# sourceMappingURL=toServerJsonApiQuery.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"toServerJsonApiQuery.js","sourceRoot":"","sources":["../../../../../../packages/json-api/src/lib/query/toServerJsonApiQuery.ts"],"names":[],"mappings":";;AA+CA,oDAiDC;AA9FD,MAAM,KAAK,GAAG;IACZ,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,uBAAuB;IAC/B,UAAU,EAAE,2BAA2B;IACvC,OAAO,EAAE,YAAY;IACrB,IAAI,EAAE,iBAAiB;IACvB,IAAI,EAAE,SAAS;CACP,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,SAAgB,oBAAoB,CAAC,YAA6B;IAChE,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC,MAAM,CAAqB,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QAChF,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,KAAqC,CAAC;QAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,QAAQ,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrC,OAAO;gBACL,GAAG,WAAW;gBACd,MAAM,EAAE;oBACN,GAAG,WAAW,CAAC,MAAM;oBACrB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;iBAC9B;aACF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,YAAY,CAAC,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;YACnE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,MAAM,CAAC;YAClC,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO;oBACL,GAAG,WAAW;oBACd,MAAM,EAAE;wBACN,GAAG,WAAW,CAAC,MAAM;wBACrB,CAAC,KAAK,CAAC,EAAE;4BACP,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC;4BAC9B,CAAC,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;yBACtC;qBACF;iBACF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1C,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtD,CAAC;QAED,IAAI,IAAI,KAAK,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnC,OAAO;gBACL,GAAG,WAAW;gBACd,IAAI,EAAE,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE;aAClD,CAAC;QACJ,CAAC;QAED,0BAA0B;QAC1B,OAAO,WAAW,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC"}
|