@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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { AuthenticatedRequest, Authenticator } from "../../Authenticator.js";
|
|
2
|
+
import { encodeFormData } from "../../encodeFormData.js";
|
|
3
|
+
|
|
4
|
+
export interface SignedRequestAuthToken {
|
|
5
|
+
/**
|
|
6
|
+
* id of the token
|
|
7
|
+
*/
|
|
8
|
+
id: string;
|
|
9
|
+
/**
|
|
10
|
+
* secret value of the token
|
|
11
|
+
*/
|
|
12
|
+
token: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function bytesToHex(hashBuffer: ArrayBuffer) {
|
|
16
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
|
|
17
|
+
const hashHex = hashArray
|
|
18
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
19
|
+
.join(""); // convert bytes to hex string
|
|
20
|
+
return hashHex;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Authenticator allowing to authenticate requests made to a cubicweb instance
|
|
25
|
+
* with the [signedrequest](https://forge.extranet.logilab.fr/cubicweb/cubes/signedrequest) cube (v3.x).
|
|
26
|
+
*
|
|
27
|
+
* @param token A {@link SignedRequestAuthToken}. This token must be enabled.
|
|
28
|
+
*/
|
|
29
|
+
export class SignedRequestAuthenticator implements Authenticator {
|
|
30
|
+
private cachedCryptoKey?: CryptoKey;
|
|
31
|
+
|
|
32
|
+
constructor(private token: SignedRequestAuthToken) {}
|
|
33
|
+
|
|
34
|
+
private async digestBody(body: Uint8Array): Promise<string> {
|
|
35
|
+
const contentHash = await crypto.subtle.digest("SHA-512", body);
|
|
36
|
+
const hashHex = bytesToHex(contentHash);
|
|
37
|
+
return hashHex;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private async encodeBody(
|
|
41
|
+
body: RequestInit["body"]
|
|
42
|
+
): Promise<{ body: Uint8Array | null; headers: Headers }> {
|
|
43
|
+
if (typeof body === "string") {
|
|
44
|
+
return {
|
|
45
|
+
body: new TextEncoder().encode(body),
|
|
46
|
+
headers: new Headers(),
|
|
47
|
+
};
|
|
48
|
+
} else if (body instanceof FormData) {
|
|
49
|
+
return await encodeFormData(body);
|
|
50
|
+
} else if (body == null) {
|
|
51
|
+
return {
|
|
52
|
+
body: null,
|
|
53
|
+
headers: new Headers(),
|
|
54
|
+
};
|
|
55
|
+
} else {
|
|
56
|
+
throw new Error("Unexpected body type.");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async getCryptoKey() {
|
|
61
|
+
if (this.cachedCryptoKey) {
|
|
62
|
+
return this.cachedCryptoKey;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const textEncoder = new TextEncoder();
|
|
66
|
+
this.cachedCryptoKey = await crypto.subtle.importKey(
|
|
67
|
+
"raw",
|
|
68
|
+
textEncoder.encode(this.token.token),
|
|
69
|
+
{
|
|
70
|
+
name: "HMAC",
|
|
71
|
+
hash: "SHA-512",
|
|
72
|
+
},
|
|
73
|
+
false,
|
|
74
|
+
["sign"]
|
|
75
|
+
);
|
|
76
|
+
return this.cachedCryptoKey;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compute headers used to sign the request and encode the content as bytes
|
|
81
|
+
*/
|
|
82
|
+
private async prepareRequest(
|
|
83
|
+
url: string,
|
|
84
|
+
init: RequestInit
|
|
85
|
+
): Promise<{ contentToSign: string; headers: Headers }> {
|
|
86
|
+
const headers = new Headers(init?.headers);
|
|
87
|
+
const { body: encodedBody, headers: encodingHeaders } =
|
|
88
|
+
await this.encodeBody(init.body || null);
|
|
89
|
+
init.body = encodedBody;
|
|
90
|
+
encodingHeaders.forEach((value, name) => headers.append(name, value));
|
|
91
|
+
|
|
92
|
+
const bodyBytes = encodedBody ?? new Uint8Array();
|
|
93
|
+
|
|
94
|
+
const bodyHash = await this.digestBody(bodyBytes);
|
|
95
|
+
headers.append("Content-SHA512", bodyHash);
|
|
96
|
+
|
|
97
|
+
const date = new Date();
|
|
98
|
+
headers.append("X-Cubicweb-Date", date.toUTCString());
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
contentToSign:
|
|
102
|
+
(init?.method ?? "GET") +
|
|
103
|
+
url +
|
|
104
|
+
bodyHash +
|
|
105
|
+
(headers.get("Content-Type") ?? "") +
|
|
106
|
+
(headers.get("X-Cubicweb-Date") ?? ""),
|
|
107
|
+
headers,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private async signContent(contentToSign: string): Promise<ArrayBuffer> {
|
|
112
|
+
const textEncoder = new TextEncoder();
|
|
113
|
+
return await crypto.subtle.sign(
|
|
114
|
+
"HMAC",
|
|
115
|
+
await this.getCryptoKey(),
|
|
116
|
+
textEncoder.encode(contentToSign)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async authenticate(
|
|
121
|
+
url: string,
|
|
122
|
+
init: RequestInit
|
|
123
|
+
): Promise<AuthenticatedRequest> {
|
|
124
|
+
const { contentToSign, headers } = await this.prepareRequest(url, init);
|
|
125
|
+
headers.append(
|
|
126
|
+
"Authorization",
|
|
127
|
+
`Cubicweb ${this.token.id}:${bytesToHex(
|
|
128
|
+
await this.signContent(contentToSign)
|
|
129
|
+
)}`
|
|
130
|
+
);
|
|
131
|
+
return { url, init: { ...init, headers } };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { RQLQueryCellRef, RQLQueryRowRef } from "./references.js";
|
|
3
|
+
import { RQLQueryParams } from "../Api.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Class representing a RQL query to be sent to the API.
|
|
7
|
+
*
|
|
8
|
+
* Queries are identified by a uuid and are independent from transactions.
|
|
9
|
+
*
|
|
10
|
+
* If a query has been used in a transaction, RQLQuery objects can easily retrieve
|
|
11
|
+
* a reference to the query for later resolving the values in a {@link TransactionResult}.
|
|
12
|
+
*
|
|
13
|
+
* @category Api
|
|
14
|
+
*/
|
|
15
|
+
export class RQLQuery {
|
|
16
|
+
public readonly queryUUID: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Class representing a RQL query to be sent to the API.
|
|
20
|
+
*
|
|
21
|
+
* Queries are identified by a uuid and are independent from transactions.
|
|
22
|
+
*
|
|
23
|
+
* If a query has been used in a transaction, RQLQuery objects can easily retrieve
|
|
24
|
+
* a reference to the query for later resolving the values in a {@link TransactionResult}.
|
|
25
|
+
*
|
|
26
|
+
* @param rql The rql query
|
|
27
|
+
* @param params The parameters for the rql query
|
|
28
|
+
*
|
|
29
|
+
* @category Api
|
|
30
|
+
*/
|
|
31
|
+
constructor(
|
|
32
|
+
public readonly rql: string,
|
|
33
|
+
public readonly params: RQLQueryParams = {}
|
|
34
|
+
) {
|
|
35
|
+
this.queryUUID = uuidv4();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @returns A reference to a {@link ResultSetRow} in the result of this query.
|
|
40
|
+
*/
|
|
41
|
+
rowRef(row: number): RQLQueryRowRef {
|
|
42
|
+
return new RQLQueryRowRef(this.queryUUID, row);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @returns A reference to a {@link ResultSetCell} in the result of this query.
|
|
47
|
+
*/
|
|
48
|
+
cellRef(row: number, column: number | string): RQLQueryCellRef {
|
|
49
|
+
return new RQLQueryCellRef(this.queryUUID, row, column);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class representing a reference to a {@link ResultSetRow} in the result of a {@link RQLQuery}.
|
|
3
|
+
*
|
|
4
|
+
* Use this to resolve a {@link ResultSetRow} of the referenced {@link RQLQuery}
|
|
5
|
+
* from a {@link TransactionResult}.
|
|
6
|
+
*
|
|
7
|
+
* @category Api
|
|
8
|
+
*/
|
|
9
|
+
export class RQLQueryRowRef {
|
|
10
|
+
constructor(public readonly queryUUID: string, public readonly row: number) {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Retrieve a reference to the given column in this row.
|
|
14
|
+
*
|
|
15
|
+
* @param c The column to get a reference for
|
|
16
|
+
* @returns A reference to the given column in the current row
|
|
17
|
+
*
|
|
18
|
+
* @category Api
|
|
19
|
+
*/
|
|
20
|
+
column(c: number | string): RQLQueryCellRef {
|
|
21
|
+
return new RQLQueryCellRef(this.queryUUID, this.row, c);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Class representing a reference to a {@link ResultSetCell} in the result of a {@link RQLQuery}.
|
|
27
|
+
*
|
|
28
|
+
* Use this to resolve a {@link ResultSetCell} of the referenced {@link RQLQuery}
|
|
29
|
+
* from a {@link TransactionResult}.
|
|
30
|
+
*
|
|
31
|
+
* This reference can also be used in parameters of other {@link RQLQuery}.
|
|
32
|
+
* If both {@link RQLQuery} are used in the same {@link RQLQuery},
|
|
33
|
+
* then the resolved value from the first query will be used in the second.
|
|
34
|
+
*
|
|
35
|
+
* @category Api
|
|
36
|
+
*/
|
|
37
|
+
export class RQLQueryCellRef {
|
|
38
|
+
constructor(
|
|
39
|
+
public readonly queryUUID: string,
|
|
40
|
+
public readonly row: number,
|
|
41
|
+
public readonly column: number | string
|
|
42
|
+
) {}
|
|
43
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RQLQuery } from "../query/RQLQuery.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Class representing a set of {@link RQLQuery} executed one after the other in a single HTTP request.
|
|
5
|
+
*
|
|
6
|
+
* These {@link RQLQuery} can be linked together by using a {@link RQLQueryCellRef}
|
|
7
|
+
* of one {@link RQLQuery} in the parameters of a subsequent {@link RQLQuery}.
|
|
8
|
+
*
|
|
9
|
+
* Binary files are global to the transaction and can be referenced using a {@link RQLQueryBinaryRef}
|
|
10
|
+
* in the parameters of a {@link RQLQuery}.
|
|
11
|
+
*/
|
|
12
|
+
export class Transaction {
|
|
13
|
+
private _queries: Array<RQLQuery> = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Adds the given queries to the transaction.
|
|
17
|
+
*
|
|
18
|
+
* Each {@link RQLQuery} will be executed one after the other.
|
|
19
|
+
* These {@link RQLQuery} can be linked together by using a {@link RQLQueryCellRef}
|
|
20
|
+
* of one {@link RQLQuery} in the parameters of a subsequent {@link RQLQuery}.
|
|
21
|
+
*
|
|
22
|
+
* @param queries New queries to add to the transaction
|
|
23
|
+
*/
|
|
24
|
+
push(...queries: RQLQuery[]): void {
|
|
25
|
+
this._queries.push(...queries);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get queries(): ReadonlyArray<RQLQuery> {
|
|
29
|
+
return this._queries;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { getQueryIndexInTransaction } from "../../utils.js";
|
|
2
|
+
import {
|
|
3
|
+
RawResultSet,
|
|
4
|
+
ResultSet,
|
|
5
|
+
ResultSetCell,
|
|
6
|
+
ResultSetRow,
|
|
7
|
+
} from "../ResultSet.js";
|
|
8
|
+
import { RQLQuery } from "../query/RQLQuery.js";
|
|
9
|
+
import { RQLQueryCellRef, RQLQueryRowRef } from "../query/references.js";
|
|
10
|
+
import { Transaction } from "./Transaction.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Interface representing the json returned by the API when executing
|
|
14
|
+
* a Transaction
|
|
15
|
+
*
|
|
16
|
+
* @category Api
|
|
17
|
+
*/
|
|
18
|
+
export interface RawTransactionResult {
|
|
19
|
+
readonly result_sets: RawResultSet[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Class representing the result of a transaction.
|
|
24
|
+
*
|
|
25
|
+
* Retrieving the values is done using query references.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
*
|
|
29
|
+
* Retrieving the first value in a result set
|
|
30
|
+
*
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const transaction = new Transaction()
|
|
33
|
+
* const query = new RQLQuery("Any X WHERE X name 'test'", {})
|
|
34
|
+
* transaction.push(query)
|
|
35
|
+
* const result = client.executeTransaction(transaction)
|
|
36
|
+
* const eid = result.resolve(query.cellRef(0, 0))
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @category Api
|
|
40
|
+
*/
|
|
41
|
+
export class TransactionResult {
|
|
42
|
+
private rawResultSets: RawResultSet[];
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
private transaction: Transaction,
|
|
46
|
+
rawTransactionResult: RawTransactionResult
|
|
47
|
+
) {
|
|
48
|
+
this.rawResultSets = rawTransactionResult.result_sets;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getResultSet(query: RQLQuery): ResultSet {
|
|
52
|
+
const index = getQueryIndexInTransaction(this.transaction, query.queryUUID);
|
|
53
|
+
const resultSet = new ResultSet(this.rawResultSets[index]);
|
|
54
|
+
return resultSet;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getRow(ref: RQLQueryRowRef): ResultSetRow {
|
|
58
|
+
const index = getQueryIndexInTransaction(this.transaction, ref.queryUUID);
|
|
59
|
+
const resultSet = new ResultSet(this.rawResultSets[index]);
|
|
60
|
+
return resultSet.getRow(ref.row);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getCell(ref: RQLQueryCellRef): ResultSetCell {
|
|
64
|
+
const index = getQueryIndexInTransaction(this.transaction, ref.queryUUID);
|
|
65
|
+
const resultSet = new ResultSet(this.rawResultSets[index]);
|
|
66
|
+
return resultSet.getCell(ref.row, ref.column);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Adapted from https://github.com/axios/axios/blob/c6cce43cd94489f655f4488c5a50ecaf781c94f2/lib/helpers/formDataToStream.js#L92
|
|
2
|
+
// See https://andreubotella.github.io/multipart-form-data/ for spec and algorithm description
|
|
3
|
+
|
|
4
|
+
const ALPHA = "abcdefghijklmnopqrstuvwxyz";
|
|
5
|
+
const BOUNDARY_ALPHABET = ALPHA + ALPHA.toUpperCase() + "0123456789" + "-_";
|
|
6
|
+
|
|
7
|
+
const textEncoder = new TextEncoder();
|
|
8
|
+
|
|
9
|
+
const CRLF = "\r\n";
|
|
10
|
+
const CRLF_BYTES = textEncoder.encode(CRLF);
|
|
11
|
+
const CRLF_BYTES_COUNT = 2;
|
|
12
|
+
|
|
13
|
+
class FormDataPart {
|
|
14
|
+
private headers: Uint8Array;
|
|
15
|
+
private contentLength: number;
|
|
16
|
+
public readonly size: number;
|
|
17
|
+
name: string;
|
|
18
|
+
value: Uint8Array | File;
|
|
19
|
+
constructor(name: string, value: FormDataEntryValue) {
|
|
20
|
+
const { escapeName } = FormDataPart;
|
|
21
|
+
|
|
22
|
+
let headers = `Content-Disposition: form-data; name="${escapeName(name)}"${
|
|
23
|
+
!(typeof value === "string") && value.name
|
|
24
|
+
? `; filename="${escapeName(value.name)}"`
|
|
25
|
+
: ""
|
|
26
|
+
}${CRLF}`;
|
|
27
|
+
let bytesOrFile: Uint8Array | File;
|
|
28
|
+
if (typeof value === "string") {
|
|
29
|
+
bytesOrFile = textEncoder.encode(
|
|
30
|
+
String(value).replace(/\r?\n|\r\n?/g, CRLF)
|
|
31
|
+
);
|
|
32
|
+
this.contentLength = bytesOrFile.byteLength;
|
|
33
|
+
} else {
|
|
34
|
+
bytesOrFile = value;
|
|
35
|
+
headers += `Content-Type: ${
|
|
36
|
+
value.type || "application/octet-stream"
|
|
37
|
+
}${CRLF}`;
|
|
38
|
+
this.contentLength = bytesOrFile.size;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.headers = textEncoder.encode(headers + CRLF);
|
|
42
|
+
|
|
43
|
+
this.size = this.headers.byteLength + this.contentLength + CRLF_BYTES_COUNT;
|
|
44
|
+
|
|
45
|
+
this.name = name;
|
|
46
|
+
this.value = bytesOrFile;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async *encode(): AsyncGenerator<Uint8Array> {
|
|
50
|
+
yield this.headers;
|
|
51
|
+
|
|
52
|
+
const { value } = this;
|
|
53
|
+
|
|
54
|
+
if (value instanceof Uint8Array) {
|
|
55
|
+
yield value;
|
|
56
|
+
} else {
|
|
57
|
+
yield new Uint8Array(await value.arrayBuffer());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
yield CRLF_BYTES;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static escapeName(name: string): string {
|
|
64
|
+
return String(name).replace(
|
|
65
|
+
/[\r\n"]/g,
|
|
66
|
+
(match) =>
|
|
67
|
+
({
|
|
68
|
+
"\r": "%0D",
|
|
69
|
+
"\n": "%0A",
|
|
70
|
+
'"': "%22",
|
|
71
|
+
}[match as "\r" | "\n" | '"'])
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function generateBoundary(): string {
|
|
77
|
+
let str = "";
|
|
78
|
+
const { length } = BOUNDARY_ALPHABET;
|
|
79
|
+
let size = 25;
|
|
80
|
+
while (size--) {
|
|
81
|
+
str += BOUNDARY_ALPHABET[(Math.random() * length) | 0];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return str;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function encodeFormData(body: FormData) {
|
|
88
|
+
const tag = "form-data-boundary";
|
|
89
|
+
const boundary = tag + "-" + generateBoundary();
|
|
90
|
+
|
|
91
|
+
const boundaryBytes = textEncoder.encode("--" + boundary + CRLF);
|
|
92
|
+
const footerBytes = textEncoder.encode("--" + boundary + "--" + CRLF + CRLF);
|
|
93
|
+
let contentLength = footerBytes.byteLength;
|
|
94
|
+
|
|
95
|
+
const parts = Array.from(body.entries()).map(([name, value]) => {
|
|
96
|
+
const part = new FormDataPart(name, value);
|
|
97
|
+
contentLength += part.size;
|
|
98
|
+
return part;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
contentLength += boundaryBytes.byteLength * parts.length;
|
|
102
|
+
|
|
103
|
+
const computedHeaders = new Headers({
|
|
104
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (Number.isFinite(contentLength)) {
|
|
108
|
+
computedHeaders.append("Content-Length", contentLength.toString());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const fullContent = new Uint8Array(contentLength);
|
|
112
|
+
let offset = 0;
|
|
113
|
+
|
|
114
|
+
for (const part of parts) {
|
|
115
|
+
fullContent.set(boundaryBytes, offset);
|
|
116
|
+
offset += boundaryBytes.length;
|
|
117
|
+
|
|
118
|
+
for await (const arr of part.encode()) {
|
|
119
|
+
fullContent.set(arr, offset);
|
|
120
|
+
offset += arr.length;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
fullContent.set(footerBytes, offset);
|
|
124
|
+
|
|
125
|
+
return { body: fullContent, headers: computedHeaders };
|
|
126
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* There was an error processing the CubicWeb schema.
|
|
3
|
+
*/
|
|
1
4
|
export class InconsistentSchemaError extends Error {
|
|
2
5
|
constructor(message: string) {
|
|
3
6
|
super(`Inconsistent schema: ${message}`);
|
|
@@ -5,16 +8,12 @@ export class InconsistentSchemaError extends Error {
|
|
|
5
8
|
}
|
|
6
9
|
}
|
|
7
10
|
|
|
11
|
+
/**
|
|
12
|
+
* There was an error while validating the data sent to the server.
|
|
13
|
+
*/
|
|
8
14
|
export class ValidationError extends Error {
|
|
9
15
|
constructor(message: string) {
|
|
10
16
|
super(message);
|
|
11
17
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
12
18
|
}
|
|
13
19
|
}
|
|
14
|
-
|
|
15
|
-
export class TransactionClosedError extends Error {
|
|
16
|
-
constructor() {
|
|
17
|
-
super("Trying to use a closed transaction");
|
|
18
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
19
|
-
}
|
|
20
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
// Raw Schema
|
|
2
|
-
export * from "./schema/raw/Constraints";
|
|
3
|
-
export * from "./schema/raw/Entities";
|
|
4
|
-
export * from "./schema/raw/RelationDefinitions";
|
|
5
|
-
export * from "./schema/raw/Schema";
|
|
6
|
-
export * from "./schema/raw/Utils";
|
|
2
|
+
export * from "./schema/raw/Constraints.js";
|
|
3
|
+
export * from "./schema/raw/Entities.js";
|
|
4
|
+
export * from "./schema/raw/RelationDefinitions.js";
|
|
5
|
+
export * from "./schema/raw/Schema.js";
|
|
6
|
+
export * from "./schema/raw/Utils.js";
|
|
7
7
|
|
|
8
8
|
// Schema Abstraction class
|
|
9
|
-
export * from "./schema/classes/EntitySchema";
|
|
10
|
-
export * from "./schema/classes/RelationDefinitionSchema";
|
|
11
|
-
export * from "./schema/classes/Schema";
|
|
12
|
-
export * from "./schema/classes/Utils";
|
|
9
|
+
export * from "./schema/classes/EntitySchema.js";
|
|
10
|
+
export * from "./schema/classes/RelationDefinitionSchema.js";
|
|
11
|
+
export * from "./schema/classes/Schema.js";
|
|
12
|
+
export * from "./schema/classes/Utils.js";
|
|
13
13
|
|
|
14
|
-
export * from "./
|
|
14
|
+
export * from "./errors.js";
|
|
15
15
|
|
|
16
16
|
// API
|
|
17
|
-
export { default as Client } from "./api/Client";
|
|
18
|
-
export * from "./api/Transaction";
|
|
19
17
|
export {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
RqlRow,
|
|
23
|
-
ResultSet,
|
|
18
|
+
RQLQueryParamValue,
|
|
19
|
+
RQLQueryParams,
|
|
24
20
|
ApiErrorResponse,
|
|
25
|
-
|
|
21
|
+
CurrentUser,
|
|
22
|
+
} from "./api/Api.js";
|
|
23
|
+
export { ResultSet, ResultSetRow, ResultSetCell } from "./api/ResultSet.js";
|
|
24
|
+
export { default as Client, ClientOptions } from "./api/Client.js";
|
|
25
|
+
export { Transaction } from "./api/transaction/Transaction.js";
|
|
26
|
+
export { TransactionResult } from "./api/transaction/TransactionResult.js";
|
|
27
|
+
export { RQLQuery } from "./api/query/RQLQuery.js";
|
|
28
|
+
export { RQLBinary } from "./api/binary/RQLBinary.js";
|
|
29
|
+
export { RQLQueryRowRef, RQLQueryCellRef } from "./api/query/references.js";
|
|
30
|
+
|
|
31
|
+
// Authenticators
|
|
32
|
+
export { Authenticator } from "./Authenticator.js";
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { EntityRawSchemaArray } from "../raw/Entities";
|
|
2
|
-
import { RelationDefinitionRawSchemaArray } from "../raw/RelationDefinitions";
|
|
3
|
-
import { RelationDefinitionSchema } from "./RelationDefinitionSchema";
|
|
4
|
-
import { Schema } from "./Schema";
|
|
5
|
-
import {
|
|
1
|
+
import { EntityRawSchemaArray } from "../raw/Entities.js";
|
|
2
|
+
import { RelationDefinitionRawSchemaArray } from "../raw/RelationDefinitions.js";
|
|
3
|
+
import { RelationDefinitionSchema } from "./RelationDefinitionSchema.js";
|
|
4
|
+
import { Schema } from "./Schema.js";
|
|
5
|
+
import {
|
|
6
|
+
MatchedRelationDefinition,
|
|
7
|
+
RelationDefinitionMatcher,
|
|
8
|
+
} from "./Utils.js";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Wrapper around raw entity schemas from the Schema.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { InconsistentSchemaError } from "../../
|
|
2
|
-
import { EntityRawSchemaArray } from "../raw/Entities";
|
|
1
|
+
import { InconsistentSchemaError } from "../../errors.js";
|
|
2
|
+
import { EntityRawSchemaArray } from "../raw/Entities.js";
|
|
3
3
|
import {
|
|
4
4
|
RelationDefinitionRawSchemaArray,
|
|
5
5
|
Cardinality,
|
|
6
|
-
} from "../raw/RelationDefinitions";
|
|
7
|
-
import { IsLooseSchema, EntityRawSchemaByType } from "../raw/Utils";
|
|
8
|
-
import { EntitySchema } from "./EntitySchema";
|
|
9
|
-
import { Schema } from "./Schema";
|
|
10
|
-
import { EntityRole, isCardinalityRequired } from "./Utils";
|
|
6
|
+
} from "../raw/RelationDefinitions.js";
|
|
7
|
+
import { IsLooseSchema, EntityRawSchemaByType } from "../raw/Utils.js";
|
|
8
|
+
import { EntitySchema } from "./EntitySchema.js";
|
|
9
|
+
import { Schema } from "./Schema.js";
|
|
10
|
+
import { EntityRole, isCardinalityRequired } from "./Utils.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Wrapper around raw relation definition schemas from the Schema.
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import { EntityRawSchemaArray } from "../raw/Entities";
|
|
1
|
+
import { EntityRawSchemaArray } from "../raw/Entities.js";
|
|
2
2
|
import {
|
|
3
3
|
RelationDefinitionRawSchema,
|
|
4
4
|
RelationDefinitionRawSchemaArray,
|
|
5
|
-
} from "../raw/RelationDefinitions";
|
|
6
|
-
import { RawSchema } from "../raw/Schema";
|
|
5
|
+
} from "../raw/RelationDefinitions.js";
|
|
6
|
+
import { RawSchema } from "../raw/Schema.js";
|
|
7
7
|
import {
|
|
8
8
|
EntityRawSchemaByType,
|
|
9
9
|
IsETypeInStrictSchema,
|
|
10
10
|
IsLooseSchema,
|
|
11
|
-
} from "../raw/Utils";
|
|
12
|
-
import { EntitySchema } from "./EntitySchema";
|
|
13
|
-
import { RelationDefinitionSchema } from "./RelationDefinitionSchema";
|
|
14
|
-
import {
|
|
11
|
+
} from "../raw/Utils.js";
|
|
12
|
+
import { EntitySchema } from "./EntitySchema.js";
|
|
13
|
+
import { RelationDefinitionSchema } from "./RelationDefinitionSchema.js";
|
|
14
|
+
import {
|
|
15
|
+
RelationDefinitionMatcher,
|
|
16
|
+
MatchedRelationDefinition,
|
|
17
|
+
} from "./Utils.js";
|
|
15
18
|
|
|
16
19
|
const rawSchemaKey: unique symbol = Symbol("Indexer of the rawSchema");
|
|
17
20
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { EntityRawSchemaArray } from "../raw/Entities";
|
|
1
|
+
import { EntityRawSchemaArray } from "../raw/Entities.js";
|
|
2
2
|
import {
|
|
3
3
|
Cardinality,
|
|
4
4
|
RelationDefinitionRawSchema,
|
|
5
5
|
RelationDefinitionRawSchemaArray,
|
|
6
|
-
} from "../raw/RelationDefinitions";
|
|
7
|
-
import { Schema } from "./Schema";
|
|
8
|
-
import { EntitySchema } from "./EntitySchema";
|
|
6
|
+
} from "../raw/RelationDefinitions.js";
|
|
7
|
+
import { Schema } from "./Schema.js";
|
|
8
|
+
import { EntitySchema } from "./EntitySchema.js";
|
|
9
9
|
|
|
10
10
|
// This prefix is often applied to relation types as convention
|
|
11
11
|
// indicating a relation is viewed from the object's point of view
|