@lodestar/api 1.35.0-dev.f80d2d52da → 1.35.0-dev.fcf8d024ea
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/lib/beacon/client/beacon.d.ts.map +1 -0
- package/lib/beacon/client/config.d.ts.map +1 -0
- package/lib/beacon/client/debug.d.ts.map +1 -0
- package/lib/beacon/client/events.d.ts.map +1 -0
- package/lib/beacon/client/index.d.ts.map +1 -0
- package/lib/beacon/client/index.js.map +1 -1
- package/lib/beacon/client/lightclient.d.ts.map +1 -0
- package/lib/beacon/client/lodestar.d.ts.map +1 -0
- package/lib/beacon/client/node.d.ts.map +1 -0
- package/lib/beacon/client/proof.d.ts.map +1 -0
- package/lib/beacon/client/validator.d.ts.map +1 -0
- package/lib/beacon/index.d.ts +1 -1
- package/lib/beacon/index.d.ts.map +1 -0
- package/lib/beacon/index.js.map +1 -1
- package/lib/beacon/routes/beacon/block.d.ts.map +1 -0
- package/lib/beacon/routes/beacon/index.d.ts +3 -3
- package/lib/beacon/routes/beacon/index.d.ts.map +1 -0
- package/lib/beacon/routes/beacon/index.js.map +1 -1
- package/lib/beacon/routes/beacon/pool.d.ts +1 -1
- package/lib/beacon/routes/beacon/pool.d.ts.map +1 -0
- package/lib/beacon/routes/beacon/rewards.d.ts.map +1 -0
- package/lib/beacon/routes/beacon/rewards.js.map +1 -1
- package/lib/beacon/routes/beacon/state.d.ts +2 -2
- package/lib/beacon/routes/beacon/state.d.ts.map +1 -0
- package/lib/beacon/routes/config.d.ts +1 -1
- package/lib/beacon/routes/config.d.ts.map +1 -0
- package/lib/beacon/routes/debug.d.ts.map +1 -0
- package/lib/beacon/routes/events.d.ts.map +1 -0
- package/lib/beacon/routes/events.js.map +1 -1
- package/lib/beacon/routes/index.d.ts.map +1 -0
- package/lib/beacon/routes/index.js.map +1 -1
- package/lib/beacon/routes/lightclient.d.ts.map +1 -0
- package/lib/beacon/routes/lodestar.d.ts.map +1 -0
- package/lib/beacon/routes/node.d.ts.map +1 -0
- package/lib/beacon/routes/proof.d.ts.map +1 -0
- package/lib/beacon/routes/validator.d.ts +1 -1
- package/lib/beacon/routes/validator.d.ts.map +1 -0
- package/lib/beacon/server/beacon.d.ts.map +1 -0
- package/lib/beacon/server/config.d.ts.map +1 -0
- package/lib/beacon/server/debug.d.ts.map +1 -0
- package/lib/beacon/server/events.d.ts.map +1 -0
- package/lib/beacon/server/index.d.ts +1 -1
- package/lib/beacon/server/index.d.ts.map +1 -0
- package/lib/beacon/server/index.js.map +1 -1
- package/lib/beacon/server/lightclient.d.ts.map +1 -0
- package/lib/beacon/server/lodestar.d.ts.map +1 -0
- package/lib/beacon/server/node.d.ts.map +1 -0
- package/lib/beacon/server/proof.d.ts.map +1 -0
- package/lib/beacon/server/validator.d.ts.map +1 -0
- package/lib/builder/client.d.ts.map +1 -0
- package/lib/builder/index.d.ts.map +1 -0
- package/lib/builder/index.js.map +1 -1
- package/lib/builder/routes.d.ts.map +1 -0
- package/lib/builder/routes.js.map +1 -1
- package/lib/builder/server/index.d.ts +1 -1
- package/lib/builder/server/index.d.ts.map +1 -0
- package/lib/index.d.ts +6 -6
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +3 -3
- package/lib/index.js.map +1 -1
- package/lib/keymanager/client.d.ts.map +1 -0
- package/lib/keymanager/index.d.ts +2 -2
- package/lib/keymanager/index.d.ts.map +1 -0
- package/lib/keymanager/index.js +1 -2
- package/lib/keymanager/index.js.map +1 -1
- package/lib/keymanager/routes.d.ts.map +1 -0
- package/lib/keymanager/server/index.d.ts +1 -1
- package/lib/keymanager/server/index.d.ts.map +1 -0
- package/lib/server/index.d.ts.map +1 -0
- package/lib/utils/client/error.d.ts.map +1 -0
- package/lib/utils/client/error.js +2 -0
- package/lib/utils/client/error.js.map +1 -1
- package/lib/utils/client/eventSource.d.ts.map +1 -0
- package/lib/utils/client/format.d.ts.map +1 -0
- package/lib/utils/client/httpClient.d.ts +1 -2
- package/lib/utils/client/httpClient.d.ts.map +1 -0
- package/lib/utils/client/httpClient.js +13 -9
- package/lib/utils/client/httpClient.js.map +1 -1
- package/lib/utils/client/index.d.ts +1 -1
- package/lib/utils/client/index.d.ts.map +1 -0
- package/lib/utils/client/index.js +1 -1
- package/lib/utils/client/index.js.map +1 -1
- package/lib/utils/client/method.d.ts.map +1 -0
- package/lib/utils/client/metrics.d.ts.map +1 -0
- package/lib/utils/client/request.d.ts.map +1 -0
- package/lib/utils/client/response.d.ts.map +1 -0
- package/lib/utils/client/response.js +6 -0
- package/lib/utils/client/response.js.map +1 -1
- package/lib/utils/codecs.d.ts.map +1 -0
- package/lib/utils/fork.d.ts.map +1 -0
- package/lib/utils/headers.d.ts.map +1 -0
- package/lib/utils/httpStatusCode.d.ts.map +1 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/metadata.d.ts.map +1 -0
- package/lib/utils/schema.d.ts.map +1 -0
- package/lib/utils/serdes.d.ts.map +1 -0
- package/lib/utils/server/error.d.ts.map +1 -0
- package/lib/utils/server/error.js +1 -0
- package/lib/utils/server/error.js.map +1 -1
- package/lib/utils/server/handler.d.ts.map +1 -0
- package/lib/utils/server/index.d.ts.map +1 -0
- package/lib/utils/server/method.d.ts.map +1 -0
- package/lib/utils/server/parser.d.ts.map +1 -0
- package/lib/utils/server/route.d.ts.map +1 -0
- package/lib/utils/server/route.js.map +1 -1
- package/lib/utils/types.d.ts.map +1 -0
- package/lib/utils/urlFormat.d.ts.map +1 -0
- package/lib/utils/wireFormat.d.ts.map +1 -0
- package/package.json +17 -11
- package/src/beacon/client/beacon.ts +12 -0
- package/src/beacon/client/config.ts +12 -0
- package/src/beacon/client/debug.ts +12 -0
- package/src/beacon/client/events.ts +69 -0
- package/src/beacon/client/index.ts +46 -0
- package/src/beacon/client/lightclient.ts +12 -0
- package/src/beacon/client/lodestar.ts +12 -0
- package/src/beacon/client/node.ts +12 -0
- package/src/beacon/client/proof.ts +12 -0
- package/src/beacon/client/validator.ts +12 -0
- package/src/beacon/index.ts +24 -0
- package/src/beacon/routes/beacon/block.ts +602 -0
- package/src/beacon/routes/beacon/index.ts +66 -0
- package/src/beacon/routes/beacon/pool.ts +503 -0
- package/src/beacon/routes/beacon/rewards.ts +216 -0
- package/src/beacon/routes/beacon/state.ts +588 -0
- package/src/beacon/routes/config.ts +114 -0
- package/src/beacon/routes/debug.ts +231 -0
- package/src/beacon/routes/events.ts +337 -0
- package/src/beacon/routes/index.ts +33 -0
- package/src/beacon/routes/lightclient.ts +241 -0
- package/src/beacon/routes/lodestar.ts +456 -0
- package/src/beacon/routes/node.ts +286 -0
- package/src/beacon/routes/proof.ts +79 -0
- package/src/beacon/routes/validator.ts +1014 -0
- package/src/beacon/server/beacon.ts +7 -0
- package/src/beacon/server/config.ts +7 -0
- package/src/beacon/server/debug.ts +7 -0
- package/src/beacon/server/events.ts +73 -0
- package/src/beacon/server/index.ts +55 -0
- package/src/beacon/server/lightclient.ts +7 -0
- package/src/beacon/server/lodestar.ts +7 -0
- package/src/beacon/server/node.ts +7 -0
- package/src/beacon/server/proof.ts +7 -0
- package/src/beacon/server/validator.ts +7 -0
- package/src/builder/client.ts +9 -0
- package/src/builder/index.ts +26 -0
- package/src/builder/routes.ts +227 -0
- package/src/builder/server/index.ts +19 -0
- package/src/index.ts +19 -0
- package/src/keymanager/client.ts +9 -0
- package/src/keymanager/index.ts +39 -0
- package/src/keymanager/routes.ts +699 -0
- package/src/keymanager/server/index.ts +19 -0
- package/src/server/index.ts +2 -0
- package/src/utils/client/error.ts +10 -0
- package/src/utils/client/eventSource.ts +7 -0
- package/src/utils/client/format.ts +22 -0
- package/src/utils/client/httpClient.ts +444 -0
- package/src/utils/client/index.ts +6 -0
- package/src/utils/client/method.ts +50 -0
- package/src/utils/client/metrics.ts +9 -0
- package/src/utils/client/request.ts +113 -0
- package/src/utils/client/response.ts +205 -0
- package/src/utils/codecs.ts +143 -0
- package/src/utils/fork.ts +44 -0
- package/src/utils/headers.ts +173 -0
- package/src/utils/httpStatusCode.ts +392 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/metadata.ts +170 -0
- package/src/utils/schema.ts +141 -0
- package/src/utils/serdes.ts +120 -0
- package/src/utils/server/error.ts +9 -0
- package/src/utils/server/handler.ts +149 -0
- package/src/utils/server/index.ts +5 -0
- package/src/utils/server/method.ts +38 -0
- package/src/utils/server/parser.ts +15 -0
- package/src/utils/server/route.ts +45 -0
- package/src/utils/types.ts +161 -0
- package/src/utils/urlFormat.ts +112 -0
- package/src/utils/wireFormat.ts +24 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {HeadersExtra, HttpHeader, parseContentTypeHeader} from "../headers.js";
|
|
2
|
+
import {HttpStatusCode} from "../httpStatusCode.js";
|
|
3
|
+
import {Endpoint} from "../types.js";
|
|
4
|
+
import {WireFormat, getWireFormat} from "../wireFormat.js";
|
|
5
|
+
import {ApiError} from "./error.js";
|
|
6
|
+
import {RouteDefinitionExtra} from "./request.js";
|
|
7
|
+
|
|
8
|
+
export type RawBody =
|
|
9
|
+
| {type: WireFormat.json; value: unknown}
|
|
10
|
+
| {type: WireFormat.ssz; value: Uint8Array}
|
|
11
|
+
| {type?: never; value?: never};
|
|
12
|
+
|
|
13
|
+
export class ApiResponse<E extends Endpoint> extends Response {
|
|
14
|
+
private definition: RouteDefinitionExtra<E>;
|
|
15
|
+
private _wireFormat?: WireFormat | null;
|
|
16
|
+
private _rawBody?: RawBody;
|
|
17
|
+
private _errorBody?: string;
|
|
18
|
+
private _meta?: E["meta"];
|
|
19
|
+
private _value?: E["return"];
|
|
20
|
+
|
|
21
|
+
constructor(definition: RouteDefinitionExtra<E>, body?: BodyInit | null, init?: ResponseInit) {
|
|
22
|
+
super(body, init);
|
|
23
|
+
this.definition = definition;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
wireFormat(): WireFormat | null {
|
|
27
|
+
if (this._wireFormat === undefined) {
|
|
28
|
+
if (this.definition.resp.isEmpty) {
|
|
29
|
+
this._wireFormat = null;
|
|
30
|
+
return this._wireFormat;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const contentType = this.headers.get(HttpHeader.ContentType);
|
|
34
|
+
if (contentType === null) {
|
|
35
|
+
if (this.status === HttpStatusCode.NO_CONTENT) {
|
|
36
|
+
this._wireFormat = null;
|
|
37
|
+
return this._wireFormat;
|
|
38
|
+
}
|
|
39
|
+
throw Error("Content-Type header is required in response");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const mediaType = parseContentTypeHeader(contentType);
|
|
43
|
+
if (mediaType === null) {
|
|
44
|
+
throw Error(`Unsupported response media type: ${contentType.split(";", 1)[0]}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const wireFormat = getWireFormat(mediaType);
|
|
48
|
+
|
|
49
|
+
const {onlySupport} = this.definition.resp;
|
|
50
|
+
if (onlySupport !== undefined && wireFormat !== onlySupport) {
|
|
51
|
+
throw Error(`Method only supports ${onlySupport.toUpperCase()} responses`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this._wireFormat = wireFormat;
|
|
55
|
+
}
|
|
56
|
+
return this._wireFormat;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async rawBody(): Promise<RawBody> {
|
|
60
|
+
this.assertOk();
|
|
61
|
+
|
|
62
|
+
if (!this._rawBody) {
|
|
63
|
+
switch (this.wireFormat()) {
|
|
64
|
+
case WireFormat.json:
|
|
65
|
+
this._rawBody = {
|
|
66
|
+
type: WireFormat.json,
|
|
67
|
+
value: await super.json(),
|
|
68
|
+
};
|
|
69
|
+
break;
|
|
70
|
+
case WireFormat.ssz:
|
|
71
|
+
this._rawBody = {
|
|
72
|
+
type: WireFormat.ssz,
|
|
73
|
+
value: new Uint8Array(await this.arrayBuffer()),
|
|
74
|
+
};
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
this._rawBody = {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return this._rawBody;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
meta(): E["meta"] {
|
|
84
|
+
this.assertOk();
|
|
85
|
+
|
|
86
|
+
if (!this._meta) {
|
|
87
|
+
switch (this.wireFormat()) {
|
|
88
|
+
case WireFormat.json: {
|
|
89
|
+
const rawBody = this.resolvedRawBody();
|
|
90
|
+
const metaJson = this.definition.resp.transform
|
|
91
|
+
? this.definition.resp.transform.fromResponse(rawBody.value).meta
|
|
92
|
+
: rawBody.value;
|
|
93
|
+
this._meta = this.definition.resp.meta.fromJson(metaJson);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case WireFormat.ssz:
|
|
97
|
+
this._meta = this.definition.resp.meta.fromHeaders(new HeadersExtra(this.headers));
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return this._meta;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
value(): E["return"] {
|
|
105
|
+
this.assertOk();
|
|
106
|
+
|
|
107
|
+
if (!this._value) {
|
|
108
|
+
const rawBody = this.resolvedRawBody();
|
|
109
|
+
const meta = this.meta();
|
|
110
|
+
switch (rawBody.type) {
|
|
111
|
+
case WireFormat.json: {
|
|
112
|
+
const dataJson = this.definition.resp.transform
|
|
113
|
+
? this.definition.resp.transform.fromResponse(rawBody.value).data
|
|
114
|
+
: (rawBody.value as Record<string, unknown>)?.data;
|
|
115
|
+
this._value = this.definition.resp.data.fromJson(dataJson, meta);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case WireFormat.ssz:
|
|
119
|
+
this._value = this.definition.resp.data.deserialize(rawBody.value, meta);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return this._value;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ssz(): Uint8Array {
|
|
127
|
+
this.assertOk();
|
|
128
|
+
|
|
129
|
+
const rawBody = this.resolvedRawBody();
|
|
130
|
+
switch (rawBody.type) {
|
|
131
|
+
case WireFormat.json:
|
|
132
|
+
return this.definition.resp.data.serialize(this.value(), this.meta());
|
|
133
|
+
case WireFormat.ssz:
|
|
134
|
+
return rawBody.value;
|
|
135
|
+
default:
|
|
136
|
+
return new Uint8Array();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
json(): Awaited<ReturnType<Response["json"]>> {
|
|
141
|
+
this.assertOk();
|
|
142
|
+
|
|
143
|
+
const rawBody = this.resolvedRawBody();
|
|
144
|
+
switch (rawBody.type) {
|
|
145
|
+
case WireFormat.json:
|
|
146
|
+
return rawBody.value;
|
|
147
|
+
case WireFormat.ssz:
|
|
148
|
+
return this.definition.resp.data.toJson(this.value(), this.meta());
|
|
149
|
+
default:
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
assertOk(): void {
|
|
155
|
+
if (!this.ok) {
|
|
156
|
+
throw this.error();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
error(): ApiError | null {
|
|
161
|
+
if (this.ok) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return new ApiError(this.getErrorMessage(), this.status, this.definition.operationId);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async errorBody(): Promise<string> {
|
|
169
|
+
if (this._errorBody === undefined) {
|
|
170
|
+
this._errorBody = await this.text();
|
|
171
|
+
}
|
|
172
|
+
return this._errorBody;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private resolvedRawBody(): RawBody {
|
|
176
|
+
if (!this._rawBody) {
|
|
177
|
+
throw Error("rawBody() must be called first");
|
|
178
|
+
}
|
|
179
|
+
return this._rawBody;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private resolvedErrorBody(): string {
|
|
183
|
+
if (this._errorBody === undefined) {
|
|
184
|
+
throw Error("errorBody() must be called first");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return this._errorBody;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private getErrorMessage(): string {
|
|
191
|
+
const errBody = this.resolvedErrorBody();
|
|
192
|
+
try {
|
|
193
|
+
const errJson = JSON.parse(errBody) as {message?: string; failures?: {message: string}[]};
|
|
194
|
+
if (errJson.message) {
|
|
195
|
+
if (errJson.failures) {
|
|
196
|
+
return `${errJson.message}\n` + errJson.failures.map((e) => e.message).join("\n");
|
|
197
|
+
}
|
|
198
|
+
return errJson.message;
|
|
199
|
+
}
|
|
200
|
+
return errBody;
|
|
201
|
+
} catch (_e) {
|
|
202
|
+
return errBody || this.statusText;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {ArrayType, ListBasicType, ListCompositeType, Type, isBasicType, isCompositeType} from "@chainsafe/ssz";
|
|
2
|
+
import {ForkName} from "@lodestar/params";
|
|
3
|
+
import {objectToExpectedCase} from "@lodestar/utils";
|
|
4
|
+
import {
|
|
5
|
+
Endpoint,
|
|
6
|
+
RequestWithBodyCodec,
|
|
7
|
+
RequestWithoutBodyCodec,
|
|
8
|
+
ResponseCodec,
|
|
9
|
+
ResponseDataCodec,
|
|
10
|
+
ResponseMetadataCodec,
|
|
11
|
+
SszRequestMethods,
|
|
12
|
+
} from "./types.js";
|
|
13
|
+
import {WireFormat} from "./wireFormat.js";
|
|
14
|
+
|
|
15
|
+
// Utility types / codecs
|
|
16
|
+
|
|
17
|
+
export type EmptyArgs = void;
|
|
18
|
+
export type EmptyRequest = Record<never, never>;
|
|
19
|
+
export type EmptyResponseData = void;
|
|
20
|
+
export type EmptyMeta = void;
|
|
21
|
+
|
|
22
|
+
// biome-ignore lint/suspicious/noExplicitAny: We can not use `unknown` type here
|
|
23
|
+
export type AnyEndpoint = Endpoint<any, any, any, any, any>;
|
|
24
|
+
// biome-ignore lint/suspicious/noExplicitAny: We can not use `unknown` type here
|
|
25
|
+
export type EmptyRequestEndpoint = Endpoint<any, EmptyArgs, EmptyRequest, any, any>;
|
|
26
|
+
// biome-ignore lint/suspicious/noExplicitAny: We can not use `unknown` type here
|
|
27
|
+
export type EmptyResponseEndpoint = Endpoint<any, any, any, EmptyResponseData, EmptyMeta>;
|
|
28
|
+
|
|
29
|
+
/** Shortcut for routes that have no params, query */
|
|
30
|
+
export const EmptyRequestCodec: RequestWithoutBodyCodec<EmptyRequestEndpoint> = {
|
|
31
|
+
writeReq: () => ({}),
|
|
32
|
+
parseReq: () => {},
|
|
33
|
+
schema: {},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function JsonOnlyReq<E extends Endpoint>(
|
|
37
|
+
req: Omit<RequestWithBodyCodec<E>, keyof SszRequestMethods<E>>
|
|
38
|
+
): RequestWithBodyCodec<E> {
|
|
39
|
+
return {
|
|
40
|
+
...req,
|
|
41
|
+
writeReqSsz: () => {
|
|
42
|
+
throw Error("Not implemented");
|
|
43
|
+
},
|
|
44
|
+
parseReqSsz: () => {
|
|
45
|
+
throw Error("Not implemented");
|
|
46
|
+
},
|
|
47
|
+
onlySupport: WireFormat.json,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const EmptyResponseDataCodec: ResponseDataCodec<EmptyResponseData, EmptyMeta> = {
|
|
52
|
+
toJson: () => {},
|
|
53
|
+
fromJson: () => {},
|
|
54
|
+
serialize: () => new Uint8Array(),
|
|
55
|
+
deserialize: () => {},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const EmptyMetaCodec: ResponseMetadataCodec<EmptyMeta> = {
|
|
59
|
+
toJson: () => {},
|
|
60
|
+
fromJson: () => {},
|
|
61
|
+
toHeadersObject: () => ({}),
|
|
62
|
+
fromHeaders: () => {},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const EmptyResponseCodec: ResponseCodec<EmptyResponseEndpoint> = {
|
|
66
|
+
data: EmptyResponseDataCodec,
|
|
67
|
+
meta: EmptyMetaCodec,
|
|
68
|
+
isEmpty: true,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function ArrayOf<T>(elementType: Type<T>, limit = Infinity): ArrayType<Type<T>, unknown, unknown> {
|
|
72
|
+
if (isCompositeType(elementType)) {
|
|
73
|
+
return new ListCompositeType(elementType, limit) as unknown as ArrayType<Type<T>, unknown, unknown>;
|
|
74
|
+
}
|
|
75
|
+
if (isBasicType(elementType)) {
|
|
76
|
+
return new ListBasicType(elementType, limit) as unknown as ArrayType<Type<T>, unknown, unknown>;
|
|
77
|
+
}
|
|
78
|
+
throw Error(`Unknown type ${elementType.typeName}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function WithMeta<T, M extends {version: ForkName}>(getType: (m: M) => Type<T>): ResponseDataCodec<T, M> {
|
|
82
|
+
return {
|
|
83
|
+
toJson: (data, meta: M) => getType(meta).toJson(data),
|
|
84
|
+
fromJson: (data, meta: M) => getType(meta).fromJson(data),
|
|
85
|
+
serialize: (data, meta: M) => getType(meta).serialize(data),
|
|
86
|
+
deserialize: (data, meta: M) => getType(meta).deserialize(data),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function WithVersion<T, M extends {version: ForkName}>(
|
|
91
|
+
getType: (v: ForkName) => Type<T>
|
|
92
|
+
): ResponseDataCodec<T, M> {
|
|
93
|
+
return {
|
|
94
|
+
toJson: (data, meta: M) => getType(meta.version).toJson(data),
|
|
95
|
+
fromJson: (data, meta: M) => getType(meta.version).fromJson(data),
|
|
96
|
+
serialize: (data, meta: M) => getType(meta.version).serialize(data),
|
|
97
|
+
deserialize: (data, meta: M) => getType(meta.version).deserialize(data),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function JsonOnlyResp<E extends Endpoint>(
|
|
102
|
+
resp: Omit<ResponseCodec<E>, "data"> & {
|
|
103
|
+
data: Omit<ResponseCodec<E>["data"], "serialize" | "deserialize">;
|
|
104
|
+
}
|
|
105
|
+
): ResponseCodec<E> {
|
|
106
|
+
return {
|
|
107
|
+
...resp,
|
|
108
|
+
data: {
|
|
109
|
+
...resp.data,
|
|
110
|
+
serialize: () => {
|
|
111
|
+
throw Error("Not implemented");
|
|
112
|
+
},
|
|
113
|
+
deserialize: () => {
|
|
114
|
+
throw Error("Not implemented");
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
onlySupport: WireFormat.json,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const JsonOnlyResponseCodec: ResponseCodec<AnyEndpoint> = {
|
|
122
|
+
data: {
|
|
123
|
+
toJson: (data: Record<string, unknown>) => {
|
|
124
|
+
// JSON fields use snake case across all existing routes
|
|
125
|
+
return objectToExpectedCase(data, "snake");
|
|
126
|
+
},
|
|
127
|
+
fromJson: (data) => {
|
|
128
|
+
if (typeof data !== "object" || data === null) {
|
|
129
|
+
throw Error("JSON must be of type object");
|
|
130
|
+
}
|
|
131
|
+
// All JSON inside the JS code must be camel case
|
|
132
|
+
return objectToExpectedCase(data as Record<string, unknown>, "camel");
|
|
133
|
+
},
|
|
134
|
+
serialize: () => {
|
|
135
|
+
throw Error("Not implemented");
|
|
136
|
+
},
|
|
137
|
+
deserialize: () => {
|
|
138
|
+
throw Error("Not implemented");
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
meta: EmptyMetaCodec,
|
|
142
|
+
onlySupport: WireFormat.json,
|
|
143
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ForkName,
|
|
3
|
+
ForkPostAltair,
|
|
4
|
+
ForkPostBellatrix,
|
|
5
|
+
ForkPostDeneb,
|
|
6
|
+
isForkPostAltair,
|
|
7
|
+
isForkPostBellatrix,
|
|
8
|
+
isForkPostDeneb,
|
|
9
|
+
} from "@lodestar/params";
|
|
10
|
+
import {SSZTypesFor, sszTypesFor} from "@lodestar/types";
|
|
11
|
+
|
|
12
|
+
export function toForkName(version: string): ForkName {
|
|
13
|
+
// Teku returns fork as UPPERCASE
|
|
14
|
+
version = version.toLowerCase();
|
|
15
|
+
|
|
16
|
+
// Un-safe external data, validate version is known ForkName value
|
|
17
|
+
if (!(version in ForkName)) throw Error(`Invalid version ${version}`);
|
|
18
|
+
|
|
19
|
+
return version as ForkName;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getPostAltairForkTypes(fork: ForkName): SSZTypesFor<ForkPostAltair> {
|
|
23
|
+
if (!isForkPostAltair(fork)) {
|
|
24
|
+
throw Error(`Invalid fork=${fork} for post-altair fork types`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return sszTypesFor(fork);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getPostBellatrixForkTypes(fork: ForkName): SSZTypesFor<ForkPostBellatrix> {
|
|
31
|
+
if (!isForkPostBellatrix(fork)) {
|
|
32
|
+
throw Error(`Invalid fork=${fork} for post-bellatrix fork types`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return sszTypesFor(fork);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getPostDenebForkTypes(fork: ForkName): SSZTypesFor<ForkPostDeneb> {
|
|
39
|
+
if (!isForkPostDeneb(fork)) {
|
|
40
|
+
throw Error(`Invalid fork=${fork} for post-deneb fork types`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return sszTypesFor(fork);
|
|
44
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {toBase64} from "@lodestar/utils";
|
|
2
|
+
|
|
3
|
+
export enum HttpHeader {
|
|
4
|
+
ContentType = "content-type",
|
|
5
|
+
Accept = "accept",
|
|
6
|
+
Authorization = "authorization",
|
|
7
|
+
/**
|
|
8
|
+
* Used to indicate which response headers should be made available to
|
|
9
|
+
* scripts running in the browser, in response to a cross-origin request.
|
|
10
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
|
|
11
|
+
*/
|
|
12
|
+
ExposeHeaders = "access-control-expose-headers",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export enum MediaType {
|
|
16
|
+
json = "application/json",
|
|
17
|
+
ssz = "application/octet-stream",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const SUPPORTED_MEDIA_TYPES = Object.values(MediaType);
|
|
21
|
+
|
|
22
|
+
function isSupportedMediaType(mediaType: string | null, supported: MediaType[]): mediaType is MediaType {
|
|
23
|
+
return mediaType !== null && supported.includes(mediaType as MediaType);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseContentTypeHeader(contentType?: string): MediaType | null {
|
|
27
|
+
if (!contentType) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const mediaType = contentType.split(";", 1)[0].trim().toLowerCase();
|
|
32
|
+
|
|
33
|
+
return isSupportedMediaType(mediaType, SUPPORTED_MEDIA_TYPES) ? mediaType : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseAcceptHeader(accept?: string, supported = SUPPORTED_MEDIA_TYPES): MediaType | null {
|
|
37
|
+
if (!accept) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Respect Quality Values per RFC-9110
|
|
42
|
+
// Acceptable mime-types are comma separated with optional whitespace
|
|
43
|
+
return accept
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.split(",")
|
|
46
|
+
.map((x) => x.trim())
|
|
47
|
+
.reduce(
|
|
48
|
+
(best: [number, MediaType | null], current: string): [number, MediaType | null] => {
|
|
49
|
+
// An optional `;` delimiter is used to separate the mime-type from the weight
|
|
50
|
+
// Normalize here, using 1 as the default qvalue
|
|
51
|
+
const quality = current.includes(";") ? current.split(";") : [current, "q=1"];
|
|
52
|
+
|
|
53
|
+
let mediaType = quality[0].trim();
|
|
54
|
+
|
|
55
|
+
if (mediaType === "*/*") {
|
|
56
|
+
// Default to json if all media types are accepted
|
|
57
|
+
mediaType = MediaType.json;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// If the mime type isn't acceptable, move on to the next entry
|
|
61
|
+
if (!isSupportedMediaType(mediaType, supported)) {
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Otherwise, the portion after the semicolon has optional whitespace and the constant prefix "q="
|
|
66
|
+
const weight = quality[1].trim();
|
|
67
|
+
if (!weight.startsWith("q=")) {
|
|
68
|
+
// If the format is invalid simply move on to the next entry
|
|
69
|
+
return best;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const qvalue = +weight.replace("q=", "");
|
|
73
|
+
if (Number.isNaN(qvalue) || qvalue > 1 || qvalue <= 0) {
|
|
74
|
+
// If we can't convert the qvalue to a valid number, move on
|
|
75
|
+
return best;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (qvalue < best[0]) {
|
|
79
|
+
// This mime type is not preferred
|
|
80
|
+
return best;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// This mime type is preferred
|
|
84
|
+
return [qvalue, mediaType];
|
|
85
|
+
},
|
|
86
|
+
[0, null]
|
|
87
|
+
)[1];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function setAuthorizationHeader(url: URL, headers: Headers, {bearerToken}: {bearerToken?: string}): void {
|
|
91
|
+
if (bearerToken && !headers.has(HttpHeader.Authorization)) {
|
|
92
|
+
headers.set(HttpHeader.Authorization, `Bearer ${bearerToken}`);
|
|
93
|
+
}
|
|
94
|
+
if (url.username || url.password) {
|
|
95
|
+
if (!headers.has(HttpHeader.Authorization)) {
|
|
96
|
+
headers.set(HttpHeader.Authorization, `Basic ${toBase64(decodeURIComponent(`${url.username}:${url.password}`))}`);
|
|
97
|
+
}
|
|
98
|
+
// Remove the username and password from the URL
|
|
99
|
+
url.username = "";
|
|
100
|
+
url.password = "";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function mergeHeaders(...headersList: (HeadersInit | undefined)[]): Headers {
|
|
105
|
+
const mergedHeaders = new Headers();
|
|
106
|
+
|
|
107
|
+
for (const headers of headersList) {
|
|
108
|
+
if (!headers) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (Array.isArray(headers)) {
|
|
112
|
+
for (const [key, value] of headers) {
|
|
113
|
+
mergedHeaders.set(key, value);
|
|
114
|
+
}
|
|
115
|
+
} else if (headers instanceof Headers) {
|
|
116
|
+
for (const [key, value] of headers as unknown as Iterable<[string, string]>) {
|
|
117
|
+
mergedHeaders.set(key, value);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
121
|
+
mergedHeaders.set(key, value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return mergedHeaders;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get header from request headers, by default an error will be thrown if the header
|
|
131
|
+
* is not present. The header can be marked as optional in which case the return value
|
|
132
|
+
* might be `undefined` but no error will be thrown if header is missing.
|
|
133
|
+
*/
|
|
134
|
+
export function fromHeaders<T extends Record<string, string | undefined>, R extends boolean = true>(
|
|
135
|
+
headers: T,
|
|
136
|
+
name: Extract<keyof T, string>,
|
|
137
|
+
required: R = true as R
|
|
138
|
+
): R extends true ? string : string | undefined {
|
|
139
|
+
// Fastify converts all headers to lower case
|
|
140
|
+
const header = headers[name.toLowerCase()];
|
|
141
|
+
|
|
142
|
+
if (header === undefined && required) {
|
|
143
|
+
throw Error(`${name} header is required`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return header as R extends true ? string : string | undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Extension of Headers object returned by Fetch API
|
|
151
|
+
*/
|
|
152
|
+
export class HeadersExtra extends Headers {
|
|
153
|
+
/**
|
|
154
|
+
* Get required header from response headers
|
|
155
|
+
*/
|
|
156
|
+
getRequired(name: string): string {
|
|
157
|
+
const header = this.get(name);
|
|
158
|
+
|
|
159
|
+
if (header === null) {
|
|
160
|
+
throw Error(`${name} header is required in response`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return header;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get optional header from response headers.
|
|
168
|
+
* Return default value if it does not exist
|
|
169
|
+
*/
|
|
170
|
+
getOrDefault(name: string, defaultValue: string): string {
|
|
171
|
+
return this.get(name) ?? defaultValue;
|
|
172
|
+
}
|
|
173
|
+
}
|