corvée(lint) applique des suggestions de lint Oxlint

This commit is contained in:
gcch 2026-04-29 10:45:49 +02:00
commit ab665599c9
6 changed files with 64 additions and 36 deletions

View file

@ -1,14 +1,16 @@
import { Console, Context, Effect, Layer, Match, pipe, Schedule, Schema, SchemaIssue } from "effect"; import { Console, Context, Effect, Layer, Match, pipe, Schedule, Schema, SchemaIssue } from "effect";
import { SchemaError } from "effect/Schema"; import type { SchemaError } from "effect/Schema";
import type {
HttpClientError,
} from "effect/unstable/http";
import { import {
FetchHttpClient, FetchHttpClient,
HttpClient, HttpClient,
HttpClientError,
HttpClientRequest, HttpClientRequest,
HttpClientResponse, HttpClientResponse,
} from "effect/unstable/http"; } from "effect/unstable/http";
import { HttpClientErrorSchema } from "effect/unstable/http/HttpClientError"; import { HttpClientErrorSchema } from "effect/unstable/http/HttpClientError";
import type { CartProduct } from "../schemas/api.ts"; import type { CartProduct, GetProducts } from "../schemas/api.ts";
import { WooCommerceCart } from "../schemas/cart.ts"; import { WooCommerceCart } from "../schemas/cart.ts";
/** Le nombre maximal d'essais pour une Requête. */ /** Le nombre maximal d'essais pour une Requête. */
@ -16,20 +18,21 @@ const MAX_RETRIES = 3;
/** Le temps d'attente avant de réessayer une Requête. */ /** Le temps d'attente avant de réessayer une Requête. */
const RETRY_WAIT_TIME = "1 seconds"; const RETRY_WAIT_TIME = "1 seconds";
type APIError = APIRequestError | APIResponseError;
type APIResponse<T> = T | WooCommerceError;
/** Décrit une Erreur survenue au traitement d'une `Request`. */ /** Décrit une Erreur survenue au traitement d'une `Request`. */
class APIRequestError extends Schema.TaggedErrorClass<APIRequestError>()("APIRequestError", { class APIRequestError extends Schema.TaggedErrorClass<APIRequestError>()("APIRequestError", {
message: Schema.String,
cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]), cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]),
message: Schema.String,
}) {} }) {}
/** Décrit une Erreur survenue au traitement d'une `Response`. */ /** Décrit une Erreur survenue au traitement d'une `Response`. */
class APIResponseError extends Schema.TaggedErrorClass<APIResponseError>()("APIResponseError", { class APIResponseError extends Schema.TaggedErrorClass<APIResponseError>()("APIResponseError", {
message: Schema.String,
cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]), cause: Schema.Union([Schema.Defect, HttpClientErrorSchema]),
message: Schema.String,
}) {} }) {}
type APIError = APIRequestError | APIResponseError;
class WooCommerceErrorBody extends Schema.Class<WooCommerceErrorBody>("WooCommerceErrorBody")({ class WooCommerceErrorBody extends Schema.Class<WooCommerceErrorBody>("WooCommerceErrorBody")({
code: Schema.String, code: Schema.String,
data: Schema.Struct({ data: Schema.Struct({
@ -37,13 +40,12 @@ class WooCommerceErrorBody extends Schema.Class<WooCommerceErrorBody>("WooCommer
}), }),
message: Schema.String, message: Schema.String,
}) {} }) {}
class WooCommerceError extends Schema.Class<WooCommerceError>("WooCommerceError")({ class WooCommerceError extends Schema.Class<WooCommerceError>("WooCommerceError")({
body: WooCommerceErrorBody, body: WooCommerceErrorBody,
status: Schema.Number, status: Schema.Number,
}) {} }) {}
type APIResponse<T> = T | WooCommerceError;
/** Client `fetch` contenant les options et en-têtes de Requêtes pré-renseignées. */ /** Client `fetch` contenant les options et en-têtes de Requêtes pré-renseignées. */
const APIFetchClient = FetchHttpClient.layer.pipe( const APIFetchClient = FetchHttpClient.layer.pipe(
Layer.provide( Layer.provide(
@ -81,22 +83,22 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
const matchAPIError = (error: HttpClientError.HttpClientError | SchemaError): APIError => { const matchAPIError = (error: HttpClientError.HttpClientError | SchemaError): APIError => {
if (error._tag === "SchemaError") { if (error._tag === "SchemaError") {
return new APIRequestError({ return new APIRequestError({
message: `Erreur lors du parsage du corps de la Requête :${SchemaIssue.makeFormatterDefault()(error.issue)}`,
cause: error, cause: error,
message: `Erreur lors du parsage du corps de la Requête :${SchemaIssue.makeFormatterDefault()(error.issue)}`,
}); });
} else { } else {
return Match.typeTags<HttpClientError.HttpClientErrorReason, APIError>()({ return Match.typeTags<HttpClientError.HttpClientErrorReason, APIError>()({
DecodeError: cause => new APIResponseError({ cause, message: "Le corps de la Réponse ne peut être lu" }),
EmptyBodyError: cause => new APIResponseError({ cause, message: "Un corps vide ne peut être lu" }),
EncodeError: cause => new APIRequestError({ cause, message: "Le corps de la Requête ne peut être lu" }),
InvalidUrlError: cause => new APIRequestError({ cause, message: "L'URL de la Requête n'est pas valide" }),
StatusCodeError: cause =>
new APIResponseError({ cause, message: "Le code HTTP de la Réponse correspond à un échec" }),
TransportError: (cause): APIError => TransportError: (cause): APIError =>
new APIRequestError({ new APIRequestError({
cause, cause,
message: "Un problème réseau empêche l'exécution de la Requête", message: "Un problème réseau empêche l'exécution de la Requête",
}), }),
EncodeError: cause => new APIRequestError({ cause, message: "Le corps de la Requête ne peut être lu" }),
InvalidUrlError: cause => new APIRequestError({ cause, message: "L'URL de la Requête n'est pas valide" }),
StatusCodeError: cause =>
new APIResponseError({ cause, message: "Le code HTTP de la Réponse correspond à un échec" }),
DecodeError: cause => new APIResponseError({ cause, message: "Le corps de la Réponse ne peut être lu" }),
EmptyBodyError: cause => new APIResponseError({ cause, message: "Un corps vide ne peut être lu" }),
})(error.reason); })(error.reason);
} }
}; };
@ -109,7 +111,7 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
return yield* Effect.succeed(error); return yield* Effect.succeed(error);
}); });
const AddProductToCart = Effect.fn("AppClient.AddProductToCart")( const AddProductToCart = Effect.fn("APIClient.AddProductToCart")(
function*(nonce: string, productToAdd: CartProduct): Effect.fn.Return<APIResponse<WooCommerceCart>, APIError> { function*(nonce: string, productToAdd: CartProduct): Effect.fn.Return<APIResponse<WooCommerceCart>, APIError> {
const request = pipe( const request = pipe(
HttpClientRequest.post(`/wp-json/wc/store/cart/add-item`), HttpClientRequest.post(`/wp-json/wc/store/cart/add-item`),
@ -132,7 +134,28 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
}, },
); );
return { AddProductToCart }; const GetProducts = Effect.fn("APIClient.GetProducts")(
function*(nonce: string, authString: string, queryParams: GetProducts) {
const request = pipe(
HttpClientRequest.get(`/wp-json/wc/store/products`),
HttpClientRequest.setHeader("Nonce", nonce),
HttpClientRequest.bearerToken(authString),
// Le corps de la Requête a été validée en amont, on peut utiliser Unsafe.
HttpClientRequest.setUrlParams(queryParams),
);
const response = yield* pipe(
haikuHTTPClient.execute(request),
Effect.flatMap(HttpClientResponse.schemaBodyJson(Schema.Unknown)),
Effect.mapError(error => matchAPIError(error)),
Effect.tapError(error => printErrorAsSuccinctMessage(error)),
);
return response;
},
);
return { AddProductToCart, GetProducts };
}), }),
}) { }) {
static readonly Live = Layer.effect(this, this.make).pipe(Layer.provide(APIFetchClient)); static readonly Live = Layer.effect(this, this.make).pipe(Layer.provide(APIFetchClient));

View file

@ -31,7 +31,7 @@ const getAllSelectorFromParent =
pipe( pipe(
parent.querySelectorAll<E>(selector), parent.querySelectorAll<E>(selector),
// Convertis NodeListOf en Array. // Convertis NodeListOf en Array.
(xs: NodeListOf<E>) => Array.from<E>(xs), (xs: NodeListOf<E>) => [...xs],
(xs: Array<E>) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs), (xs: Array<E>) => Option.liftPredicate(FxArray.isReadonlyArrayNonEmpty)(xs),
); );

