Skip to content

Commit 03b64a3

Browse files
authored
feat(slots): block + blob download modal on the slot detail page (#437)
* feat(slots): add block/blob download modal Add a Download modal to the slot detail page with a Block tab (JSON/SSZ) and a Blobs tab (one row per blob, download by versioned hash). Downloads go through the lab-backend download proxy via a same-origin fetch+save hook that surfaces archive 404s inline. Blob list currently sources versioned hashes from dim_block_blob_submitter; to be repointed at fct_block_blob_head (head-timely) once that CBT table is deployed. * chore(api): regenerate from prod cbt-api (adds fct_block_blob_head) * feat(slots): source download-modal blobs from fct_block_blob_head The Blobs tab now self-fetches the slot's versioned hashes from the head table fct_block_blob_head (real-time) instead of dim_block_blob_submitter (which lags the chain head and was empty for recent slots). Queried by slot_start_date_time since the cbt-api slot projection filter does not match.
1 parent f7071ce commit 03b64a3

25 files changed

Lines changed: 129124 additions & 231983 deletions

src/api/@tanstack/react-query.gen.ts

Lines changed: 4404 additions & 8933 deletions
Large diffs are not rendered by default.

src/api/client.gen.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ import type { ClientOptions as ClientOptions2 } from './types.gen';
1313
* `setConfig()`. This is useful for example if you're using Next.js
1414
* to ensure your client always has the correct values.
1515
*/
16-
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (
17-
override?: Config<ClientOptions & T>
18-
) => Config<Required<ClientOptions> & T>;
16+
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
1917

20-
export const client = createClient(
21-
createClientConfig(createConfig<ClientOptions2>({ baseUrl: 'http://localhost:8091' }))
22-
);
18+
export const client = createClient(createClientConfig(createConfig<ClientOptions2>({ baseUrl: 'https://cbt-api-mainnet.analytics.production.platform.ethpandaops.io' })));

src/api/client/client.gen.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const createClient = (config: Config = {}): Client => {
6565
return { opts, url };
6666
};
6767

68-
const request: Client['request'] = async options => {
68+
const request: Client['request'] = async (options) => {
6969
// @ts-expect-error
7070
const { opts, url } = await beforeRequest(options);
7171
const requestInit: ReqInit = {
@@ -128,7 +128,9 @@ export const createClient = (config: Config = {}): Client => {
128128

129129
if (response.ok) {
130130
const parseAs =
131-
(opts.parseAs === 'auto' ? getParseAs(response.headers.get('Content-Type')) : opts.parseAs) ?? 'json';
131+
(opts.parseAs === 'auto'
132+
? getParseAs(response.headers.get('Content-Type'))
133+
: opts.parseAs) ?? 'json';
132134

133135
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
134136
let emptyData: any;
@@ -232,7 +234,8 @@ export const createClient = (config: Config = {}): Client => {
232234
};
233235
};
234236

235-
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) => request({ ...options, method });
237+
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
238+
request({ ...options, method });
236239

237240
const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
238241
const { opts, url } = await beforeRequest(options);

src/api/client/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
export type { Auth } from '../core/auth.gen';
44
export type { QuerySerializerOptions } from '../core/bodySerializer.gen';
5-
export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer } from '../core/bodySerializer.gen';
5+
export {
6+
formDataBodySerializer,
7+
jsonBodySerializer,
8+
urlSearchParamsBodySerializer,
9+
} from '../core/bodySerializer.gen';
610
export { buildClientParams } from '../core/params.gen';
711
export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen';
812
export { createClient } from './client.gen';

src/api/client/types.gen.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// This file is auto-generated by @hey-api/openapi-ts
22

33
import type { Auth } from '../core/auth.gen';
4-
import type { ServerSentEventsOptions, ServerSentEventsResult } from '../core/serverSentEvents.gen';
4+
import type {
5+
ServerSentEventsOptions,
6+
ServerSentEventsResult,
7+
} from '../core/serverSentEvents.gen';
58
import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen';
69
import type { Middleware } from './utils.gen';
710

@@ -63,7 +66,11 @@ export interface RequestOptions<
6366
}>,
6467
Pick<
6568
ServerSentEventsOptions<TData>,
66-
'onSseError' | 'onSseEvent' | 'sseDefaultRetryDelay' | 'sseMaxRetryAttempts' | 'sseMaxRetryDelay'
69+
| 'onSseError'
70+
| 'onSseEvent'
71+
| 'sseDefaultRetryDelay'
72+
| 'sseMaxRetryAttempts'
73+
| 'sseMaxRetryDelay'
6774
> {
6875
/**
6976
* Any body that you want to add to your request.
@@ -135,7 +142,7 @@ type MethodFn = <
135142
ThrowOnError extends boolean = false,
136143
TResponseStyle extends ResponseStyle = 'fields',
137144
>(
138-
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>
145+
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
139146
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
140147

141148
type SseFn = <
@@ -144,7 +151,7 @@ type SseFn = <
144151
ThrowOnError extends boolean = false,
145152
TResponseStyle extends ResponseStyle = 'fields',
146153
>(
147-
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>
154+
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
148155
) => Promise<ServerSentEventsResult<TData, TError>>;
149156

150157
type RequestFn = <
@@ -154,7 +161,7 @@ type RequestFn = <
154161
TResponseStyle extends ResponseStyle = 'fields',
155162
>(
156163
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> &
157-
Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, 'method'>
164+
Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, 'method'>,
158165
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
159166

160167
type BuildUrlFn = <
@@ -165,7 +172,7 @@ type BuildUrlFn = <
165172
url: string;
166173
},
167174
>(
168-
options: TData & Options<TData>
175+
options: TData & Options<TData>,
169176
) => string;
170177

171178
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & {
@@ -181,7 +188,7 @@ export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn>
181188
* to ensure your client always has the correct values.
182189
*/
183190
export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
184-
override?: Config<ClientOptions & T>
191+
override?: Config<ClientOptions & T>,
185192
) => Config<Required<ClientOptions> & T>;
186193

187194
export interface TDataShape {
@@ -199,5 +206,8 @@ export type Options<
199206
ThrowOnError extends boolean = boolean,
200207
TResponse = unknown,
201208
TResponseStyle extends ResponseStyle = 'fields',
202-
> = OmitKeys<RequestOptions<TResponse, TResponseStyle, ThrowOnError>, 'body' | 'path' | 'query' | 'url'> &
209+
> = OmitKeys<
210+
RequestOptions<TResponse, TResponseStyle, ThrowOnError>,
211+
'body' | 'path' | 'query' | 'url'
212+
> &
203213
([TData] extends [never] ? unknown : Omit<TData, 'url'>);

src/api/client/utils.gen.ts

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
import { getAuthToken } from '../core/auth.gen';
44
import type { QuerySerializerOptions } from '../core/bodySerializer.gen';
55
import { jsonBodySerializer } from '../core/bodySerializer.gen';
6-
import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from '../core/pathSerializer.gen';
6+
import {
7+
serializeArrayParam,
8+
serializeObjectParam,
9+
serializePrimitiveParam,
10+
} from '../core/pathSerializer.gen';
711
import { getUrl } from '../core/utils.gen';
812
import type { Client, ClientOptions, Config, RequestOptions } from './types.gen';
913

10-
export const createQuerySerializer = <T = unknown>({ parameters = {}, ...args }: QuerySerializerOptions = {}) => {
14+
export const createQuerySerializer = <T = unknown>({
15+
parameters = {},
16+
...args
17+
}: QuerySerializerOptions = {}) => {
1118
const querySerializer = (queryParams: T) => {
1219
const search: string[] = [];
1320
if (queryParams && typeof queryParams === 'object') {
@@ -79,7 +86,9 @@ export const getParseAs = (contentType: string | null): Exclude<Config['parseAs'
7986
return 'formData';
8087
}
8188

82-
if (['application/', 'audio/', 'image/', 'video/'].some(type => cleanContent.startsWith(type))) {
89+
if (
90+
['application/', 'audio/', 'image/', 'video/'].some((type) => cleanContent.startsWith(type))
91+
) {
8392
return 'blob';
8493
}
8594

@@ -94,12 +103,16 @@ const checkForExistence = (
94103
options: Pick<RequestOptions, 'auth' | 'query'> & {
95104
headers: Headers;
96105
},
97-
name?: string
106+
name?: string,
98107
): boolean => {
99108
if (!name) {
100109
return false;
101110
}
102-
if (options.headers.has(name) || options.query?.[name] || options.headers.get('Cookie')?.includes(`${name}=`)) {
111+
if (
112+
options.headers.has(name) ||
113+
options.query?.[name] ||
114+
options.headers.get('Cookie')?.includes(`${name}=`)
115+
) {
103116
return true;
104117
}
105118
return false;
@@ -143,7 +156,7 @@ export const setAuthParams = async ({
143156
}
144157
};
145158

146-
export const buildUrl: Client['buildUrl'] = options =>
159+
export const buildUrl: Client['buildUrl'] = (options) =>
147160
getUrl({
148161
baseUrl: options.baseUrl as string,
149162
path: options.path,
@@ -172,7 +185,9 @@ const headersEntries = (headers: Headers): Array<[string, string]> => {
172185
return entries;
173186
};
174187

175-
export const mergeHeaders = (...headers: Array<Required<Config>['headers'] | undefined>): Headers => {
188+
export const mergeHeaders = (
189+
...headers: Array<Required<Config>['headers'] | undefined>
190+
): Headers => {
176191
const mergedHeaders = new Headers();
177192
for (const header of headers) {
178193
if (!header) {
@@ -191,7 +206,10 @@ export const mergeHeaders = (...headers: Array<Required<Config>['headers'] | und
191206
} else if (value !== undefined) {
192207
// assume object headers are meant to be JSON stringified, i.e. their
193208
// content value in OpenAPI specification is 'application/json'
194-
mergedHeaders.set(key, typeof value === 'object' ? JSON.stringify(value) : (value as string));
209+
mergedHeaders.set(
210+
key,
211+
typeof value === 'object' ? JSON.stringify(value) : (value as string),
212+
);
195213
}
196214
}
197215
}
@@ -202,12 +220,16 @@ type ErrInterceptor<Err, Res, Req, Options> = (
202220
error: Err,
203221
response: Res,
204222
request: Req,
205-
options: Options
223+
options: Options,
206224
) => Err | Promise<Err>;
207225

208226
type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>;
209227

210-
type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>;
228+
type ResInterceptor<Res, Req, Options> = (
229+
response: Res,
230+
request: Req,
231+
options: Options,
232+
) => Res | Promise<Res>;
211233

212234
class Interceptors<Interceptor> {
213235
fns: Array<Interceptor | null> = [];
@@ -256,7 +278,12 @@ export interface Middleware<Req, Res, Err, Options> {
256278
response: Interceptors<ResInterceptor<Res, Req, Options>>;
257279
}
258280

259-
export const createInterceptors = <Req, Res, Err, Options>(): Middleware<Req, Res, Err, Options> => ({
281+
export const createInterceptors = <Req, Res, Err, Options>(): Middleware<
282+
Req,
283+
Res,
284+
Err,
285+
Options
286+
> => ({
260287
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
261288
request: new Interceptors<ReqInterceptor<Req, Options>>(),
262289
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
@@ -279,7 +306,7 @@ const defaultHeaders = {
279306
};
280307

281308
export const createConfig = <T extends ClientOptions = ClientOptions>(
282-
override: Config<Omit<ClientOptions, keyof T> & T> = {}
309+
override: Config<Omit<ClientOptions, keyof T> & T> = {},
283310
): Config<Omit<ClientOptions, keyof T> & T> => ({
284311
...jsonBodySerializer,
285312
headers: defaultHeaders,

src/api/core/auth.gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface Auth {
2121

2222
export const getAuthToken = async (
2323
auth: Auth,
24-
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken
24+
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
2525
): Promise<string | undefined> => {
2626
const token = typeof callback === 'function' ? await callback(auth) : callback;
2727

src/api/core/bodySerializer.gen.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,17 @@ const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value:
3939
};
4040

4141
export const formDataBodySerializer = {
42-
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): FormData => {
42+
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
43+
body: T,
44+
): FormData => {
4345
const data = new FormData();
4446

4547
Object.entries(body).forEach(([key, value]) => {
4648
if (value === undefined || value === null) {
4749
return;
4850
}
4951
if (Array.isArray(value)) {
50-
value.forEach(v => serializeFormDataPair(data, key, v));
52+
value.forEach((v) => serializeFormDataPair(data, key, v));
5153
} else {
5254
serializeFormDataPair(data, key, value);
5355
}
@@ -71,7 +73,7 @@ export const urlSearchParamsBodySerializer = {
7173
return;
7274
}
7375
if (Array.isArray(value)) {
74-
value.forEach(v => serializeUrlSearchParamsPair(data, key, v));
76+
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
7577
} else {
7678
serializeUrlSearchParamsPair(data, key, value);
7779
}

src/api/core/pathSerializer.gen.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ export const serializeArrayParam = ({
7474
value: unknown[];
7575
}) => {
7676
if (!explode) {
77-
const joinedValues = (allowReserved ? value : value.map(v => encodeURIComponent(v as string))).join(
78-
separatorArrayNoExplode(style)
79-
);
77+
const joinedValues = (
78+
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
79+
).join(separatorArrayNoExplode(style));
8080
switch (style) {
8181
case 'label':
8282
return `.${joinedValues}`;
@@ -91,7 +91,7 @@ export const serializeArrayParam = ({
9191

9292
const separator = separatorArrayExplode(style);
9393
const joinedValues = value
94-
.map(v => {
94+
.map((v) => {
9595
if (style === 'label' || style === 'simple') {
9696
return allowReserved ? v : encodeURIComponent(v as string);
9797
}
@@ -106,14 +106,18 @@ export const serializeArrayParam = ({
106106
return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues;
107107
};
108108

109-
export const serializePrimitiveParam = ({ allowReserved, name, value }: SerializePrimitiveParam) => {
109+
export const serializePrimitiveParam = ({
110+
allowReserved,
111+
name,
112+
value,
113+
}: SerializePrimitiveParam) => {
110114
if (value === undefined || value === null) {
111115
return '';
112116
}
113117

114118
if (typeof value === 'object') {
115119
throw new Error(
116-
'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.'
120+
'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.',
117121
);
118122
}
119123

@@ -160,7 +164,7 @@ export const serializeObjectParam = ({
160164
allowReserved,
161165
name: style === 'deepObject' ? `${name}[${key}]` : key,
162166
value: v as string,
163-
})
167+
}),
164168
)
165169
.join(separator);
166170
return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues;

src/api/core/queryKeySerializer.gen.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
/**
44
* JSON-friendly union that mirrors what Pinia Colada can hash.
55
*/
6-
export type JsonValue = null | string | number | boolean | JsonValue[] | { [key: string]: JsonValue };
6+
export type JsonValue =
7+
| null
8+
| string
9+
| number
10+
| boolean
11+
| JsonValue[]
12+
| { [key: string]: JsonValue };
713

814
/**
915
* Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.

0 commit comments

Comments
 (0)