@leodamours/jsonapi-client 1.0.2
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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/index.d.mts +130 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +161 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +150 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Léo Damours
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# @leodamours/jsonapi-client
|
|
2
|
+
|
|
3
|
+
[](https://github.com/LeoDamours/json-api-client/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@leodamours/jsonapi-client)
|
|
5
|
+
|
|
6
|
+
A typed HTTP client for [JSON:API](https://jsonapi.org/) — full CRUD with automatic type inference, powered by Axios.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @leodamours/jsonapi-client @leodamours/jsonapi-dsl axios
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { defineResource, t, hasOne, hasMany } from "@leodamours/jsonapi-dsl";
|
|
18
|
+
import { createClient } from "@leodamours/jsonapi-client";
|
|
19
|
+
|
|
20
|
+
const User = defineResource(
|
|
21
|
+
"users",
|
|
22
|
+
{
|
|
23
|
+
name: t.string(),
|
|
24
|
+
email: t.string(),
|
|
25
|
+
age: t.nullable(t.number()),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
posts: hasMany("posts"),
|
|
29
|
+
company: hasOne("companies"),
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const api = createClient({ baseURL: "https://api.example.com" });
|
|
34
|
+
|
|
35
|
+
// CREATE — attributes fully typed, relationships type-checked
|
|
36
|
+
const created = await api.create(User, {
|
|
37
|
+
attributes: { name: "Leo", email: "leo@example.com", age: null },
|
|
38
|
+
relationships: { company: { type: "companies", id: "1" } },
|
|
39
|
+
});
|
|
40
|
+
console.log(created.data.attributes.name); // string
|
|
41
|
+
|
|
42
|
+
// FIND ONE
|
|
43
|
+
const user = await api.findOne(User, "1", { include: "posts" });
|
|
44
|
+
console.log(user.data.attributes.email); // string
|
|
45
|
+
|
|
46
|
+
// FIND ALL with pagination
|
|
47
|
+
const users = await api.findAll(User, {
|
|
48
|
+
page: { size: 10, number: 1 },
|
|
49
|
+
filter: { active: true },
|
|
50
|
+
sort: ["-createdAt", "name"],
|
|
51
|
+
});
|
|
52
|
+
for (const u of users.data) {
|
|
53
|
+
console.log(u.attributes.name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// UPDATE — attributes are Partial
|
|
57
|
+
await api.update(User, "1", {
|
|
58
|
+
attributes: { name: "Updated" },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// DELETE
|
|
62
|
+
await api.remove(User, "1");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Client Configuration
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { createClient } from "@leodamours/jsonapi-client";
|
|
69
|
+
|
|
70
|
+
const api = createClient({
|
|
71
|
+
baseURL: "https://api.example.com",
|
|
72
|
+
jwtToken: "your-jwt-token", // sets Authorization: Bearer ...
|
|
73
|
+
headers: { "X-Custom": "value" }, // extra headers
|
|
74
|
+
timeout: 5000, // request timeout in ms
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Update config at runtime (e.g. after login)
|
|
78
|
+
api.updateConfig({ jwtToken: "new-token" });
|
|
79
|
+
|
|
80
|
+
// Clear auth
|
|
81
|
+
api.updateConfig({ jwtToken: null as any });
|
|
82
|
+
|
|
83
|
+
// Access underlying Axios instance
|
|
84
|
+
const axiosInstance = api.getAxiosInstance();
|
|
85
|
+
axiosInstance.interceptors.response.use(/* ... */);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Per-Verb Attribute Narrowing
|
|
89
|
+
|
|
90
|
+
When a resource is defined with `create`/`update` options, the client enforces which attributes each verb accepts:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const Author = defineResource(
|
|
94
|
+
"authors",
|
|
95
|
+
{
|
|
96
|
+
name: t.string(),
|
|
97
|
+
email: t.string(),
|
|
98
|
+
createdAt: t.string(),
|
|
99
|
+
},
|
|
100
|
+
{ posts: hasMany("posts") },
|
|
101
|
+
{
|
|
102
|
+
create: ["name", "email"] as const,
|
|
103
|
+
update: ["name"] as const,
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Only name and email accepted (createdAt is rejected at compile time)
|
|
108
|
+
await api.create(Author, {
|
|
109
|
+
attributes: { name: "Leo", email: "leo@example.com" },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Only name accepted
|
|
113
|
+
await api.update(Author, "1", {
|
|
114
|
+
attributes: { name: "Updated" },
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Response still has all attributes
|
|
118
|
+
const doc = await api.findOne(Author, "1");
|
|
119
|
+
doc.data.attributes.createdAt; // string
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Payload Types
|
|
123
|
+
|
|
124
|
+
Use `CreatePayload` and `UpdatePayload` for standalone typing:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import type { CreatePayload, UpdatePayload } from "@leodamours/jsonapi-client";
|
|
128
|
+
|
|
129
|
+
type CreateUser = CreatePayload<typeof User>;
|
|
130
|
+
// { attributes: { name: string; email: string; age: number | null }; relationships?: ... }
|
|
131
|
+
|
|
132
|
+
type UpdateUser = UpdatePayload<typeof User>;
|
|
133
|
+
// { attributes?: Partial<{ name: string; email: string; age: number | null }>; relationships?: ... }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Query Parameters
|
|
137
|
+
|
|
138
|
+
The client serializes query params following JSON:API conventions:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
await api.findAll(User, {
|
|
142
|
+
page: { size: 10, number: 2 }, // → page[size]=10&page[number]=2
|
|
143
|
+
filter: { active: true }, // → filter[active]=true
|
|
144
|
+
sort: ["-createdAt", "name"], // → sort=-createdAt,name
|
|
145
|
+
include: ["posts", "company"], // → include=posts,company
|
|
146
|
+
fields: { users: ["name", "email"] },// → fields[users]=name,email
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Serialization Utilities
|
|
151
|
+
|
|
152
|
+
Lower-level serialization functions are exported for custom use:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import {
|
|
156
|
+
serializeCreatePayload,
|
|
157
|
+
serializeUpdatePayload,
|
|
158
|
+
serializeQueryParams,
|
|
159
|
+
serializeRelationships,
|
|
160
|
+
} from "@leodamours/jsonapi-client";
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Axios Integration
|
|
164
|
+
|
|
165
|
+
The client uses Axios under the hood. All CRUD methods accept an optional `AxiosRequestConfig` as the last argument:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
await api.findOne(User, "1", undefined, {
|
|
169
|
+
signal: abortController.signal,
|
|
170
|
+
headers: { "X-Request-Id": "abc" },
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosInstance } from 'axios';
|
|
2
|
+
import { ResourceDefinition, InferAttributesSimple, RelationshipsSchema, JsonApiDocument, InferResource, JsonApiCollectionDocument, JsonApiRelationships } from '@leodamours/jsonapi-dsl';
|
|
3
|
+
|
|
4
|
+
type SimplifiedHasOne<T extends string> = {
|
|
5
|
+
type: T;
|
|
6
|
+
id: string;
|
|
7
|
+
} | null;
|
|
8
|
+
type SimplifiedHasMany<T extends string> = Array<{
|
|
9
|
+
type: T;
|
|
10
|
+
id: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Indexed lookup for simplified relationship shapes — avoids conditional types.
|
|
14
|
+
*/
|
|
15
|
+
interface SimplifiedRelTypeMap<T extends string> {
|
|
16
|
+
one: SimplifiedHasOne<T>;
|
|
17
|
+
many: SimplifiedHasMany<T>;
|
|
18
|
+
}
|
|
19
|
+
type SimplifiedRelationships<Schema extends RelationshipsSchema> = {
|
|
20
|
+
[K in keyof Schema]: SimplifiedRelTypeMap<Schema[K]["type"]>[Schema[K]["_rel"]];
|
|
21
|
+
};
|
|
22
|
+
type RelationshipClause<Rels extends RelationshipsSchema> = {
|
|
23
|
+
relationships?: Partial<SimplifiedRelationships<Rels>>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Payload for creating a resource.
|
|
27
|
+
* When the definition has `createKeys`, only those attribute keys are accepted.
|
|
28
|
+
*/
|
|
29
|
+
type CreatePayload<Def extends ResourceDefinition> = Def extends {
|
|
30
|
+
readonly createKeys: readonly (infer CK extends string)[];
|
|
31
|
+
} ? {
|
|
32
|
+
attributes: Pick<InferAttributesSimple<Def["attributes"]>, CK & keyof Def["attributes"]>;
|
|
33
|
+
relationships?: Partial<SimplifiedRelationships<Def["relationships"]>>;
|
|
34
|
+
} : {
|
|
35
|
+
attributes: InferAttributesSimple<Def["attributes"]>;
|
|
36
|
+
relationships?: Partial<SimplifiedRelationships<Def["relationships"]>>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Payload for updating a resource.
|
|
40
|
+
* When the definition has `updateKeys`, only those attribute keys are accepted.
|
|
41
|
+
*/
|
|
42
|
+
type UpdatePayload<Def extends ResourceDefinition> = Def extends {
|
|
43
|
+
readonly updateKeys: readonly (infer UK extends string)[];
|
|
44
|
+
} ? {
|
|
45
|
+
attributes?: Partial<Pick<InferAttributesSimple<Def["attributes"]>, UK & keyof Def["attributes"]>>;
|
|
46
|
+
relationships?: Partial<SimplifiedRelationships<Def["relationships"]>>;
|
|
47
|
+
} : {
|
|
48
|
+
attributes?: Partial<InferAttributesSimple<Def["attributes"]>>;
|
|
49
|
+
relationships?: Partial<SimplifiedRelationships<Def["relationships"]>>;
|
|
50
|
+
};
|
|
51
|
+
interface ClientConfig {
|
|
52
|
+
baseURL: string;
|
|
53
|
+
jwtToken?: string;
|
|
54
|
+
headers?: Record<string, string>;
|
|
55
|
+
timeout?: number;
|
|
56
|
+
}
|
|
57
|
+
interface QueryParams {
|
|
58
|
+
page?: Record<string, string | number>;
|
|
59
|
+
filter?: Record<string, string | number | boolean>;
|
|
60
|
+
sort?: string | string[];
|
|
61
|
+
include?: string | string[];
|
|
62
|
+
fields?: Record<string, string | string[]>;
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare class JsonApiClient {
|
|
67
|
+
private axios;
|
|
68
|
+
constructor(config: ClientConfig);
|
|
69
|
+
/**
|
|
70
|
+
* POST /{resourceType}
|
|
71
|
+
*/
|
|
72
|
+
create<Def extends ResourceDefinition>(definition: Def, payload: CreatePayload<Def>, options?: AxiosRequestConfig): Promise<JsonApiDocument<InferResource<Def>>>;
|
|
73
|
+
/**
|
|
74
|
+
* GET /{resourceType}/{id}
|
|
75
|
+
*/
|
|
76
|
+
findOne<Def extends ResourceDefinition>(definition: Def, id: string, params?: QueryParams, options?: AxiosRequestConfig): Promise<JsonApiDocument<InferResource<Def>>>;
|
|
77
|
+
/**
|
|
78
|
+
* GET /{resourceType}
|
|
79
|
+
*/
|
|
80
|
+
findAll<Def extends ResourceDefinition>(definition: Def, params?: QueryParams, options?: AxiosRequestConfig): Promise<JsonApiCollectionDocument<InferResource<Def>>>;
|
|
81
|
+
/**
|
|
82
|
+
* PATCH /{resourceType}/{id}
|
|
83
|
+
*/
|
|
84
|
+
update<Def extends ResourceDefinition>(definition: Def, id: string, payload: UpdatePayload<Def>, options?: AxiosRequestConfig): Promise<JsonApiDocument<InferResource<Def>>>;
|
|
85
|
+
/**
|
|
86
|
+
* DELETE /{resourceType}/{id}
|
|
87
|
+
*/
|
|
88
|
+
remove(definition: ResourceDefinition, id: string, options?: AxiosRequestConfig): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Update client configuration at runtime.
|
|
91
|
+
*/
|
|
92
|
+
updateConfig(config: Partial<ClientConfig>): void;
|
|
93
|
+
/**
|
|
94
|
+
* Access the underlying Axios instance for advanced use.
|
|
95
|
+
*/
|
|
96
|
+
getAxiosInstance(): AxiosInstance;
|
|
97
|
+
}
|
|
98
|
+
declare function createClient(config: ClientConfig): JsonApiClient;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Wrap simplified relationship values in `{ data: ... }` per JSON:API spec.
|
|
102
|
+
*/
|
|
103
|
+
declare function serializeRelationships(simplified: Record<string, unknown> | undefined, definition: ResourceDefinition): JsonApiRelationships | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Build a JSON:API request body for creating a resource.
|
|
106
|
+
*/
|
|
107
|
+
declare function serializeCreatePayload(definition: ResourceDefinition, payload: {
|
|
108
|
+
attributes: Record<string, unknown>;
|
|
109
|
+
relationships?: Record<string, unknown>;
|
|
110
|
+
}): {
|
|
111
|
+
data: Record<string, unknown>;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Build a JSON:API request body for updating a resource.
|
|
115
|
+
*/
|
|
116
|
+
declare function serializeUpdatePayload(definition: ResourceDefinition, id: string, payload: {
|
|
117
|
+
attributes?: Record<string, unknown>;
|
|
118
|
+
relationships?: Record<string, unknown>;
|
|
119
|
+
}): {
|
|
120
|
+
data: Record<string, unknown>;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Encode query params using JSON:API bracket conventions.
|
|
124
|
+
*
|
|
125
|
+
* `{ page: { size: 10 } }` → `"page[size]=10"`
|
|
126
|
+
* `{ sort: ["-createdAt", "title"] }` → `"sort=-createdAt,title"`
|
|
127
|
+
*/
|
|
128
|
+
declare function serializeQueryParams(params: Record<string, unknown>): Record<string, string>;
|
|
129
|
+
|
|
130
|
+
export { type ClientConfig, type CreatePayload, JsonApiClient, type QueryParams, type RelationshipClause, type SimplifiedHasMany, type SimplifiedHasOne, type SimplifiedRelationships, type UpdatePayload, createClient, serializeCreatePayload, serializeQueryParams, serializeRelationships, serializeUpdatePayload };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosInstance } from 'axios';
|
|
2
|
+
import { ResourceDefinition, InferAttributesSimple, RelationshipsSchema, JsonApiDocument, InferResource, JsonApiCollectionDocument, JsonApiRelationships } from '@leodamours/jsonapi-dsl';
|
|
3
|
+
|
|
4
|
+
type SimplifiedHasOne<T extends string> = {
|
|
5
|
+
type: T;
|
|
6
|
+
id: string;
|
|
7
|
+
} | null;
|
|
8
|
+
type SimplifiedHasMany<T extends string> = Array<{
|
|
9
|
+
type: T;
|
|
10
|
+
id: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Indexed lookup for simplified relationship shapes — avoids conditional types.
|
|
14
|
+
*/
|
|
15
|
+
interface SimplifiedRelTypeMap<T extends string> {
|
|
16
|
+
one: SimplifiedHasOne<T>;
|
|
17
|
+
many: SimplifiedHasMany<T>;
|
|
18
|
+
}
|
|
19
|
+
type SimplifiedRelationships<Schema extends RelationshipsSchema> = {
|
|
20
|
+
[K in keyof Schema]: SimplifiedRelTypeMap<Schema[K]["type"]>[Schema[K]["_rel"]];
|
|
21
|
+
};
|
|
22
|
+
type RelationshipClause<Rels extends RelationshipsSchema> = {
|
|
23
|
+
relationships?: Partial<SimplifiedRelationships<Rels>>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Payload for creating a resource.
|
|
27
|
+
* When the definition has `createKeys`, only those attribute keys are accepted.
|
|
28
|
+
*/
|
|
29
|
+
type CreatePayload<Def extends ResourceDefinition> = Def extends {
|
|
30
|
+
readonly createKeys: readonly (infer CK extends string)[];
|
|
31
|
+
} ? {
|
|
32
|
+
attributes: Pick<InferAttributesSimple<Def["attributes"]>, CK & keyof Def["attributes"]>;
|
|
33
|
+
relationships?: Partial<SimplifiedRelationships<Def["relationships"]>>;
|
|
34
|
+
} : {
|
|
35
|
+
attributes: InferAttributesSimple<Def["attributes"]>;
|
|
36
|
+
relationships?: Partial<SimplifiedRelationships<Def["relationships"]>>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Payload for updating a resource.
|
|
40
|
+
* When the definition has `updateKeys`, only those attribute keys are accepted.
|
|
41
|
+
*/
|
|
42
|
+
type UpdatePayload<Def extends ResourceDefinition> = Def extends {
|
|
43
|
+
readonly updateKeys: readonly (infer UK extends string)[];
|
|
44
|
+
} ? {
|
|
45
|
+
attributes?: Partial<Pick<InferAttributesSimple<Def["attributes"]>, UK & keyof Def["attributes"]>>;
|
|
46
|
+
relationships?: Partial<SimplifiedRelationships<Def["relationships"]>>;
|
|
47
|
+
} : {
|
|
48
|
+
attributes?: Partial<InferAttributesSimple<Def["attributes"]>>;
|
|
49
|
+
relationships?: Partial<SimplifiedRelationships<Def["relationships"]>>;
|
|
50
|
+
};
|
|
51
|
+
interface ClientConfig {
|
|
52
|
+
baseURL: string;
|
|
53
|
+
jwtToken?: string;
|
|
54
|
+
headers?: Record<string, string>;
|
|
55
|
+
timeout?: number;
|
|
56
|
+
}
|
|
57
|
+
interface QueryParams {
|
|
58
|
+
page?: Record<string, string | number>;
|
|
59
|
+
filter?: Record<string, string | number | boolean>;
|
|
60
|
+
sort?: string | string[];
|
|
61
|
+
include?: string | string[];
|
|
62
|
+
fields?: Record<string, string | string[]>;
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare class JsonApiClient {
|
|
67
|
+
private axios;
|
|
68
|
+
constructor(config: ClientConfig);
|
|
69
|
+
/**
|
|
70
|
+
* POST /{resourceType}
|
|
71
|
+
*/
|
|
72
|
+
create<Def extends ResourceDefinition>(definition: Def, payload: CreatePayload<Def>, options?: AxiosRequestConfig): Promise<JsonApiDocument<InferResource<Def>>>;
|
|
73
|
+
/**
|
|
74
|
+
* GET /{resourceType}/{id}
|
|
75
|
+
*/
|
|
76
|
+
findOne<Def extends ResourceDefinition>(definition: Def, id: string, params?: QueryParams, options?: AxiosRequestConfig): Promise<JsonApiDocument<InferResource<Def>>>;
|
|
77
|
+
/**
|
|
78
|
+
* GET /{resourceType}
|
|
79
|
+
*/
|
|
80
|
+
findAll<Def extends ResourceDefinition>(definition: Def, params?: QueryParams, options?: AxiosRequestConfig): Promise<JsonApiCollectionDocument<InferResource<Def>>>;
|
|
81
|
+
/**
|
|
82
|
+
* PATCH /{resourceType}/{id}
|
|
83
|
+
*/
|
|
84
|
+
update<Def extends ResourceDefinition>(definition: Def, id: string, payload: UpdatePayload<Def>, options?: AxiosRequestConfig): Promise<JsonApiDocument<InferResource<Def>>>;
|
|
85
|
+
/**
|
|
86
|
+
* DELETE /{resourceType}/{id}
|
|
87
|
+
*/
|
|
88
|
+
remove(definition: ResourceDefinition, id: string, options?: AxiosRequestConfig): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Update client configuration at runtime.
|
|
91
|
+
*/
|
|
92
|
+
updateConfig(config: Partial<ClientConfig>): void;
|
|
93
|
+
/**
|
|
94
|
+
* Access the underlying Axios instance for advanced use.
|
|
95
|
+
*/
|
|
96
|
+
getAxiosInstance(): AxiosInstance;
|
|
97
|
+
}
|
|
98
|
+
declare function createClient(config: ClientConfig): JsonApiClient;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Wrap simplified relationship values in `{ data: ... }` per JSON:API spec.
|
|
102
|
+
*/
|
|
103
|
+
declare function serializeRelationships(simplified: Record<string, unknown> | undefined, definition: ResourceDefinition): JsonApiRelationships | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Build a JSON:API request body for creating a resource.
|
|
106
|
+
*/
|
|
107
|
+
declare function serializeCreatePayload(definition: ResourceDefinition, payload: {
|
|
108
|
+
attributes: Record<string, unknown>;
|
|
109
|
+
relationships?: Record<string, unknown>;
|
|
110
|
+
}): {
|
|
111
|
+
data: Record<string, unknown>;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Build a JSON:API request body for updating a resource.
|
|
115
|
+
*/
|
|
116
|
+
declare function serializeUpdatePayload(definition: ResourceDefinition, id: string, payload: {
|
|
117
|
+
attributes?: Record<string, unknown>;
|
|
118
|
+
relationships?: Record<string, unknown>;
|
|
119
|
+
}): {
|
|
120
|
+
data: Record<string, unknown>;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Encode query params using JSON:API bracket conventions.
|
|
124
|
+
*
|
|
125
|
+
* `{ page: { size: 10 } }` → `"page[size]=10"`
|
|
126
|
+
* `{ sort: ["-createdAt", "title"] }` → `"sort=-createdAt,title"`
|
|
127
|
+
*/
|
|
128
|
+
declare function serializeQueryParams(params: Record<string, unknown>): Record<string, string>;
|
|
129
|
+
|
|
130
|
+
export { type ClientConfig, type CreatePayload, JsonApiClient, type QueryParams, type RelationshipClause, type SimplifiedHasMany, type SimplifiedHasOne, type SimplifiedRelationships, type UpdatePayload, createClient, serializeCreatePayload, serializeQueryParams, serializeRelationships, serializeUpdatePayload };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var axios = require('axios');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
8
|
+
|
|
9
|
+
// src/client.ts
|
|
10
|
+
|
|
11
|
+
// src/serialize.ts
|
|
12
|
+
function serializeRelationships(simplified, definition) {
|
|
13
|
+
if (!simplified) return void 0;
|
|
14
|
+
const result = {};
|
|
15
|
+
let hasKeys = false;
|
|
16
|
+
for (const key of Object.keys(simplified)) {
|
|
17
|
+
const value = simplified[key];
|
|
18
|
+
if (value === void 0) continue;
|
|
19
|
+
const relDef = definition.relationships[key];
|
|
20
|
+
if (!relDef) continue;
|
|
21
|
+
hasKeys = true;
|
|
22
|
+
if (relDef._rel === "one") {
|
|
23
|
+
result[key] = { data: value };
|
|
24
|
+
} else {
|
|
25
|
+
result[key] = { data: value };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return hasKeys ? result : void 0;
|
|
29
|
+
}
|
|
30
|
+
function serializeCreatePayload(definition, payload) {
|
|
31
|
+
const data = {
|
|
32
|
+
type: definition.resourceType,
|
|
33
|
+
attributes: payload.attributes
|
|
34
|
+
};
|
|
35
|
+
const rels = serializeRelationships(payload.relationships, definition);
|
|
36
|
+
if (rels) data.relationships = rels;
|
|
37
|
+
return { data };
|
|
38
|
+
}
|
|
39
|
+
function serializeUpdatePayload(definition, id, payload) {
|
|
40
|
+
const data = {
|
|
41
|
+
type: definition.resourceType,
|
|
42
|
+
id
|
|
43
|
+
};
|
|
44
|
+
if (payload.attributes && Object.keys(payload.attributes).length > 0) {
|
|
45
|
+
data.attributes = payload.attributes;
|
|
46
|
+
}
|
|
47
|
+
const rels = serializeRelationships(payload.relationships, definition);
|
|
48
|
+
if (rels) data.relationships = rels;
|
|
49
|
+
return { data };
|
|
50
|
+
}
|
|
51
|
+
function serializeQueryParams(params) {
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const [key, value] of Object.entries(params)) {
|
|
54
|
+
if (value == null) continue;
|
|
55
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
56
|
+
for (const [nested, nv] of Object.entries(value)) {
|
|
57
|
+
if (nv != null) out[`${key}[${nested}]`] = String(nv);
|
|
58
|
+
}
|
|
59
|
+
} else if (Array.isArray(value)) {
|
|
60
|
+
out[key] = value.join(",");
|
|
61
|
+
} else {
|
|
62
|
+
out[key] = String(value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/client.ts
|
|
69
|
+
var JsonApiClient = class {
|
|
70
|
+
constructor(config) {
|
|
71
|
+
const headers = {
|
|
72
|
+
"Content-Type": "application/vnd.api+json",
|
|
73
|
+
Accept: "application/vnd.api+json",
|
|
74
|
+
...config.headers
|
|
75
|
+
};
|
|
76
|
+
if (config.jwtToken) {
|
|
77
|
+
headers.Authorization = `Bearer ${config.jwtToken}`;
|
|
78
|
+
}
|
|
79
|
+
this.axios = axios__default.default.create({
|
|
80
|
+
baseURL: config.baseURL,
|
|
81
|
+
headers,
|
|
82
|
+
timeout: config.timeout
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* POST /{resourceType}
|
|
87
|
+
*/
|
|
88
|
+
async create(definition, payload, options) {
|
|
89
|
+
const body = serializeCreatePayload(definition, payload);
|
|
90
|
+
const res = await this.axios.post(`/${definition.resourceType}`, body, options);
|
|
91
|
+
return res.data;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* GET /{resourceType}/{id}
|
|
95
|
+
*/
|
|
96
|
+
async findOne(definition, id, params, options) {
|
|
97
|
+
const res = await this.axios.get(`/${definition.resourceType}/${id}`, {
|
|
98
|
+
params: params ? serializeQueryParams(params) : void 0,
|
|
99
|
+
...options
|
|
100
|
+
});
|
|
101
|
+
return res.data;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* GET /{resourceType}
|
|
105
|
+
*/
|
|
106
|
+
async findAll(definition, params, options) {
|
|
107
|
+
const res = await this.axios.get(`/${definition.resourceType}`, {
|
|
108
|
+
params: params ? serializeQueryParams(params) : void 0,
|
|
109
|
+
...options
|
|
110
|
+
});
|
|
111
|
+
return res.data;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* PATCH /{resourceType}/{id}
|
|
115
|
+
*/
|
|
116
|
+
async update(definition, id, payload, options) {
|
|
117
|
+
const body = serializeUpdatePayload(definition, id, payload);
|
|
118
|
+
const res = await this.axios.patch(`/${definition.resourceType}/${id}`, body, options);
|
|
119
|
+
return res.data;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* DELETE /{resourceType}/{id}
|
|
123
|
+
*/
|
|
124
|
+
async remove(definition, id, options) {
|
|
125
|
+
await this.axios.delete(`/${definition.resourceType}/${id}`, options);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Update client configuration at runtime.
|
|
129
|
+
*/
|
|
130
|
+
updateConfig(config) {
|
|
131
|
+
if (config.baseURL) {
|
|
132
|
+
this.axios.defaults.baseURL = config.baseURL;
|
|
133
|
+
}
|
|
134
|
+
if (config.headers) {
|
|
135
|
+
Object.assign(this.axios.defaults.headers.common, config.headers);
|
|
136
|
+
}
|
|
137
|
+
if (config.jwtToken) {
|
|
138
|
+
this.axios.defaults.headers.common.Authorization = `Bearer ${config.jwtToken}`;
|
|
139
|
+
} else if (config.jwtToken === null) {
|
|
140
|
+
delete this.axios.defaults.headers.common.Authorization;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Access the underlying Axios instance for advanced use.
|
|
145
|
+
*/
|
|
146
|
+
getAxiosInstance() {
|
|
147
|
+
return this.axios;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
function createClient(config) {
|
|
151
|
+
return new JsonApiClient(config);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
exports.JsonApiClient = JsonApiClient;
|
|
155
|
+
exports.createClient = createClient;
|
|
156
|
+
exports.serializeCreatePayload = serializeCreatePayload;
|
|
157
|
+
exports.serializeQueryParams = serializeQueryParams;
|
|
158
|
+
exports.serializeRelationships = serializeRelationships;
|
|
159
|
+
exports.serializeUpdatePayload = serializeUpdatePayload;
|
|
160
|
+
//# sourceMappingURL=index.js.map
|
|
161
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serialize.ts","../src/client.ts"],"names":["axios"],"mappings":"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","file":"index.js","sourcesContent":["import type { ResourceDefinition, JsonApiRelationships } from \"@leodamours/jsonapi-dsl\";\n\n/**\n * Wrap simplified relationship values in `{ data: ... }` per JSON:API spec.\n */\nexport function serializeRelationships(\n simplified: Record<string, unknown> | undefined,\n definition: ResourceDefinition,\n): JsonApiRelationships | undefined {\n if (!simplified) return undefined;\n\n const result: JsonApiRelationships = {};\n let hasKeys = false;\n\n for (const key of Object.keys(simplified)) {\n const value = simplified[key];\n if (value === undefined) continue;\n\n const relDef = definition.relationships[key];\n if (!relDef) continue;\n\n hasKeys = true;\n\n if (relDef._rel === \"one\") {\n result[key] = { data: value as { type: string; id: string } | null };\n } else {\n result[key] = { data: value as { type: string; id: string }[] };\n }\n }\n\n return hasKeys ? result : undefined;\n}\n\n/**\n * Build a JSON:API request body for creating a resource.\n */\nexport function serializeCreatePayload(\n definition: ResourceDefinition,\n payload: { attributes: Record<string, unknown>; relationships?: Record<string, unknown> },\n): { data: Record<string, unknown> } {\n const data: Record<string, unknown> = {\n type: definition.resourceType,\n attributes: payload.attributes,\n };\n\n const rels = serializeRelationships(payload.relationships, definition);\n if (rels) data.relationships = rels;\n\n return { data };\n}\n\n/**\n * Build a JSON:API request body for updating a resource.\n */\nexport function serializeUpdatePayload(\n definition: ResourceDefinition,\n id: string,\n payload: { attributes?: Record<string, unknown>; relationships?: Record<string, unknown> },\n): { data: Record<string, unknown> } {\n const data: Record<string, unknown> = {\n type: definition.resourceType,\n id,\n };\n\n if (payload.attributes && Object.keys(payload.attributes).length > 0) {\n data.attributes = payload.attributes;\n }\n\n const rels = serializeRelationships(payload.relationships, definition);\n if (rels) data.relationships = rels;\n\n return { data };\n}\n\n/**\n * Encode query params using JSON:API bracket conventions.\n *\n * `{ page: { size: 10 } }` → `\"page[size]=10\"`\n * `{ sort: [\"-createdAt\", \"title\"] }` → `\"sort=-createdAt,title\"`\n */\nexport function serializeQueryParams(params: Record<string, unknown>): Record<string, string> {\n const out: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(params)) {\n if (value == null) continue;\n\n if (typeof value === \"object\" && !Array.isArray(value)) {\n for (const [nested, nv] of Object.entries(value as Record<string, unknown>)) {\n if (nv != null) out[`${key}[${nested}]`] = String(nv);\n }\n } else if (Array.isArray(value)) {\n out[key] = value.join(\",\");\n } else {\n out[key] = String(value);\n }\n }\n\n return out;\n}\n","import axios, { type AxiosInstance, type AxiosRequestConfig } from \"axios\";\nimport type {\n ResourceDefinition,\n InferResource,\n JsonApiDocument,\n JsonApiCollectionDocument,\n} from \"@leodamours/jsonapi-dsl\";\nimport type { ClientConfig, QueryParams, CreatePayload, UpdatePayload } from \"./types\";\nimport { serializeCreatePayload, serializeUpdatePayload, serializeQueryParams } from \"./serialize\";\n\nexport class JsonApiClient {\n private axios: AxiosInstance;\n\n constructor(config: ClientConfig) {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/vnd.api+json\",\n Accept: \"application/vnd.api+json\",\n ...config.headers,\n };\n\n if (config.jwtToken) {\n headers.Authorization = `Bearer ${config.jwtToken}`;\n }\n\n this.axios = axios.create({\n baseURL: config.baseURL,\n headers,\n timeout: config.timeout,\n });\n }\n\n /**\n * POST /{resourceType}\n */\n async create<Def extends ResourceDefinition>(\n definition: Def,\n payload: CreatePayload<Def>,\n options?: AxiosRequestConfig,\n ): Promise<JsonApiDocument<InferResource<Def>>> {\n const body = serializeCreatePayload(definition, payload as any);\n const res = await this.axios.post(`/${definition.resourceType}`, body, options);\n return res.data;\n }\n\n /**\n * GET /{resourceType}/{id}\n */\n async findOne<Def extends ResourceDefinition>(\n definition: Def,\n id: string,\n params?: QueryParams,\n options?: AxiosRequestConfig,\n ): Promise<JsonApiDocument<InferResource<Def>>> {\n const res = await this.axios.get(`/${definition.resourceType}/${id}`, {\n params: params ? serializeQueryParams(params) : undefined,\n ...options,\n });\n return res.data;\n }\n\n /**\n * GET /{resourceType}\n */\n async findAll<Def extends ResourceDefinition>(\n definition: Def,\n params?: QueryParams,\n options?: AxiosRequestConfig,\n ): Promise<JsonApiCollectionDocument<InferResource<Def>>> {\n const res = await this.axios.get(`/${definition.resourceType}`, {\n params: params ? serializeQueryParams(params) : undefined,\n ...options,\n });\n return res.data;\n }\n\n /**\n * PATCH /{resourceType}/{id}\n */\n async update<Def extends ResourceDefinition>(\n definition: Def,\n id: string,\n payload: UpdatePayload<Def>,\n options?: AxiosRequestConfig,\n ): Promise<JsonApiDocument<InferResource<Def>>> {\n const body = serializeUpdatePayload(definition, id, payload as any);\n const res = await this.axios.patch(`/${definition.resourceType}/${id}`, body, options);\n return res.data;\n }\n\n /**\n * DELETE /{resourceType}/{id}\n */\n async remove(\n definition: ResourceDefinition,\n id: string,\n options?: AxiosRequestConfig,\n ): Promise<void> {\n await this.axios.delete(`/${definition.resourceType}/${id}`, options);\n }\n\n /**\n * Update client configuration at runtime.\n */\n updateConfig(config: Partial<ClientConfig>): void {\n if (config.baseURL) {\n this.axios.defaults.baseURL = config.baseURL;\n }\n if (config.headers) {\n Object.assign(this.axios.defaults.headers.common, config.headers);\n }\n if (config.jwtToken) {\n this.axios.defaults.headers.common.Authorization = `Bearer ${config.jwtToken}`;\n } else if (config.jwtToken === null) {\n delete this.axios.defaults.headers.common.Authorization;\n }\n }\n\n /**\n * Access the underlying Axios instance for advanced use.\n */\n getAxiosInstance(): AxiosInstance {\n return this.axios;\n }\n}\n\nexport function createClient(config: ClientConfig): JsonApiClient {\n return new JsonApiClient(config);\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
// src/client.ts
|
|
4
|
+
|
|
5
|
+
// src/serialize.ts
|
|
6
|
+
function serializeRelationships(simplified, definition) {
|
|
7
|
+
if (!simplified) return void 0;
|
|
8
|
+
const result = {};
|
|
9
|
+
let hasKeys = false;
|
|
10
|
+
for (const key of Object.keys(simplified)) {
|
|
11
|
+
const value = simplified[key];
|
|
12
|
+
if (value === void 0) continue;
|
|
13
|
+
const relDef = definition.relationships[key];
|
|
14
|
+
if (!relDef) continue;
|
|
15
|
+
hasKeys = true;
|
|
16
|
+
if (relDef._rel === "one") {
|
|
17
|
+
result[key] = { data: value };
|
|
18
|
+
} else {
|
|
19
|
+
result[key] = { data: value };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return hasKeys ? result : void 0;
|
|
23
|
+
}
|
|
24
|
+
function serializeCreatePayload(definition, payload) {
|
|
25
|
+
const data = {
|
|
26
|
+
type: definition.resourceType,
|
|
27
|
+
attributes: payload.attributes
|
|
28
|
+
};
|
|
29
|
+
const rels = serializeRelationships(payload.relationships, definition);
|
|
30
|
+
if (rels) data.relationships = rels;
|
|
31
|
+
return { data };
|
|
32
|
+
}
|
|
33
|
+
function serializeUpdatePayload(definition, id, payload) {
|
|
34
|
+
const data = {
|
|
35
|
+
type: definition.resourceType,
|
|
36
|
+
id
|
|
37
|
+
};
|
|
38
|
+
if (payload.attributes && Object.keys(payload.attributes).length > 0) {
|
|
39
|
+
data.attributes = payload.attributes;
|
|
40
|
+
}
|
|
41
|
+
const rels = serializeRelationships(payload.relationships, definition);
|
|
42
|
+
if (rels) data.relationships = rels;
|
|
43
|
+
return { data };
|
|
44
|
+
}
|
|
45
|
+
function serializeQueryParams(params) {
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const [key, value] of Object.entries(params)) {
|
|
48
|
+
if (value == null) continue;
|
|
49
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
50
|
+
for (const [nested, nv] of Object.entries(value)) {
|
|
51
|
+
if (nv != null) out[`${key}[${nested}]`] = String(nv);
|
|
52
|
+
}
|
|
53
|
+
} else if (Array.isArray(value)) {
|
|
54
|
+
out[key] = value.join(",");
|
|
55
|
+
} else {
|
|
56
|
+
out[key] = String(value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/client.ts
|
|
63
|
+
var JsonApiClient = class {
|
|
64
|
+
constructor(config) {
|
|
65
|
+
const headers = {
|
|
66
|
+
"Content-Type": "application/vnd.api+json",
|
|
67
|
+
Accept: "application/vnd.api+json",
|
|
68
|
+
...config.headers
|
|
69
|
+
};
|
|
70
|
+
if (config.jwtToken) {
|
|
71
|
+
headers.Authorization = `Bearer ${config.jwtToken}`;
|
|
72
|
+
}
|
|
73
|
+
this.axios = axios.create({
|
|
74
|
+
baseURL: config.baseURL,
|
|
75
|
+
headers,
|
|
76
|
+
timeout: config.timeout
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* POST /{resourceType}
|
|
81
|
+
*/
|
|
82
|
+
async create(definition, payload, options) {
|
|
83
|
+
const body = serializeCreatePayload(definition, payload);
|
|
84
|
+
const res = await this.axios.post(`/${definition.resourceType}`, body, options);
|
|
85
|
+
return res.data;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* GET /{resourceType}/{id}
|
|
89
|
+
*/
|
|
90
|
+
async findOne(definition, id, params, options) {
|
|
91
|
+
const res = await this.axios.get(`/${definition.resourceType}/${id}`, {
|
|
92
|
+
params: params ? serializeQueryParams(params) : void 0,
|
|
93
|
+
...options
|
|
94
|
+
});
|
|
95
|
+
return res.data;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* GET /{resourceType}
|
|
99
|
+
*/
|
|
100
|
+
async findAll(definition, params, options) {
|
|
101
|
+
const res = await this.axios.get(`/${definition.resourceType}`, {
|
|
102
|
+
params: params ? serializeQueryParams(params) : void 0,
|
|
103
|
+
...options
|
|
104
|
+
});
|
|
105
|
+
return res.data;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* PATCH /{resourceType}/{id}
|
|
109
|
+
*/
|
|
110
|
+
async update(definition, id, payload, options) {
|
|
111
|
+
const body = serializeUpdatePayload(definition, id, payload);
|
|
112
|
+
const res = await this.axios.patch(`/${definition.resourceType}/${id}`, body, options);
|
|
113
|
+
return res.data;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* DELETE /{resourceType}/{id}
|
|
117
|
+
*/
|
|
118
|
+
async remove(definition, id, options) {
|
|
119
|
+
await this.axios.delete(`/${definition.resourceType}/${id}`, options);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Update client configuration at runtime.
|
|
123
|
+
*/
|
|
124
|
+
updateConfig(config) {
|
|
125
|
+
if (config.baseURL) {
|
|
126
|
+
this.axios.defaults.baseURL = config.baseURL;
|
|
127
|
+
}
|
|
128
|
+
if (config.headers) {
|
|
129
|
+
Object.assign(this.axios.defaults.headers.common, config.headers);
|
|
130
|
+
}
|
|
131
|
+
if (config.jwtToken) {
|
|
132
|
+
this.axios.defaults.headers.common.Authorization = `Bearer ${config.jwtToken}`;
|
|
133
|
+
} else if (config.jwtToken === null) {
|
|
134
|
+
delete this.axios.defaults.headers.common.Authorization;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Access the underlying Axios instance for advanced use.
|
|
139
|
+
*/
|
|
140
|
+
getAxiosInstance() {
|
|
141
|
+
return this.axios;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
function createClient(config) {
|
|
145
|
+
return new JsonApiClient(config);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { JsonApiClient, createClient, serializeCreatePayload, serializeQueryParams, serializeRelationships, serializeUpdatePayload };
|
|
149
|
+
//# sourceMappingURL=index.mjs.map
|
|
150
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serialize.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ResourceDefinition, JsonApiRelationships } from \"@leodamours/jsonapi-dsl\";\n\n/**\n * Wrap simplified relationship values in `{ data: ... }` per JSON:API spec.\n */\nexport function serializeRelationships(\n simplified: Record<string, unknown> | undefined,\n definition: ResourceDefinition,\n): JsonApiRelationships | undefined {\n if (!simplified) return undefined;\n\n const result: JsonApiRelationships = {};\n let hasKeys = false;\n\n for (const key of Object.keys(simplified)) {\n const value = simplified[key];\n if (value === undefined) continue;\n\n const relDef = definition.relationships[key];\n if (!relDef) continue;\n\n hasKeys = true;\n\n if (relDef._rel === \"one\") {\n result[key] = { data: value as { type: string; id: string } | null };\n } else {\n result[key] = { data: value as { type: string; id: string }[] };\n }\n }\n\n return hasKeys ? result : undefined;\n}\n\n/**\n * Build a JSON:API request body for creating a resource.\n */\nexport function serializeCreatePayload(\n definition: ResourceDefinition,\n payload: { attributes: Record<string, unknown>; relationships?: Record<string, unknown> },\n): { data: Record<string, unknown> } {\n const data: Record<string, unknown> = {\n type: definition.resourceType,\n attributes: payload.attributes,\n };\n\n const rels = serializeRelationships(payload.relationships, definition);\n if (rels) data.relationships = rels;\n\n return { data };\n}\n\n/**\n * Build a JSON:API request body for updating a resource.\n */\nexport function serializeUpdatePayload(\n definition: ResourceDefinition,\n id: string,\n payload: { attributes?: Record<string, unknown>; relationships?: Record<string, unknown> },\n): { data: Record<string, unknown> } {\n const data: Record<string, unknown> = {\n type: definition.resourceType,\n id,\n };\n\n if (payload.attributes && Object.keys(payload.attributes).length > 0) {\n data.attributes = payload.attributes;\n }\n\n const rels = serializeRelationships(payload.relationships, definition);\n if (rels) data.relationships = rels;\n\n return { data };\n}\n\n/**\n * Encode query params using JSON:API bracket conventions.\n *\n * `{ page: { size: 10 } }` → `\"page[size]=10\"`\n * `{ sort: [\"-createdAt\", \"title\"] }` → `\"sort=-createdAt,title\"`\n */\nexport function serializeQueryParams(params: Record<string, unknown>): Record<string, string> {\n const out: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(params)) {\n if (value == null) continue;\n\n if (typeof value === \"object\" && !Array.isArray(value)) {\n for (const [nested, nv] of Object.entries(value as Record<string, unknown>)) {\n if (nv != null) out[`${key}[${nested}]`] = String(nv);\n }\n } else if (Array.isArray(value)) {\n out[key] = value.join(\",\");\n } else {\n out[key] = String(value);\n }\n }\n\n return out;\n}\n","import axios, { type AxiosInstance, type AxiosRequestConfig } from \"axios\";\nimport type {\n ResourceDefinition,\n InferResource,\n JsonApiDocument,\n JsonApiCollectionDocument,\n} from \"@leodamours/jsonapi-dsl\";\nimport type { ClientConfig, QueryParams, CreatePayload, UpdatePayload } from \"./types\";\nimport { serializeCreatePayload, serializeUpdatePayload, serializeQueryParams } from \"./serialize\";\n\nexport class JsonApiClient {\n private axios: AxiosInstance;\n\n constructor(config: ClientConfig) {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/vnd.api+json\",\n Accept: \"application/vnd.api+json\",\n ...config.headers,\n };\n\n if (config.jwtToken) {\n headers.Authorization = `Bearer ${config.jwtToken}`;\n }\n\n this.axios = axios.create({\n baseURL: config.baseURL,\n headers,\n timeout: config.timeout,\n });\n }\n\n /**\n * POST /{resourceType}\n */\n async create<Def extends ResourceDefinition>(\n definition: Def,\n payload: CreatePayload<Def>,\n options?: AxiosRequestConfig,\n ): Promise<JsonApiDocument<InferResource<Def>>> {\n const body = serializeCreatePayload(definition, payload as any);\n const res = await this.axios.post(`/${definition.resourceType}`, body, options);\n return res.data;\n }\n\n /**\n * GET /{resourceType}/{id}\n */\n async findOne<Def extends ResourceDefinition>(\n definition: Def,\n id: string,\n params?: QueryParams,\n options?: AxiosRequestConfig,\n ): Promise<JsonApiDocument<InferResource<Def>>> {\n const res = await this.axios.get(`/${definition.resourceType}/${id}`, {\n params: params ? serializeQueryParams(params) : undefined,\n ...options,\n });\n return res.data;\n }\n\n /**\n * GET /{resourceType}\n */\n async findAll<Def extends ResourceDefinition>(\n definition: Def,\n params?: QueryParams,\n options?: AxiosRequestConfig,\n ): Promise<JsonApiCollectionDocument<InferResource<Def>>> {\n const res = await this.axios.get(`/${definition.resourceType}`, {\n params: params ? serializeQueryParams(params) : undefined,\n ...options,\n });\n return res.data;\n }\n\n /**\n * PATCH /{resourceType}/{id}\n */\n async update<Def extends ResourceDefinition>(\n definition: Def,\n id: string,\n payload: UpdatePayload<Def>,\n options?: AxiosRequestConfig,\n ): Promise<JsonApiDocument<InferResource<Def>>> {\n const body = serializeUpdatePayload(definition, id, payload as any);\n const res = await this.axios.patch(`/${definition.resourceType}/${id}`, body, options);\n return res.data;\n }\n\n /**\n * DELETE /{resourceType}/{id}\n */\n async remove(\n definition: ResourceDefinition,\n id: string,\n options?: AxiosRequestConfig,\n ): Promise<void> {\n await this.axios.delete(`/${definition.resourceType}/${id}`, options);\n }\n\n /**\n * Update client configuration at runtime.\n */\n updateConfig(config: Partial<ClientConfig>): void {\n if (config.baseURL) {\n this.axios.defaults.baseURL = config.baseURL;\n }\n if (config.headers) {\n Object.assign(this.axios.defaults.headers.common, config.headers);\n }\n if (config.jwtToken) {\n this.axios.defaults.headers.common.Authorization = `Bearer ${config.jwtToken}`;\n } else if (config.jwtToken === null) {\n delete this.axios.defaults.headers.common.Authorization;\n }\n }\n\n /**\n * Access the underlying Axios instance for advanced use.\n */\n getAxiosInstance(): AxiosInstance {\n return this.axios;\n }\n}\n\nexport function createClient(config: ClientConfig): JsonApiClient {\n return new JsonApiClient(config);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leodamours/jsonapi-client",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "A typed HTTP client for JSON:API — powered by Axios with full type inference",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "vitest run"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"jsonapi",
|
|
23
|
+
"json:api",
|
|
24
|
+
"typescript",
|
|
25
|
+
"http-client",
|
|
26
|
+
"axios",
|
|
27
|
+
"type-safe"
|
|
28
|
+
],
|
|
29
|
+
"author": "Leo Damours",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/LeoDamours/json-api-client.git",
|
|
34
|
+
"directory": "packages/client"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/LeoDamours/json-api-client/tree/main/packages/client",
|
|
37
|
+
"bugs": "https://github.com/LeoDamours/json-api-client/issues",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@leodamours/jsonapi-dsl": "^1.0.0",
|
|
43
|
+
"axios": "^1.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@leodamours/jsonapi-dsl": "^1.0.0",
|
|
47
|
+
"axios": "^1.7.0",
|
|
48
|
+
"typescript": "^5.4.0",
|
|
49
|
+
"tsup": "^8.0.0"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist/**/*",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
]
|
|
55
|
+
}
|