View file

@ -0,0 +1,9 @@
import { Effect } from "effect";
import { ATTRIBUT_CHARGEMENT, ATTRIBUT_DESACTIVE } from "../../scripts/constantes/dom.ts";
const setLoadingState = Effect.fn("setLoadingState")(function*(element: HTMLElement, isLoading: boolean) {
element.toggleAttribute(ATTRIBUT_DESACTIVE, isLoading);
element.toggleAttribute(ATTRIBUT_CHARGEMENT, isLoading);
});
export { setLoadingState };

View file

@ -1,18 +1,18 @@
import { Context, Effect, flow, Layer, pipe, Schedule, Schema } from "effect"; import { Context, Effect, flow, Layer, pipe, Schedule, Schema } from "effect";
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
import { HttpClientError } from "effect/unstable/http/HttpClientError"; import type { HttpClientError } from "effect/unstable/http/HttpClientError";
class Todo extends Schema.Class<Todo>("Todo")({
userId: Schema.Number,
id: Schema.Number,
title: Schema.String,
completed: Schema.Boolean,
}) {}
class FetchClientError extends Schema.TaggedErrorClass<FetchClientError>()("FetchClientError", { class FetchClientError extends Schema.TaggedErrorClass<FetchClientError>()("FetchClientError", {
cause: Schema.Defect, cause: Schema.Defect,
}) {} }) {}
class Todo extends Schema.Class<Todo>("Todo")({
completed: Schema.Boolean,
id: Schema.Number,
title: Schema.String,
userId: Schema.Number,
}) {}
class FetchClientExample extends Context.Service<FetchClientExample, { class FetchClientExample extends Context.Service<FetchClientExample, {
readonly allTodos: Effect.Effect<ReadonlyArray<Todo>, FetchClientError>; readonly allTodos: Effect.Effect<ReadonlyArray<Todo>, FetchClientError>;
createTodo(todo: Omit<Todo, "id">): Effect.Effect<Todo, FetchClientError>; createTodo(todo: Omit<Todo, "id">): Effect.Effect<Todo, FetchClientError>;
@ -75,8 +75,8 @@ class FetchClientExample extends Context.Service<FetchClientExample, {
return FetchClientExample.of({ return FetchClientExample.of({
allTodos, allTodos,
getTodo,
createTodo, createTodo,
getTodo,
}); });
}), }),
).pipe( ).pipe(

View file

@ -1,6 +1,6 @@
// oxlint-disable typescript/dot-notation
import type { SchemaError } from "effect/Schema"; import type { SchemaError } from "effect/Schema";
// oxlint-disable typescript/dot-notation
import { import {
Array as FxArray, Array as FxArray,
Console, Console,
@ -21,6 +21,7 @@ import type { APIError } from "../../scripts-effect/lib/api.ts";
import type { DetailEnsemble } from "./types.d.ts"; import type { DetailEnsemble } from "./types.d.ts";
import { APIClient } from "../../scripts-effect/lib/api.ts"; import { APIClient } from "../../scripts-effect/lib/api.ts";
import { setLoadingState } from "../../scripts-effect/lib/elements.ts";
import { CartProduct } from "../../scripts-effect/schemas/api.ts"; import { CartProduct } from "../../scripts-effect/schemas/api.ts";
import { WooCommerceCart } from "../../scripts-effect/schemas/cart.ts"; import { WooCommerceCart } from "../../scripts-effect/schemas/cart.ts";
import { Product, ProductVariation, ProductVariationAttribute } from "../../scripts-effect/schemas/product.ts"; import { Product, ProductVariation, ProductVariationAttribute } from "../../scripts-effect/schemas/product.ts";
@ -98,11 +99,6 @@ class ProductPageDOM extends Context.Service<ProductPageDOM>()(
); );
}); });
const setLoadingState = Effect.fn("setLoadingState")(function*(element: HTMLElement, isLoading: boolean) {
element.toggleAttribute(ATTRIBUT_DESACTIVE, isLoading);
element.toggleAttribute(ATTRIBUT_CHARGEMENT, isLoading);
});
const detailButtonClickHandler = Effect.fn("detailButtonClickHandler")( const detailButtonClickHandler = Effect.fn("detailButtonClickHandler")(
function*(evt: Event) { function*(evt: Event) {
// Empêche la pollution de l'historique de navigation // Empêche la pollution de l'historique de navigation
@ -232,7 +228,7 @@ class ProductPageDOM extends Context.Service<ProductPageDOM>()(
// Désactive les interactions le temps de la requête. // Désactive les interactions le temps de la requête.
yield* setLoadingState(AddToCartButton, true); yield* setLoadingState(AddToCartButton, true);
yield* SubscriptionRef.set(AddToCartButtonText, "Adding Product..."); yield* SubscriptionRef.set(AddToCartButtonText, "Adding Product...");
// lanceAnimationCycleLoading(AddToCartButton, 500); // LanceAnimationCycleLoading(AddToCartButton, 500);
// Exécute la Requête auprès du backend. // Exécute la Requête auprès du backend.
const newCart = yield* API.AddProductToCart(PageStates.nonce, requestBody); const newCart = yield* API.AddProductToCart(PageStates.nonce, requestBody);

View file

@ -7,7 +7,7 @@ class ProductPageMessages extends Context.Service<ProductPageMessages>()("haikua
const { AddToCartButton } = yield* ProductPageElements; const { AddToCartButton } = yield* ProductPageElements;
const AddToCartButtonText = yield* SubscriptionRef.make("Add to cart"); const AddToCartButtonText = yield* SubscriptionRef.make("Add to cart");
// const AddToCartErrorText = yield* SubscriptionRef.make<Option.Option<string>>(Option.none()); // Const AddToCartErrorText = yield* SubscriptionRef.make<Option.Option<string>>(Option.none());
const initAddToCartButtonUpdates = Effect.fn("initAddToCartButtonUpdates")(function*() { const initAddToCartButtonUpdates = Effect.fn("initAddToCartButtonUpdates")(function*() {
return yield* pipe( return yield* pipe(