Extract http request attempt

master
dev 5 months ago
parent ea424b1b3b
commit 2aeef3010e

@ -14,6 +14,7 @@ Estado al cierre:
- `svrs/auth/adapters/memory-store.ts` queda como composition root de 39 lineas; la logica se reparte en stores internos de credentials, flows, linked accounts, sessions/devices, refresh y state/snapshot.
- `arts/perm/client.ts` delega transporte HTTP/JSON en `client-http.ts`; el cliente queda centrado en cache, snapshot y fallback.
- `arts/perm/client.ts` delega TTL, cache positiva, backoff de fallos e invalidacion por prefijo en `client-cache.ts`.
- `arts/http/engine-http.ts` delega ejecucion de cada intento, hooks pre-request y diagnosticos request/network en `request-attempt.ts`.
- `arts/timr/engine-timers.ts` delega `TimerHandle` cancel/reschedule en `timer-handle.ts`.
- `arts/stor/engine-storage.ts` delega IDs de adapter, bus keys y suscripciones cross-tab en `adapter-registry.ts`.
- `arts/stor/engine-storage.ts` delega el registro de defaults conflictivos en `defaults-registry.ts`.

@ -1,8 +1,7 @@
import type { StandardSchemaV1 } from '$libs/standard-schema';
import { mergeHeaders, serializeBody } from './body.ts';
import { serializeBody } from './body.ts';
import {
HTTP_HEADER_CONTENT_TYPE,
HTTP_METHOD_DELETE,
HTTP_METHOD_GET,
HTTP_METHOD_HEAD,
@ -20,11 +19,9 @@ import {
resolveHttpCallRetry,
type ResolvedHttpDefaults
} from './engine-options.ts';
import {
runBeforeRequest,
runBeforeRetry
} from './hooks.ts';
import { runBeforeRetry } from './hooks.ts';
import { computeRetryDelay, delayWithSignal, shouldRetryRequest } from './retry.ts';
import { runHttpAttempt } from './request-attempt.ts';
import { validateHttpRequestBody } from './request-validation.ts';
import { resolveHttpFinalResult } from './response-resolution.ts';
import { appendSearch, normalizeSearch, resolveUrl } from './search.ts';
@ -38,7 +35,6 @@ import type {
EngineHttp,
EngineHttpOptions,
HookContext,
HookRequest,
HttpBodyRequestInit,
HttpGetInit,
HttpInit,
@ -138,56 +134,22 @@ async function execute<S extends StandardSchemaV1 | undefined>(
while (true) {
const attemptSignal = attemptTimeoutSignal(init?.timeout ?? defaults.timeout);
const signal = composeSignals([userSignal, totalSignal, attemptSignal]);
// Headers refresh per attempt — `defaults.headers` may be a hook
// (auth refresh pattern) and `beforeRetry` may have updated the
// closure-captured token.
const headers = await mergeHeaders(defaults.headers, init?.headers);
if (contentType !== undefined && !headers.has(HTTP_HEADER_CONTENT_TYPE)) {
headers.set(HTTP_HEADER_CONTENT_TYPE, contentType);
}
// Fresh request bag per attempt — hooks mutate this; mutations within
// an attempt persist to fetch, but each retry starts from defaults.
const request: HookRequest = {
const attemptResult = await runHttpAttempt({
defaults,
hooks,
fetchImpl,
method,
headers,
fullUrl,
init,
attempt,
body: serializedBody,
contentType,
signal
};
const ctx: HookContext = { url: fullUrl, request, attempt, init: init ?? {} };
lastCtx = ctx;
// beforeRequest hooks may short-circuit by returning a Response.
const earlyResponse = await runBeforeRequest(hooks.beforeRequest, ctx);
try {
emitHttpDiagnostic(diagnostics, HTTP_DIAGNOSTIC_EVENTS.REQUEST, {
method,
url: ctx.url,
attempt
});
response =
earlyResponse instanceof Response
? earlyResponse
: await fetchImpl(ctx.url, {
method: ctx.request.method,
headers: ctx.request.headers,
body: ctx.request.body,
signal: ctx.request.signal
});
lastError = undefined;
} catch (err) {
response = undefined;
lastError = classifyFetchError(err, signal);
emitHttpDiagnostic(diagnostics, HTTP_DIAGNOSTIC_EVENTS.NETWORK_ERROR, {
method,
url: ctx.url,
attempt,
error: lastError instanceof Error ? lastError : new Error(String(lastError))
});
}
const ctx = attemptResult.ctx;
lastCtx = ctx;
response = attemptResult.response;
lastError = attemptResult.error;
// Decide whether to retry.
if (retry !== null) {
@ -245,11 +207,3 @@ async function execute<S extends StandardSchemaV1 | undefined>(
lastError
});
}
function classifyFetchError(err: unknown, signal: AbortSignal): unknown {
if (signal.aborted) {
const classified = classifyAbort(signal);
if (classified !== null) return classified;
}
return err;
}

@ -0,0 +1,93 @@
import { mergeHeaders } from './body.ts';
import {
HTTP_DIAGNOSTIC_EVENTS,
HTTP_HEADER_CONTENT_TYPE
} from './consts.ts';
import { emitHttpDiagnostic } from './diagnostics.ts';
import { runBeforeRequest } from './hooks.ts';
import { classifyAbort } from './timeout.ts';
import type { ResolvedHttpDefaults } from './engine-options.ts';
import type {
HookContext,
HookRequest,
HttpHooks,
HttpInit,
HttpMethod
} from './types.ts';
export interface HttpAttemptInput {
readonly defaults: ResolvedHttpDefaults;
readonly hooks: Required<HttpHooks>;
readonly fetchImpl: typeof fetch;
readonly method: HttpMethod;
readonly fullUrl: string;
readonly init: HttpInit | undefined;
readonly attempt: number;
readonly body: BodyInit | undefined;
readonly contentType: string | undefined;
readonly signal: AbortSignal;
}
export interface HttpAttemptResult {
readonly ctx: HookContext;
readonly response: Response | undefined;
readonly error: unknown;
}
export async function runHttpAttempt(input: HttpAttemptInput): Promise<HttpAttemptResult> {
const headers = await mergeHeaders(input.defaults.headers, input.init?.headers);
if (input.contentType !== undefined && !headers.has(HTTP_HEADER_CONTENT_TYPE)) {
headers.set(HTTP_HEADER_CONTENT_TYPE, input.contentType);
}
const request: HookRequest = {
method: input.method,
headers,
body: input.body,
signal: input.signal
};
const ctx: HookContext = {
url: input.fullUrl,
request,
attempt: input.attempt,
init: input.init ?? {}
};
const earlyResponse = await runBeforeRequest(input.hooks.beforeRequest, ctx);
try {
emitHttpDiagnostic(input.defaults.diagnostics, HTTP_DIAGNOSTIC_EVENTS.REQUEST, {
method: input.method,
url: ctx.url,
attempt: input.attempt
});
const response =
earlyResponse instanceof Response
? earlyResponse
: await input.fetchImpl(ctx.url, {
method: ctx.request.method,
headers: ctx.request.headers,
body: ctx.request.body,
signal: ctx.request.signal
});
return { ctx, response, error: undefined };
} catch (err) {
const error = classifyFetchError(err, input.signal);
emitHttpDiagnostic(input.defaults.diagnostics, HTTP_DIAGNOSTIC_EVENTS.NETWORK_ERROR, {
method: input.method,
url: ctx.url,
attempt: input.attempt,
error: error instanceof Error ? error : new Error(String(error))
});
return { ctx, response: undefined, error };
}
}
function classifyFetchError(err: unknown, signal: AbortSignal): unknown {
if (signal.aborted) {
const classified = classifyAbort(signal);
if (classified !== null) return classified;
}
return err;
}
Loading…
Cancel
Save

Powered by TurnKey Linux.