@cubicweb/client 3.0.0-alpha.9 → 3.0.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 +58 -34
- package/lib/Authenticator.d.ts +8 -0
- package/lib/Authenticator.d.ts.map +1 -0
- package/lib/Authenticator.js +2 -0
- package/lib/Authenticator.js.map +1 -0
- package/lib/api/Api.d.ts +56 -23
- package/lib/api/Api.d.ts.map +1 -1
- package/lib/api/Api.js +141 -143
- package/lib/api/Api.js.map +1 -1
- package/lib/api/Client.d.ts +26 -10
- package/lib/api/Client.d.ts.map +1 -1
- package/lib/api/Client.js +41 -58
- package/lib/api/Client.js.map +1 -1
- package/lib/api/ResultSet.d.ts +52 -0
- package/lib/api/ResultSet.d.ts.map +1 -0
- package/lib/api/ResultSet.js +64 -0
- package/lib/api/ResultSet.js.map +1 -0
- package/lib/api/authenticators/CookieAuthenticator.d.ts +10 -0
- package/lib/api/authenticators/CookieAuthenticator.d.ts.map +1 -0
- package/lib/api/authenticators/CookieAuthenticator.js +11 -0
- package/lib/api/authenticators/CookieAuthenticator.js.map +1 -0
- package/lib/api/authenticators/SignedRequestAuthenticator.d.ts +32 -0
- package/lib/api/authenticators/SignedRequestAuthenticator.d.ts.map +1 -0
- package/lib/api/authenticators/SignedRequestAuthenticator.js +89 -0
- package/lib/api/authenticators/SignedRequestAuthenticator.js.map +1 -0
- package/lib/api/binary/RQLBinary.d.ts +6 -0
- package/lib/api/binary/RQLBinary.d.ts.map +1 -0
- package/lib/api/binary/RQLBinary.js +10 -0
- package/lib/api/binary/RQLBinary.js.map +1 -0
- package/lib/api/query/RQLQuery.d.ts +40 -0
- package/lib/api/query/RQLQuery.d.ts.map +1 -0
- package/lib/api/query/RQLQuery.js +48 -0
- package/lib/api/query/RQLQuery.js.map +1 -0
- package/lib/api/query/references.d.ts +41 -0
- package/lib/api/query/references.d.ts.map +1 -0
- package/lib/api/query/references.js +50 -0
- package/lib/api/query/references.js.map +1 -0
- package/lib/api/transaction/Transaction.d.ts +25 -0
- package/lib/api/transaction/Transaction.d.ts.map +1 -0
- package/lib/api/transaction/Transaction.js +28 -0
- package/lib/api/transaction/Transaction.js.map +1 -0
- package/lib/api/transaction/TransactionResult.d.ts +41 -0
- package/lib/api/transaction/TransactionResult.d.ts.map +1 -0
- package/lib/api/transaction/TransactionResult.js +45 -0
- package/lib/api/transaction/TransactionResult.js.map +1 -0
- package/lib/encodeFormData.d.ts +5 -0
- package/lib/encodeFormData.d.ts.map +1 -0
- package/lib/encodeFormData.js +94 -0
- package/lib/encodeFormData.js.map +1 -0
- package/lib/{Errors.d.ts → errors.d.ts} +7 -4
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +19 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +19 -13
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +17 -34
- package/lib/index.js.map +1 -1
- package/lib/schema/classes/EntitySchema.d.ts +5 -5
- package/lib/schema/classes/EntitySchema.d.ts.map +1 -1
- package/lib/schema/classes/EntitySchema.js +3 -5
- package/lib/schema/classes/EntitySchema.js.map +1 -1
- package/lib/schema/classes/RelationDefinitionSchema.d.ts +6 -6
- package/lib/schema/classes/RelationDefinitionSchema.d.ts.map +1 -1
- package/lib/schema/classes/RelationDefinitionSchema.js +8 -10
- package/lib/schema/classes/RelationDefinitionSchema.js.map +1 -1
- package/lib/schema/classes/Schema.d.ts +7 -7
- package/lib/schema/classes/Schema.d.ts.map +1 -1
- package/lib/schema/classes/Schema.js +8 -11
- package/lib/schema/classes/Schema.js.map +1 -1
- package/lib/schema/classes/Utils.d.ts +10 -10
- package/lib/schema/classes/Utils.d.ts.map +1 -1
- package/lib/schema/classes/Utils.js +19 -35
- package/lib/schema/classes/Utils.js.map +1 -1
- package/lib/schema/raw/Constraints.d.ts +27 -0
- package/lib/schema/raw/Constraints.d.ts.map +1 -1
- package/lib/schema/raw/Constraints.js +1 -2
- package/lib/schema/raw/Entities.js +1 -2
- package/lib/schema/raw/RelationDefinitions.d.ts +3 -3
- package/lib/schema/raw/RelationDefinitions.d.ts.map +1 -1
- package/lib/schema/raw/RelationDefinitions.js +1 -2
- package/lib/schema/raw/Schema.d.ts +2 -2
- package/lib/schema/raw/Schema.d.ts.map +1 -1
- package/lib/schema/raw/Schema.js +1 -2
- package/lib/schema/raw/Utils.d.ts +1 -1
- package/lib/schema/raw/Utils.d.ts.map +1 -1
- package/lib/schema/raw/Utils.js +1 -2
- package/lib/utils.d.ts +39 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +58 -0
- package/lib/utils.js.map +1 -0
- package/package.json +15 -5
- package/src/Authenticator.ts +8 -0
- package/src/api/Api.ts +148 -125
- package/src/api/Client.ts +31 -18
- package/src/api/ResultSet.ts +95 -0
- package/src/api/authenticators/CookieAuthenticator.ts +17 -0
- package/src/api/authenticators/SignedRequestAuthenticator.ts +133 -0
- package/src/api/binary/RQLBinary.ts +9 -0
- package/src/api/query/RQLQuery.ts +51 -0
- package/src/api/query/references.ts +43 -0
- package/src/api/transaction/Transaction.ts +31 -0
- package/src/api/transaction/TransactionResult.ts +68 -0
- package/src/encodeFormData.ts +126 -0
- package/src/{Errors.ts → errors.ts} +6 -7
- package/src/index.ts +24 -17
- package/src/schema/classes/EntitySchema.ts +8 -5
- package/src/schema/classes/RelationDefinitionSchema.ts +7 -7
- package/src/schema/classes/Schema.ts +10 -7
- package/src/schema/classes/Utils.ts +4 -4
- package/src/schema/raw/Constraints.ts +28 -0
- package/src/schema/raw/RelationDefinitions.ts +3 -3
- package/src/schema/raw/Schema.ts +2 -2
- package/src/schema/raw/Utils.ts +1 -1
- package/src/utils.ts +91 -0
- package/lib/Errors.d.ts.map +0 -1
- package/lib/Errors.js +0 -25
- package/lib/Errors.js.map +0 -1
- package/lib/api/Transaction.d.ts +0 -46
- package/lib/api/Transaction.d.ts.map +0 -1
- package/lib/api/Transaction.js +0 -75
- package/lib/api/Transaction.js.map +0 -1
- package/src/api/Transaction.ts +0 -94
package/package.json
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
"name": "@cubicweb/client",
|
|
3
3
|
"description": "Official JS client for CubicWeb API",
|
|
4
4
|
"author": "Logilab",
|
|
5
|
-
"version": "3.0.0
|
|
5
|
+
"version": "3.0.0",
|
|
6
6
|
"license": "LGPL-3.0-or-later",
|
|
7
|
-
"main": "lib/index.js",
|
|
8
|
-
"types": "lib/index.d.ts",
|
|
9
7
|
"scripts": {
|
|
10
|
-
"build": "run -T tsc
|
|
8
|
+
"build": "run -T tsc --project tsconfig.build.json",
|
|
11
9
|
"docs": "run -T typedoc"
|
|
12
10
|
},
|
|
13
11
|
"repository": {
|
|
@@ -23,5 +21,17 @@
|
|
|
23
21
|
"lib/**",
|
|
24
22
|
"src/**",
|
|
25
23
|
"!**/__tests__/**"
|
|
26
|
-
]
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"uuid": "^9.0.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/uuid": "^9.0.8"
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./lib/index.js",
|
|
34
|
+
"./authenticators/SignedRequestAuthenticator": "./lib/api/authenticators/SignedRequestAuthenticator.js",
|
|
35
|
+
"./authenticators/CookieAuthenticator": "./lib/api/authenticators/CookieAuthenticator.js"
|
|
36
|
+
}
|
|
27
37
|
}
|
package/src/api/Api.ts
CHANGED
|
@@ -1,46 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { Authenticator } from "../Authenticator.js";
|
|
2
|
+
import { ValidationError } from "../errors.js";
|
|
3
|
+
import { RawSchema } from "../schema/raw/Schema.js";
|
|
4
|
+
import { getBinariesFromTransaction, serializeQueryParams } from "../utils.js";
|
|
5
|
+
import { RQLBinary } from "./binary/RQLBinary.js";
|
|
6
|
+
import { RQLQueryCellRef } from "./query/references.js";
|
|
7
|
+
import { Transaction } from "./transaction/Transaction.js";
|
|
3
8
|
import {
|
|
9
|
+
RawTransactionResult,
|
|
4
10
|
TransactionResult,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
} from "./transaction/TransactionResult.js";
|
|
12
|
+
|
|
13
|
+
interface ApiOptions {
|
|
14
|
+
authenticator?: Authenticator;
|
|
15
|
+
}
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
|
-
* Type representing
|
|
18
|
+
* Type representing valid final values to use as parameters in a RQL query.
|
|
19
|
+
*
|
|
20
|
+
* For references to requests/binaries, see {@link RQLQueryCellRef} and {@link RQLQueryBinaryRef}
|
|
12
21
|
*
|
|
13
22
|
* @category Api
|
|
14
23
|
*/
|
|
15
|
-
export type
|
|
24
|
+
export type RQLQueryParamValue = string | number | boolean | null;
|
|
16
25
|
|
|
17
|
-
export type RQLParamValue = string | number | boolean | null;
|
|
18
26
|
/**
|
|
19
27
|
* Type representing RQL parameters to send along a request.
|
|
20
28
|
*
|
|
29
|
+
* Each key must be referenced in the RQL query.
|
|
30
|
+
*
|
|
21
31
|
* @category Api
|
|
22
32
|
*/
|
|
23
|
-
export type
|
|
33
|
+
export type RQLQueryParams = Record<
|
|
24
34
|
string,
|
|
25
|
-
|
|
35
|
+
RQLQueryParamValue | RQLQueryCellRef | RQLBinary
|
|
26
36
|
>;
|
|
27
37
|
|
|
28
38
|
/**
|
|
29
|
-
* Type representing
|
|
30
|
-
*
|
|
31
|
-
* @category Api
|
|
32
|
-
*/
|
|
33
|
-
export type RqlRow = Array<string | number | boolean | null>;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Type representing a RQL request result.
|
|
37
|
-
*
|
|
38
|
-
* @category Api
|
|
39
|
-
*/
|
|
40
|
-
export type ResultSet = Array<RqlRow>;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Type representing the currently logged user's data
|
|
39
|
+
* Type representing the currently logged user's data as returned by the `/current-user` route
|
|
44
40
|
*
|
|
45
41
|
* @category Api
|
|
46
42
|
*/
|
|
@@ -64,6 +60,9 @@ type ApiError = {
|
|
|
64
60
|
/**
|
|
65
61
|
* Type representing the error data sent to clients when retrieving an API error.
|
|
66
62
|
*
|
|
63
|
+
* It simply adds the HTTP status code in a `status` key to the regular CubicWeb api error,
|
|
64
|
+
* so users can handle HTTP status codes in their code.
|
|
65
|
+
*
|
|
67
66
|
* @category Api
|
|
68
67
|
*/
|
|
69
68
|
export type ApiErrorResponse = {
|
|
@@ -74,7 +73,7 @@ export type ApiErrorResponse = {
|
|
|
74
73
|
* Class used to handle requests to the CubicWeb API.
|
|
75
74
|
*
|
|
76
75
|
* You should never have to create this object yourself.
|
|
77
|
-
* Instead, retrieve this object from the methods in the
|
|
76
|
+
* Instead, retrieve this object from the methods in the {@link Client} class.
|
|
78
77
|
*
|
|
79
78
|
* @internal
|
|
80
79
|
*/
|
|
@@ -87,18 +86,19 @@ export default class Api {
|
|
|
87
86
|
private _logoutApiUrl: string;
|
|
88
87
|
private _currentUserApiUrl: string;
|
|
89
88
|
private _binaryApiUrl: string;
|
|
89
|
+
private _authenticator?: Authenticator;
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* Class used to handle requests to the CubicWeb API.
|
|
93
93
|
*
|
|
94
94
|
* You should never have to create this object yourself.
|
|
95
|
-
* Instead, retrieve this object from the methods in the
|
|
95
|
+
* Instead, retrieve this object from the methods in the {@link Client} class.
|
|
96
96
|
*
|
|
97
97
|
* @param apiUrl The API url. It should not include the API version as it will be automatically added.
|
|
98
98
|
* Example: if you can retrieve the schema with https://example.com/api/v1/schema,
|
|
99
99
|
* use https://example.com/api as `apiUrl`.
|
|
100
100
|
*/
|
|
101
|
-
constructor(apiUrl: string) {
|
|
101
|
+
constructor(apiUrl: string, options: ApiOptions) {
|
|
102
102
|
this._apiUrl = apiUrl;
|
|
103
103
|
this._rqlApiUrl = this.getApiRoute("rql");
|
|
104
104
|
this._schemaApiUrl = this.getApiRoute("schema");
|
|
@@ -106,6 +106,7 @@ export default class Api {
|
|
|
106
106
|
this._logoutApiUrl = this.getApiRoute("logout");
|
|
107
107
|
this._currentUserApiUrl = this.getApiRoute("current-user");
|
|
108
108
|
this._binaryApiUrl = this.getApiRoute("binary");
|
|
109
|
+
this._authenticator = options.authenticator;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
get apiUrl() {
|
|
@@ -122,9 +123,8 @@ export default class Api {
|
|
|
122
123
|
* @returns The CubicWeb instance's JSON schema.
|
|
123
124
|
*/
|
|
124
125
|
async schema(): Promise<RawSchema> {
|
|
125
|
-
return nonNullFetchApi<RawSchema>(this._schemaApiUrl, {
|
|
126
|
+
return this.nonNullFetchApi<RawSchema>(this._schemaApiUrl, {
|
|
126
127
|
method: "GET",
|
|
127
|
-
credentials: "include",
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -132,18 +132,17 @@ export default class Api {
|
|
|
132
132
|
* Tries to log in the user on the configured CubicWeb instance with the given credentials.
|
|
133
133
|
*
|
|
134
134
|
* @param login The user login
|
|
135
|
-
* @param password
|
|
135
|
+
* @param password The user password
|
|
136
136
|
* @returns A promise returning true if the login was successful, false otherwise
|
|
137
137
|
*/
|
|
138
138
|
async login(login: string, password: string): Promise<boolean> {
|
|
139
139
|
try {
|
|
140
|
-
await fetchApi<null>(this._loginApiUrl, {
|
|
140
|
+
await this.fetchApi<null>(this._loginApiUrl, {
|
|
141
141
|
method: "POST",
|
|
142
142
|
body: JSON.stringify({
|
|
143
143
|
login,
|
|
144
144
|
password,
|
|
145
145
|
}),
|
|
146
|
-
credentials: "include",
|
|
147
146
|
});
|
|
148
147
|
return true;
|
|
149
148
|
} catch (_error) {
|
|
@@ -157,9 +156,8 @@ export default class Api {
|
|
|
157
156
|
* @returns A promise which will resolve when the user is properly logged out
|
|
158
157
|
*/
|
|
159
158
|
async logout(): Promise<void> {
|
|
160
|
-
await fetchApi<null>(this._logoutApiUrl, {
|
|
159
|
+
await this.fetchApi<null>(this._logoutApiUrl, {
|
|
161
160
|
method: "POST",
|
|
162
|
-
credentials: "include",
|
|
163
161
|
});
|
|
164
162
|
}
|
|
165
163
|
|
|
@@ -169,32 +167,42 @@ export default class Api {
|
|
|
169
167
|
* @returns A promise resolving to the current user
|
|
170
168
|
*/
|
|
171
169
|
async currentUser(): Promise<CurrentUser> {
|
|
172
|
-
return await nonNullFetchApi<CurrentUser>(this._currentUserApiUrl, {
|
|
170
|
+
return await this.nonNullFetchApi<CurrentUser>(this._currentUserApiUrl, {
|
|
173
171
|
method: "GET",
|
|
174
|
-
credentials: "include",
|
|
175
172
|
});
|
|
176
173
|
}
|
|
177
174
|
|
|
178
175
|
/**
|
|
179
176
|
* Get the binary for the given entity attribute
|
|
180
177
|
*
|
|
181
|
-
* @
|
|
178
|
+
* @param eid Entity eid to fetch the binary from
|
|
179
|
+
* @param attribute Attribute on the entity in which the binary is stored
|
|
180
|
+
* @returns A promise resolving to the binary as a Blob
|
|
182
181
|
*/
|
|
183
182
|
async binary(eid: number, attribute: string): Promise<Blob> {
|
|
184
183
|
const params = new URLSearchParams({
|
|
185
184
|
eid: eid.toString(),
|
|
186
185
|
attribute,
|
|
187
186
|
});
|
|
188
|
-
return await nonNullFetchApi<Blob>(this._binaryApiUrl + params, {
|
|
187
|
+
return await this.nonNullFetchApi<Blob>(this._binaryApiUrl + params, {
|
|
189
188
|
method: "GET",
|
|
190
|
-
credentials: "include",
|
|
191
189
|
});
|
|
192
190
|
}
|
|
193
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Executes the given transaction.
|
|
194
|
+
*
|
|
195
|
+
* If the {@link Transaction} object contains binaries, the request is automatically
|
|
196
|
+
* sent using `multipart/form-data` to allow uploading binaries.
|
|
197
|
+
*
|
|
198
|
+
* @param transaction Transaction to execute
|
|
199
|
+
* @returns A promise returning the transaction result on success
|
|
200
|
+
*/
|
|
194
201
|
async rql(transaction: Transaction): Promise<TransactionResult> {
|
|
202
|
+
const binaries = getBinariesFromTransaction(transaction);
|
|
195
203
|
try {
|
|
196
|
-
if (
|
|
197
|
-
return await this.multipartTransaction(transaction);
|
|
204
|
+
if (binaries.length > 0) {
|
|
205
|
+
return await this.multipartTransaction(transaction, binaries);
|
|
198
206
|
} else {
|
|
199
207
|
return await this.jsonTransaction(transaction);
|
|
200
208
|
}
|
|
@@ -204,44 +212,42 @@ export default class Api {
|
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
private getTransactionParams(transaction: Transaction) {
|
|
207
|
-
return transaction.queries.map((
|
|
208
|
-
query:
|
|
209
|
-
params:
|
|
215
|
+
return transaction.queries.map((rqlQuery) => ({
|
|
216
|
+
query: rqlQuery.rql,
|
|
217
|
+
params: serializeQueryParams(transaction, rqlQuery.params),
|
|
210
218
|
}));
|
|
211
219
|
}
|
|
212
220
|
|
|
213
|
-
private async multipartTransaction(
|
|
221
|
+
private async multipartTransaction(
|
|
222
|
+
transaction: Transaction,
|
|
223
|
+
binaries: RQLBinary[]
|
|
224
|
+
) {
|
|
214
225
|
const jsonParams = this.getTransactionParams(transaction);
|
|
215
226
|
const formData = new FormData();
|
|
216
227
|
formData.append("queries", JSON.stringify(jsonParams));
|
|
217
|
-
|
|
218
|
-
formData.append(
|
|
228
|
+
binaries.forEach((binary) => {
|
|
229
|
+
formData.append(binary.uuid, binary.blob);
|
|
219
230
|
});
|
|
220
231
|
|
|
221
|
-
const resultSets = await nonNullFetchApi<
|
|
232
|
+
const resultSets = await this.nonNullFetchApi<RawTransactionResult>(
|
|
222
233
|
this._rqlApiUrl,
|
|
223
234
|
{
|
|
224
235
|
method: "POST",
|
|
225
|
-
credentials: "include",
|
|
226
236
|
body: formData,
|
|
227
|
-
headers: {
|
|
228
|
-
"Content-Type": "multipart/form-data",
|
|
229
|
-
},
|
|
230
237
|
}
|
|
231
238
|
);
|
|
232
|
-
return new TransactionResult(resultSets);
|
|
239
|
+
return new TransactionResult(transaction, resultSets);
|
|
233
240
|
}
|
|
234
241
|
|
|
235
242
|
private async jsonTransaction(transaction: Transaction) {
|
|
236
|
-
const resultSets = await nonNullFetchApi<
|
|
243
|
+
const resultSets = await this.nonNullFetchApi<RawTransactionResult>(
|
|
237
244
|
this._rqlApiUrl,
|
|
238
245
|
{
|
|
239
246
|
method: "POST",
|
|
240
|
-
credentials: "include",
|
|
241
247
|
body: JSON.stringify(this.getTransactionParams(transaction)),
|
|
242
248
|
}
|
|
243
249
|
);
|
|
244
|
-
return new TransactionResult(resultSets);
|
|
250
|
+
return new TransactionResult(transaction, resultSets);
|
|
245
251
|
}
|
|
246
252
|
|
|
247
253
|
private handleUserErrors(error: unknown): never {
|
|
@@ -260,79 +266,96 @@ export default class Api {
|
|
|
260
266
|
}
|
|
261
267
|
throw error;
|
|
262
268
|
}
|
|
263
|
-
}
|
|
264
269
|
|
|
265
|
-
/**
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
async
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
throw error;
|
|
270
|
+
/**
|
|
271
|
+
* Behaves the same as `fetchAPI` but forbids null responses.
|
|
272
|
+
* If this function detects a null response from the API,
|
|
273
|
+
* the promise will be rejected with a 500 error code.
|
|
274
|
+
*
|
|
275
|
+
* @param input The API route to use
|
|
276
|
+
* @param init Additional request parameters
|
|
277
|
+
* @returns A promise with the non-null fetched result
|
|
278
|
+
*/
|
|
279
|
+
private async nonNullFetchApi<R = unknown>(url: string, init: RequestInit) {
|
|
280
|
+
const result = await this.fetchApi<R>(url, init);
|
|
281
|
+
if (result == null) {
|
|
282
|
+
const error: ApiErrorResponse = {
|
|
283
|
+
status: 500,
|
|
284
|
+
title: "Unexpected null response",
|
|
285
|
+
message: "Response was null but a value was expected",
|
|
286
|
+
};
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
286
290
|
}
|
|
287
|
-
return result;
|
|
288
|
-
}
|
|
289
291
|
|
|
290
|
-
/**
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
async
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
): Promise<R | null> {
|
|
303
|
-
|
|
304
|
-
if (init?.method && init.method !== "GET") {
|
|
305
|
-
headers = {
|
|
306
|
-
"Content-Type": "application/json;charset=UTF-8",
|
|
307
|
-
// CSRF custom header necessary for POST requests
|
|
308
|
-
"X-Client-Name": "XMLHttpRequest",
|
|
309
|
-
...init?.headers,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
292
|
+
/**
|
|
293
|
+
* Sends a request to the given API route with the `Content-Type` header set and waits for a response.
|
|
294
|
+
* The returned promise will be rejected if the API returns HTTP error codes.
|
|
295
|
+
* The rejected promise will contain an `ApiErrorResponse` object.
|
|
296
|
+
*
|
|
297
|
+
* @param input The API route to use
|
|
298
|
+
* @param init Additional request parameters
|
|
299
|
+
* @returns A promise with the fetched result
|
|
300
|
+
*/
|
|
301
|
+
private async fetchApi<R = unknown>(
|
|
302
|
+
url: string,
|
|
303
|
+
init: RequestInit
|
|
304
|
+
): Promise<R | null> {
|
|
305
|
+
const headers = new Headers(init.headers);
|
|
312
306
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (status >= 200 && status < 300) {
|
|
319
|
-
if (status === 204) {
|
|
320
|
-
return null;
|
|
307
|
+
// Add custom header used as CSRF protection
|
|
308
|
+
// note: it is only necessary for non GET requests but can also be useful
|
|
309
|
+
// while debugging.
|
|
310
|
+
if (!headers.has("X-Client-Name")) {
|
|
311
|
+
headers.append("X-Client-Name", "XMLHttpRequest");
|
|
321
312
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
313
|
+
|
|
314
|
+
// Add a "Content-Type" header whenever a body is passed.
|
|
315
|
+
// The header might already have been set (either by the caller of this
|
|
316
|
+
// function or by and encoder)
|
|
317
|
+
if (
|
|
318
|
+
init.body &&
|
|
319
|
+
!headers.has("Content-Type") &&
|
|
320
|
+
!(init.body instanceof FormData)
|
|
321
|
+
) {
|
|
322
|
+
headers.append("Content-Type", "application/json;charset=UTF-8");
|
|
327
323
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
324
|
+
init.headers = headers;
|
|
325
|
+
// Authenticate request
|
|
326
|
+
if (this._authenticator) {
|
|
327
|
+
const authenticatedRequest = await this._authenticator.authenticate(
|
|
328
|
+
url,
|
|
329
|
+
init
|
|
330
|
+
);
|
|
331
|
+
url = authenticatedRequest.url;
|
|
332
|
+
init = authenticatedRequest.init;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Execute request
|
|
336
|
+
const response = await fetch(url, init);
|
|
337
|
+
|
|
338
|
+
// Parse response body and handle errors
|
|
339
|
+
const status = response.status;
|
|
340
|
+
if (status >= 200 && status < 300) {
|
|
341
|
+
if (status === 204) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
return await response.json();
|
|
346
|
+
} catch (e) {
|
|
347
|
+
console.error(e);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
let json;
|
|
352
|
+
try {
|
|
353
|
+
json = await response.json();
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.error(e);
|
|
356
|
+
throw { status, title: "Error", message: "" };
|
|
357
|
+
}
|
|
358
|
+
throw { status, ...json };
|
|
335
359
|
}
|
|
336
|
-
throw { status, ...json };
|
|
337
360
|
}
|
|
338
361
|
}
|
package/src/api/Client.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { Authenticator } from "../Authenticator.js";
|
|
2
|
+
import { Schema } from "../schema/classes/Schema.js";
|
|
3
|
+
import Api, { RQLQueryParams, CurrentUser } from "./Api.js";
|
|
4
|
+
import { ResultSet } from "./ResultSet.js";
|
|
5
|
+
import { RQLQuery } from "./query/RQLQuery.js";
|
|
6
|
+
import { Transaction } from "./transaction/Transaction.js";
|
|
7
|
+
import { TransactionResult } from "./transaction/TransactionResult.js";
|
|
8
|
+
|
|
9
|
+
export interface ClientOptions {
|
|
10
|
+
authenticator?: Authenticator;
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
/**
|
|
6
14
|
* Class used to communicate with a CubicWeb instance's API.
|
|
@@ -9,7 +17,6 @@ import { BinariesParam, Transaction, TransactionResult } from "./Transaction";
|
|
|
9
17
|
*/
|
|
10
18
|
export default class Client {
|
|
11
19
|
private api: Api;
|
|
12
|
-
|
|
13
20
|
/**
|
|
14
21
|
* Class used to communicate with a CubicWeb instance's API.
|
|
15
22
|
*
|
|
@@ -17,8 +24,8 @@ export default class Client {
|
|
|
17
24
|
* Example: if you can retrieve the schema with https://example.com/api/v1/schema,
|
|
18
25
|
* use https://example.com/api as `apiUrl`.
|
|
19
26
|
*/
|
|
20
|
-
constructor(apiUrl: string) {
|
|
21
|
-
this.api = new Api(apiUrl);
|
|
27
|
+
constructor(apiUrl: string, options?: ClientOptions) {
|
|
28
|
+
this.api = new Api(apiUrl, options ?? {});
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
get apiUrl() {
|
|
@@ -28,20 +35,19 @@ export default class Client {
|
|
|
28
35
|
/**
|
|
29
36
|
* Executes the given query and waits for the response.
|
|
30
37
|
*
|
|
31
|
-
*
|
|
38
|
+
* If binaries are given, the request is automatically
|
|
39
|
+
* sent using `multipart/form-data` to allow uploading binaries.
|
|
40
|
+
*
|
|
41
|
+
* @param rql The RQL query to send
|
|
32
42
|
* @param params The additional parameters for the request
|
|
33
43
|
* @returns A promise with the request's result set
|
|
34
44
|
*/
|
|
35
|
-
async execute(
|
|
36
|
-
query: string,
|
|
37
|
-
params: RQLParams = {},
|
|
38
|
-
binaries?: BinariesParam
|
|
39
|
-
): Promise<ResultSet> {
|
|
45
|
+
async execute(rql: string, params: RQLQueryParams = {}): Promise<ResultSet> {
|
|
40
46
|
const transaction = new Transaction();
|
|
41
|
-
const
|
|
42
|
-
transaction.
|
|
47
|
+
const query = new RQLQuery(rql, params);
|
|
48
|
+
transaction.push(query);
|
|
43
49
|
const result = await this.executeTransaction(transaction);
|
|
44
|
-
return result.
|
|
50
|
+
return result.getResultSet(query);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
/**
|
|
@@ -58,7 +64,7 @@ export default class Client {
|
|
|
58
64
|
* Tries to log in the user on the configured CubicWeb instance with the given credentials.
|
|
59
65
|
*
|
|
60
66
|
* @param login The user login
|
|
61
|
-
* @param password
|
|
67
|
+
* @param password The user password
|
|
62
68
|
* @returns A promise returning true if the login was successful, false otherwise
|
|
63
69
|
*/
|
|
64
70
|
async login(login: string, password: string) {
|
|
@@ -84,8 +90,13 @@ export default class Client {
|
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
/**
|
|
93
|
+
* Executes the given transaction.
|
|
94
|
+
*
|
|
95
|
+
* If the {@link Transaction} object contains binaries, the request is automatically
|
|
96
|
+
* sent using `multipart/form-data` to allow uploading binaries.
|
|
97
|
+
*
|
|
87
98
|
* @param transaction Transaction to execute
|
|
88
|
-
* @returns A promise
|
|
99
|
+
* @returns A promise returning the transaction result on success
|
|
89
100
|
*/
|
|
90
101
|
async executeTransaction(
|
|
91
102
|
transaction: Transaction
|
|
@@ -96,7 +107,9 @@ export default class Client {
|
|
|
96
107
|
/**
|
|
97
108
|
* Get the binary for the given entity attribute
|
|
98
109
|
*
|
|
99
|
-
* @
|
|
110
|
+
* @param eid Entity eid to fetch the binary from
|
|
111
|
+
* @param attribute Attribute on the entity in which the binary is stored
|
|
112
|
+
* @returns A promise resolving to the binary as a Blob
|
|
100
113
|
*/
|
|
101
114
|
async getBinary(eid: number, attribute: string) {
|
|
102
115
|
return await this.api.binary(eid, attribute);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface representing the json returned by the API for a single result set
|
|
3
|
+
*
|
|
4
|
+
* @category Api
|
|
5
|
+
*/
|
|
6
|
+
export interface RawResultSet {
|
|
7
|
+
readonly rows: ResultSetCell[][];
|
|
8
|
+
readonly column_names: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interface representing the json returned by the API for a single row in a result set
|
|
13
|
+
*
|
|
14
|
+
* @category Api
|
|
15
|
+
*/
|
|
16
|
+
export type RawResultSetRow = ResultSetCell[];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Type representing a final value in a result set.
|
|
20
|
+
*
|
|
21
|
+
* Please not this type may not be accurate if you use entity methods,
|
|
22
|
+
* as those methods may return any data structure.
|
|
23
|
+
*
|
|
24
|
+
* @category Api
|
|
25
|
+
*/
|
|
26
|
+
export type ResultSetCell = string | number | boolean | null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Class representing a row returned in a result set.
|
|
30
|
+
*
|
|
31
|
+
* @category Api
|
|
32
|
+
*/
|
|
33
|
+
export class ResultSetRow implements Iterable<ResultSetCell> {
|
|
34
|
+
constructor(
|
|
35
|
+
public readonly columnNames: string[],
|
|
36
|
+
private readonly cells: RawResultSetRow
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
*[Symbol.iterator]() {
|
|
40
|
+
for (const cell of this.cells) {
|
|
41
|
+
yield cell;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get length() {
|
|
46
|
+
return this.cells.length;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getCell(column: number | string) {
|
|
50
|
+
if (typeof column === "number") {
|
|
51
|
+
return this.cells[column];
|
|
52
|
+
} else {
|
|
53
|
+
const columnIndex = this.columnNames.indexOf(column);
|
|
54
|
+
return this.cells[columnIndex];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Class representing a RQL request result.
|
|
61
|
+
*
|
|
62
|
+
* @category Api
|
|
63
|
+
*/
|
|
64
|
+
export class ResultSet {
|
|
65
|
+
public readonly columnNames: string[];
|
|
66
|
+
readonly #rows: RawResultSetRow[];
|
|
67
|
+
|
|
68
|
+
constructor(rawResultSet: RawResultSet) {
|
|
69
|
+
this.columnNames = rawResultSet.column_names;
|
|
70
|
+
this.#rows = rawResultSet.rows;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
*[Symbol.iterator]() {
|
|
74
|
+
for (const r of this.#rows) {
|
|
75
|
+
yield new ResultSetRow(this.columnNames, r);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get length() {
|
|
80
|
+
return this.#rows.length;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getRow(row: number): ResultSetRow {
|
|
84
|
+
return new ResultSetRow(this.columnNames, this.#rows[row]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getCell(row: number, column: number | string): ResultSetCell {
|
|
88
|
+
if (typeof column === "number") {
|
|
89
|
+
return this.#rows[row][column];
|
|
90
|
+
} else {
|
|
91
|
+
const columnIndex = this.columnNames.indexOf(column);
|
|
92
|
+
return this.#rows[row][columnIndex];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AuthenticatedRequest, Authenticator } from "../../Authenticator.js";
|
|
2
|
+
|
|
3
|
+
export interface CookieAuthenticatorOptions {
|
|
4
|
+
credentials?: RequestCredentials;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class CookieAuthenticator implements Authenticator {
|
|
8
|
+
constructor(private options?: CookieAuthenticatorOptions) {}
|
|
9
|
+
|
|
10
|
+
async authenticate(
|
|
11
|
+
url: string,
|
|
12
|
+
init: RequestInit
|
|
13
|
+
): Promise<AuthenticatedRequest> {
|
|
14
|
+
const credentials = this.options?.credentials;
|
|
15
|
+
return { url, init: { ...init, credentials } };
|
|
16
|
+
}
|
|
17
|
+
}
|