@skapxd/eslint-opinionated 0.3.0 → 0.5.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 CHANGED
@@ -13,8 +13,9 @@ Cursor o Copilot.
13
13
 
14
14
  - **Una función por archivo:** un archivo con cinco helpers escondidos no pasa;
15
15
  la regla hasta te dibuja la carpeta sugerida con formato `tree`.
16
- - **Errores con `Result`:** una función `async` que puede fallar lo dice en su
17
- tipo de retorno (`Promise<Result<...>>`), no en una convención oral.
16
+ - **Errores con `Result`:** ningún `await` queda fuera del sistema de errores:
17
+ o llamas una función que retorna `Promise<Result<...>>` o envuelves la
18
+ operación en `trySafe`. Nada lanza sin que el tipo lo diga.
18
19
  - **Causa preservada:** al transformar un error de dominio, el `cause` original
19
20
  no puede desaparecer — type-aware, vía el checker de TypeScript.
20
21
  - **Hooks acotados:** un hook con demasiado estado deja de pasar como "solo un
@@ -251,6 +252,70 @@ hay errores, sale con código `1` (apto para CI). Como acota por **archivo
251
252
  completo**, también dispara las reglas estructurales (p. ej.
252
253
  `one-root-function-per-file`), que un filtrado por línea se perdería.
253
254
 
255
+ ## Cómo encaja todo: `@skapxd/result` + `ts-pattern`
256
+
257
+ Este plugin no es una colección de reglas sueltas: es el guardián de un
258
+ pipeline de errores donde cada pieza cierra un hueco que las otras dejan.
259
+
260
+ ```text
261
+ excepción ──trySafe──▶ Result ──map con cause──▶ error de dominio ──match()──▶ UI/respuesta
262
+ ```
263
+
264
+ | Pieza | Qué aporta | Regla que lo vigila |
265
+ | --- | --- | --- |
266
+ | `try/catch` prohibido | Los errores no viajan invisibles al tipo. | `skapxd/no-try-catch` |
267
+ | `.then/.catch` prohibido | Una sola forma de asincronía: `await`. | `skapxd/no-promise-chain` |
268
+ | `trySafe` (`@skapxd/result`) | La única puerta: lo que lanza se vuelve `Result`. | `skapxd/await-requires-result` |
269
+ | Errores de dominio con `cause` | Al traducir un error técnico, la causa sobrevive. | `skapxd/result-error-requires-cause` |
270
+ | Un solo contrato `Result` | Nada de `{ ok: ... }` caseros que fragmenten el sistema. | `skapxd/no-ad-hoc-ok-result` |
271
+ | `match()` (`ts-pattern`) | Consumo exhaustivo: el compilador exige manejar cada error. | `skapxd/prefer-ts-pattern` |
272
+
273
+ De punta a punta:
274
+
275
+ ```ts
276
+ import { Result, trySafe } from "@skapxd/result";
277
+ import { match } from "ts-pattern";
278
+
279
+ type UserError =
280
+ | { type: "NETWORK"; message: string; cause: unknown }
281
+ | { type: "NOT_FOUND"; message: string };
282
+
283
+ // 1. La frontera con el mundo que lanza: trySafe + errores de dominio.
284
+ async function getUser(id: string): Promise<Result<User, UserError>> {
285
+ const response = await trySafe(() => fetch(`/users/${id}`));
286
+
287
+ if (!response.ok) {
288
+ return Result.err({
289
+ cause: response.error, // result-error-requires-cause vigila esto
290
+ message: "No pude cargar el usuario.",
291
+ type: "NETWORK",
292
+ });
293
+ }
294
+
295
+ if (response.value.status === 404) {
296
+ return Result.err({ message: "El usuario no existe.", type: "NOT_FOUND" });
297
+ }
298
+
299
+ return trySafe(() => response.value.json());
300
+ }
301
+
302
+ // 2. El consumo: el await ya resuelve en Result (await-requires-result pasa)
303
+ // y match() obliga a manejar cada variante (prefer-ts-pattern).
304
+ const user = await getUser(id);
305
+
306
+ const label = match(user)
307
+ .with({ ok: true }, ({ value }) => value.name)
308
+ .with({ ok: false, error: { type: "NOT_FOUND" } }, () => "No existe")
309
+ .with({ ok: false, error: { type: "NETWORK" } }, () => "Reintenta")
310
+ .exhaustive();
311
+ ```
312
+
313
+ El resultado: ningún error puede escaparse (sin `try/catch` ni `.catch`, todo
314
+ pasa por `trySafe`), ningún error pierde su origen (siempre hay `cause` hasta
315
+ la excepción original), y ningún error queda sin manejar (el `.exhaustive()`
316
+ de ts-pattern no compila si falta una variante). Legibilidad y manejo de
317
+ errores dejan de depender de la disciplina del autor — humano o agente.
318
+
254
319
  ## Estructura del paquete
255
320
 
256
321
  ```text
@@ -302,6 +367,29 @@ export default [
302
367
  ];
303
368
  ```
304
369
 
370
+ El contrato del back es el mismo que el del front: todo `await` debe resolver
371
+ en un `Result` (`skapxd/await-requires-result`). Exigir además la firma
372
+ `Promise<Result<...>>` en cada función async
373
+ (`skapxd/async-functions-return-result`) está **apagado por defecto** — los
374
+ motivos están documentados en la sección de esa regla. Si quieres el contrato
375
+ duro, actívala encima del preset:
376
+
377
+ ```js
378
+ export default [
379
+ {
380
+ files: ["src/server/**/*.{ts,tsx}"],
381
+ ...skapxd.configs.shared.backend,
382
+ rules: {
383
+ ...skapxd.configs.shared.backend.rules,
384
+ "skapxd/async-functions-return-result": [
385
+ "error",
386
+ { checkMissingReturnType: true },
387
+ ],
388
+ },
389
+ },
390
+ ];
391
+ ```
392
+
305
393
  ### Frontend
306
394
 
307
395
  ```js
@@ -318,7 +406,7 @@ export default [
318
406
  El contrato del front: ninguna función está obligada a retornar `Result`, pero
319
407
  toda llamada asíncrona debe ir envuelta en `trySafe` — salvo que lo llamado ya
320
408
  retorne `Result`/`Promise<Result<...>>` (exención type-aware de
321
- `skapxd/await-requires-try-safe`). Aplica el preset a TODO el código del front
409
+ `skapxd/await-requires-result`). Aplica el preset a TODO el código del front
322
410
  (componentes, hooks, servicios), no solo a los componentes.
323
411
 
324
412
  ### Next.js
@@ -427,9 +515,9 @@ bloque con `linterOptions: { noInlineConfig: false }` para esos globs.
427
515
  | Regla | Qué protege |
428
516
  | --- | --- |
429
517
  | `skapxd/one-root-function-per-file` | Un archivo, una función top-level semántica. |
430
- | `skapxd/async-functions-return-result` | Funciones async de dominio deben retornar `Promise<Result<...>>`. |
518
+ | `skapxd/async-functions-return-result` | Funciones async de dominio deben retornar `Promise<Result<...>>`. **Apagada por defecto; opt-in** (ver motivos en su sección). |
431
519
  | `skapxd/result-error-requires-cause` | Un `Result.err` derivado debe preservar `cause: result.error`. |
432
- | `skapxd/await-requires-try-safe` | Los `await` deben estar protegidos por `trySafe`, salvo que lo awaiteado ya retorne `Result`. La activa el preset `shared.frontend`. |
520
+ | `skapxd/await-requires-result` | Todo `await` debe resolver en un `Result`: o la función llamada retorna `Promise<Result<...>>` (preferido) o se envuelve en `trySafe`. **Obligatoria en todos los presets tipados.** |
433
521
  | `skapxd/no-ad-hoc-ok-result` | Evita contratos `{ ok: ... }` hechos a mano en async exports. |
434
522
  | `skapxd/max-hook-size` | Marca hooks grandes o con demasiados `useState`. |
435
523
  | `skapxd/jsx-return-name-pascal-case` | Funciones que retornan JSX deben nombrarse como componentes. |
@@ -465,6 +553,39 @@ sugiere helpers al lado.
465
553
 
466
554
  ### `skapxd/async-functions-return-result`
467
555
 
556
+ > **Apagada por defecto desde v0.5.0** — ningún preset la activa. La regla
557
+ > obligatoria del sistema de errores es `skapxd/await-requires-result`.
558
+ >
559
+ > **Por qué se tomó esta decisión:**
560
+ >
561
+ > 1. **`await-requires-result` produce el mismo estado final con mejor
562
+ > ergonomía.** Si ningún `await` puede quedar sin `Result`, envolver con
563
+ > `trySafe` inline una y otra vez se vuelve incómodo rápido — la presión
564
+ > natural es extraer funciones que retornen `Promise<Result<...>>` con
565
+ > errores de dominio. Se llega a las mismas firmas que esta regla imponía,
566
+ > pero por gravedad, no por decreto.
567
+ > 2. **Imponer la firma choca con los bordes del framework.** Los handlers
568
+ > `GET/POST` de Next, `page.tsx`, los callbacks de librerías: sus firmas no
569
+ > son tuyas. Esta regla necesitaba listas de excepciones
570
+ > (`allowFilePatterns`, `allowNamePatterns`) para convivir con eso;
571
+ > `await-requires-result` no necesita ninguna, porque envolver un `await`
572
+ > es compatible con cualquier firma.
573
+ > 3. **Adopción incremental.** En un codebase existente, exigir la firma en
574
+ > cada función async lo rompe todo de golpe. Exigir `Result` en los `await`
575
+ > permite migrar llamada por llamada.
576
+ >
577
+ > Sigue disponible para quien quiera endurecer el contrato (p. ej. un backend
578
+ > nuevo donde todas las firmas son tuyas):
579
+ >
580
+ > ```js
581
+ > rules: {
582
+ > "skapxd/async-functions-return-result": ["error", {
583
+ > checkMissingReturnType: true,
584
+ > resultTypeNames: ["Result", "ResultValue", "SafeResult"],
585
+ > }],
586
+ > }
587
+ > ```
588
+
468
589
  Obliga a que funciones async en dominios configurados declaren un retorno como:
469
590
 
470
591
  ```ts
@@ -484,7 +605,8 @@ type Result<T, E> = ...; // ❌ Result ajeno
484
605
  async function no(): Promise<Result<number, Error>> {} // se reporta
485
606
  ```
486
607
 
487
- > Requiere `projectService` (los presets `backend` y `next/server` ya lo activan).
608
+ > Requiere `projectService` (actívalo en `languageOptions.parserOptions` o
609
+ > apóyate en un preset tipado del plugin, que ya lo trae).
488
610
  > Sin información de tipos cae a una comprobación por nombre (`resultTypeNames`),
489
611
  > menos estricta.
490
612
 
@@ -507,27 +629,60 @@ valor del guard y `Result.err` vienen de `@skapxd/result`. Por eso funciona con
507
629
  aliases, re-exports y tipos inferidos, sin depender solo del nombre importado en
508
630
  el archivo.
509
631
 
510
- ### `skapxd/await-requires-try-safe`
632
+ ### `skapxd/await-requires-result`
511
633
 
512
- > La activa el preset `shared.frontend` en todo el front: componentes, hooks,
513
- > handlers y servicios. El contrato queda así: ninguna función está obligada
514
- > a retornar `Result`, pero toda llamada asíncrona debe ir en `trySafe` — salvo
515
- > que lo llamado ya retorne `Result` (ver exención más abajo). Para activarla
516
- > en otros globs, añádela tú mismo:
634
+ > **Es la regla obligatoria del sistema de errores**: la activan todos los
635
+ > presets tipados (`shared.frontend`, `shared.backend`, `next/server`,
636
+ > `astro/typescript`). El contrato queda así: ninguna función está obligada
637
+ > a retornar `Result` (eso es `async-functions-return-result`, apagada por
638
+ > defecto), pero todo `await` debe **resolver** en uno. Para activarla en
639
+ > otros globs, añádela tú mismo:
517
640
  >
518
641
  > ```js
519
642
  > rules: {
520
- > "skapxd/await-requires-try-safe": ["error", {
643
+ > "skapxd/await-requires-result": ["error", {
521
644
  > trySafeCallNames: ["trySafe"],
522
645
  > allowFilePatterns: [],
523
646
  > }],
524
647
  > }
525
648
  > ```
649
+ >
650
+ > (`skapxd/await-requires-try-safe` es el nombre anterior; sigue funcionando
651
+ > como alias deprecado y se eliminará en una versión futura.)
652
+
653
+ Hay dos caminos válidos, y la regla recomienda el primero:
526
654
 
527
- Obliga a proteger operaciones `await` con `trySafe`:
655
+ **1. El camino preferido: extrae la operación a una función que retorne
656
+ `Promise<Result<...>>`** y modela ahí los errores de dominio. El `trySafe` vive
657
+ dentro de esa función, en la frontera con el código que lanza, y el resto del
658
+ código habla en errores con significado:
528
659
 
529
660
  ```ts
530
- const result = await trySafe(() => client.execute({...}));
661
+ async function getUser(id: string): Promise<Result<User, UserError>> {
662
+ const response = await trySafe(() => fetch(`/users/${id}`));
663
+
664
+ if (!response.ok) {
665
+ return Result.err({
666
+ cause: response.error,
667
+ message: "No pude cargar el usuario.",
668
+ type: "USER_FETCH_FAILED",
669
+ });
670
+ }
671
+
672
+ return trySafe(() => response.value.json());
673
+ }
674
+
675
+ // En el componente: ya resuelve en Result, pasa directo.
676
+ const user = await getUser(id); // ✅
677
+ ```
678
+
679
+ La detección es type-aware: la regla resuelve el símbolo hasta `@skapxd/result`,
680
+ así que un `Result` casero (homónimo, de otra librería) no exime.
681
+
682
+ **2. La alternativa rápida: envuelve el `await` en `trySafe` ahí mismo:**
683
+
684
+ ```ts
685
+ const result = await trySafe(() => client.execute({...})); // ✅
531
686
  ```
532
687
 
533
688
  o dentro de un callback:
@@ -539,18 +694,9 @@ const result = await trySafe(async () => {
539
694
  });
540
695
  ```
541
696
 
542
- Si lo que se awaitea ya retorna `Result`/`Promise<Result<...>>` de
543
- `@skapxd/result`, la regla no exige `trySafe`: los errores ya están modelados en
544
- el tipo y envolverlo sería redundante.
545
-
546
- ```ts
547
- declare function getUser(): Promise<Result<User, Error>>;
548
-
549
- const result = await getUser(); // ✅ ya es un Result, no necesita trySafe
550
- ```
551
-
552
- Esta exención es type-aware: un `Result` casero (que no venga de
553
- `@skapxd/result`) no exime.
697
+ Sirve para código de pegamento, pero deja el error sin modelar (`Result<T,
698
+ unknown>`). Cuando la misma operación se repite o el error importa, el mensaje
699
+ de la regla empuja hacia el camino 1.
554
700
 
555
701
  ### `skapxd/max-hook-size`
556
702
 
@@ -628,8 +774,9 @@ const result = await trySafe(() => client.execute(query)); // ✅
628
774
  if (!result.ok) return Result.err({ cause: result.error, type: "DB_FAILED" });
629
775
  ```
630
776
 
631
- Se complementa con `result-error-requires-cause` (preservar la causa) y, si la
632
- activas, con `await-requires-try-safe` (que además exige envolver cada `await`).
777
+ Se complementa con `result-error-requires-cause` (preservar la causa) y con
778
+ `await-requires-result` (obligatoria en los presets tipados: cada `await`
779
+ resuelve en un `Result`).
633
780
 
634
781
  ### `skapxd/no-promise-chain`
635
782
 
@@ -702,7 +849,7 @@ cierran el hueco.
702
849
 
703
850
  En cambio, las reglas atadas a `@skapxd/result`
704
851
  (`async-functions-return-result`, `result-error-requires-cause`,
705
- `await-requires-try-safe`) **no** dependen de nombres: resuelven el símbolo hasta
852
+ `await-requires-result`) **no** dependen de nombres: resuelven el símbolo hasta
706
853
  el paquete real (vía el `name` de su `package.json`), así que funcionan con
707
854
  alias, re-exports y en monorepos.
708
855
 
@@ -47,6 +47,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
47
47
  skapxd: unknown;
48
48
  };
49
49
  rules: {
50
+ "skapxd/await-requires-result": string;
50
51
  "skapxd/result-error-requires-cause": string;
51
52
  };
52
53
  })[];
@@ -47,6 +47,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
47
47
  skapxd: unknown;
48
48
  };
49
49
  rules: {
50
+ "skapxd/await-requires-result": string;
50
51
  "skapxd/result-error-requires-cause": string;
51
52
  };
52
53
  })[];
@@ -92,6 +92,7 @@ function createAstroConfigs(pluginReference) {
92
92
  name: "skapxd/astro/typescript",
93
93
  plugins: { skapxd: pluginReference },
94
94
  rules: {
95
+ "skapxd/await-requires-result": "error",
95
96
  "skapxd/result-error-requires-cause": "error"
96
97
  }
97
98
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/astro/index.ts","../../src/shared/configs/base-rules.ts","../../src/shared/configs/create-base-language-options.ts","../../src/shared/configs/create-typed-language-options.ts","../../src/astro/configs.ts"],"sourcesContent":["export { createAstroConfigs } from \"./configs\";\n","export const baseRules = {\n \"skapxd/no-ad-hoc-ok-result\": \"error\",\n \"skapxd/no-deep-relative-imports\": \"error\",\n \"skapxd/no-promise-chain\": \"error\",\n \"skapxd/no-try-catch\": \"error\",\n \"skapxd/one-root-function-per-file\": \"error\",\n \"skapxd/prefer-ts-pattern\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n};\n","import tseslint from \"typescript-eslint\";\n\n// Variante sin type-checking: solo el parser, para presets que aplican a TS\n// pero no necesitan projectService (base, package, next/base, next/react).\nexport function createBaseLanguageOptions() {\n return {\n parser: tseslint.parser,\n };\n}\n","import tseslint from \"typescript-eslint\";\n\nexport function createTypedLanguageOptions() {\n return {\n // Sin el parser explícito, un consumidor que use solo estos presets\n // obtiene \"Parsing error\" en cada archivo TS (espree no parsea TS).\n parser: tseslint.parser,\n parserOptions: {\n projectService: true,\n },\n };\n}\n","import {\n baseRules,\n createBaseLanguageOptions,\n createTypedLanguageOptions,\n} from \"#/shared/configs\";\n\nexport function createAstroConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return [\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: baseLanguageOptions,\n name: \"skapxd/astro/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n // Los .astro no llevan parser propio: lo aporta eslint-plugin-astro,\n // que el consumidor debe tener configurado.\n {\n files: [\"src/**/*.astro\"],\n name: \"skapxd/astro/astro-files\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: typedLanguageOptions,\n name: \"skapxd/astro/typescript\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/result-error-requires-cause\": \"error\",\n },\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,YAAY;AAAA,EACvB,8BAA8B;AAAA,EAC9B,mCAAmC;AAAA,EACnC,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,sCAAsC;AACxC;;;ACRA,+BAAqB;AAId,SAAS,4BAA4B;AAC1C,SAAO;AAAA,IACL,QAAQ,yBAAAA,QAAS;AAAA,EACnB;AACF;;;ACRA,IAAAC,4BAAqB;AAEd,SAAS,6BAA6B;AAC3C,SAAO;AAAA;AAAA;AAAA,IAGL,QAAQ,0BAAAC,QAAS;AAAA,IACjB,eAAe;AAAA,MACb,gBAAgB;AAAA,IAClB;AAAA,EACF;AACF;;;ACLO,SAAS,mBAAmB,iBAA0B;AAC3D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA;AAAA;AAAA,IAGA;AAAA,MACE,OAAO,CAAC,gBAAgB;AAAA,MACxB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,sCAAsC;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;","names":["tseslint","import_typescript_eslint","tseslint"]}
1
+ {"version":3,"sources":["../../src/astro/index.ts","../../src/shared/configs/base-rules.ts","../../src/shared/configs/create-base-language-options.ts","../../src/shared/configs/create-typed-language-options.ts","../../src/astro/configs.ts"],"sourcesContent":["export { createAstroConfigs } from \"./configs\";\n","export const baseRules = {\n \"skapxd/no-ad-hoc-ok-result\": \"error\",\n \"skapxd/no-deep-relative-imports\": \"error\",\n \"skapxd/no-promise-chain\": \"error\",\n \"skapxd/no-try-catch\": \"error\",\n \"skapxd/one-root-function-per-file\": \"error\",\n \"skapxd/prefer-ts-pattern\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n};\n","import tseslint from \"typescript-eslint\";\n\n// Variante sin type-checking: solo el parser, para presets que aplican a TS\n// pero no necesitan projectService (base, package, next/base, next/react).\nexport function createBaseLanguageOptions() {\n return {\n parser: tseslint.parser,\n };\n}\n","import tseslint from \"typescript-eslint\";\n\nexport function createTypedLanguageOptions() {\n return {\n // Sin el parser explícito, un consumidor que use solo estos presets\n // obtiene \"Parsing error\" en cada archivo TS (espree no parsea TS).\n parser: tseslint.parser,\n parserOptions: {\n projectService: true,\n },\n };\n}\n","import {\n baseRules,\n createBaseLanguageOptions,\n createTypedLanguageOptions,\n} from \"#/shared/configs\";\n\nexport function createAstroConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return [\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: baseLanguageOptions,\n name: \"skapxd/astro/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n // Los .astro no llevan parser propio: lo aporta eslint-plugin-astro,\n // que el consumidor debe tener configurado.\n {\n files: [\"src/**/*.astro\"],\n name: \"skapxd/astro/astro-files\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: typedLanguageOptions,\n name: \"skapxd/astro/typescript\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/await-requires-result\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n },\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,YAAY;AAAA,EACvB,8BAA8B;AAAA,EAC9B,mCAAmC;AAAA,EACnC,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,sCAAsC;AACxC;;;ACRA,+BAAqB;AAId,SAAS,4BAA4B;AAC1C,SAAO;AAAA,IACL,QAAQ,yBAAAA,QAAS;AAAA,EACnB;AACF;;;ACRA,IAAAC,4BAAqB;AAEd,SAAS,6BAA6B;AAC3C,SAAO;AAAA;AAAA;AAAA,IAGL,QAAQ,0BAAAC,QAAS;AAAA,IACjB,eAAe;AAAA,MACb,gBAAgB;AAAA,IAClB;AAAA,EACF;AACF;;;ACLO,SAAS,mBAAmB,iBAA0B;AAC3D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA;AAAA;AAAA,IAGA;AAAA,MACE,OAAO,CAAC,gBAAgB;AAAA,MACxB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,gCAAgC;AAAA,QAChC,sCAAsC;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;","names":["tseslint","import_typescript_eslint","tseslint"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createAstroConfigs
3
- } from "../chunk-P32EECMV.mjs";
4
- import "../chunk-TFGBNVXP.mjs";
3
+ } from "../chunk-7H7DRTP2.mjs";
4
+ import "../chunk-RWQGOTVZ.mjs";
5
5
  export {
6
6
  createAstroConfigs
7
7
  };
@@ -2,7 +2,7 @@ import {
2
2
  baseRules,
3
3
  createBaseLanguageOptions,
4
4
  createTypedLanguageOptions
5
- } from "./chunk-TFGBNVXP.mjs";
5
+ } from "./chunk-RWQGOTVZ.mjs";
6
6
 
7
7
  // src/astro/configs.ts
8
8
  function createAstroConfigs(pluginReference) {
@@ -30,6 +30,7 @@ function createAstroConfigs(pluginReference) {
30
30
  name: "skapxd/astro/typescript",
31
31
  plugins: { skapxd: pluginReference },
32
32
  rules: {
33
+ "skapxd/await-requires-result": "error",
33
34
  "skapxd/result-error-requires-cause": "error"
34
35
  }
35
36
  }
@@ -39,4 +40,4 @@ function createAstroConfigs(pluginReference) {
39
40
  export {
40
41
  createAstroConfigs
41
42
  };
42
- //# sourceMappingURL=chunk-P32EECMV.mjs.map
43
+ //# sourceMappingURL=chunk-7H7DRTP2.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/astro/configs.ts"],"sourcesContent":["import {\n baseRules,\n createBaseLanguageOptions,\n createTypedLanguageOptions,\n} from \"#/shared/configs\";\n\nexport function createAstroConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return [\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: baseLanguageOptions,\n name: \"skapxd/astro/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n // Los .astro no llevan parser propio: lo aporta eslint-plugin-astro,\n // que el consumidor debe tener configurado.\n {\n files: [\"src/**/*.astro\"],\n name: \"skapxd/astro/astro-files\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: typedLanguageOptions,\n name: \"skapxd/astro/typescript\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/result-error-requires-cause\": \"error\",\n },\n },\n ];\n}\n"],"mappings":";;;;;;;AAMO,SAAS,mBAAmB,iBAA0B;AAC3D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA;AAAA;AAAA,IAGA;AAAA,MACE,OAAO,CAAC,gBAAgB;AAAA,MACxB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,sCAAsC;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/astro/configs.ts"],"sourcesContent":["import {\n baseRules,\n createBaseLanguageOptions,\n createTypedLanguageOptions,\n} from \"#/shared/configs\";\n\nexport function createAstroConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return [\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: baseLanguageOptions,\n name: \"skapxd/astro/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n // Los .astro no llevan parser propio: lo aporta eslint-plugin-astro,\n // que el consumidor debe tener configurado.\n {\n files: [\"src/**/*.astro\"],\n name: \"skapxd/astro/astro-files\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: typedLanguageOptions,\n name: \"skapxd/astro/typescript\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/await-requires-result\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n },\n },\n ];\n}\n"],"mappings":";;;;;;;AAMO,SAAS,mBAAmB,iBAA0B;AAC3D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA;AAAA;AAAA,IAGA;AAAA,MACE,OAAO,CAAC,gBAAgB;AAAA,MACxB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,gCAAgC;AAAA,QAChC,sCAAsC;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  baseRules,
3
3
  createBaseLanguageOptions,
4
4
  createTypedLanguageOptions
5
- } from "./chunk-TFGBNVXP.mjs";
5
+ } from "./chunk-RWQGOTVZ.mjs";
6
6
 
7
7
  // src/next/configs.ts
8
8
  function createNextConfigs(pluginReference) {
@@ -22,21 +22,11 @@ function createNextConfigs(pluginReference) {
22
22
  plugins: { skapxd: pluginReference },
23
23
  rules: {
24
24
  ...baseRules,
25
- "skapxd/async-functions-return-result": [
26
- "error",
27
- {
28
- allowFilePatterns: [
29
- "/(route|page|layout|template|loading|error|not-found)\\.tsx?$"
30
- ],
31
- allowNamePatterns: [
32
- "^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)$",
33
- "^handle(Get|Post|Put|Patch|Delete|Head|Options)$",
34
- "^start$"
35
- ],
36
- checkMissingReturnType: true,
37
- resultTypeNames: ["Result", "ResultValue", "SafeResult"]
38
- }
39
- ]
25
+ // Obligatoria: todo await resuelve en Result. A diferencia de
26
+ // async-functions-return-result (apagada por defecto), no necesita
27
+ // excepciones para los entrypoints de Next: envolver un await en
28
+ // trySafe es compatible con cualquier firma que imponga el framework.
29
+ "skapxd/await-requires-result": "error"
40
30
  }
41
31
  },
42
32
  {
@@ -63,4 +53,4 @@ function createNextConfigs(pluginReference) {
63
53
  export {
64
54
  createNextConfigs
65
55
  };
66
- //# sourceMappingURL=chunk-SUNOHZFS.mjs.map
56
+ //# sourceMappingURL=chunk-QJQ2E2NG.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/next/configs.ts"],"sourcesContent":["import {\n baseRules,\n createBaseLanguageOptions,\n createTypedLanguageOptions,\n} from \"#/shared/configs\";\n\nexport function createNextConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return [\n {\n languageOptions: baseLanguageOptions,\n name: \"skapxd/next/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n {\n files: [\"src/app/api/**/*.{ts,tsx}\", \"src/server/**/*.{ts,tsx}\"],\n languageOptions: typedLanguageOptions,\n name: \"skapxd/next/server\",\n plugins: { skapxd: pluginReference },\n rules: {\n ...baseRules,\n // Obligatoria: todo await resuelve en Result. A diferencia de\n // async-functions-return-result (apagada por defecto), no necesita\n // excepciones para los entrypoints de Next: envolver un await en\n // trySafe es compatible con cualquier firma que imponga el framework.\n \"skapxd/await-requires-result\": \"error\",\n },\n },\n {\n files: [\"**/*.tsx\"],\n languageOptions: baseLanguageOptions,\n name: \"skapxd/next/react\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/jsx-return-name-pascal-case\": \"error\",\n \"skapxd/no-functions-inside-components\": \"error\",\n \"skapxd/no-jsx-ternary-null\": \"error\",\n \"skapxd/max-hook-size\": [\n \"error\",\n {\n maxLines: 120,\n maxUseState: 1,\n },\n ],\n },\n },\n ];\n}\n"],"mappings":";;;;;;;AAMO,SAAS,kBAAkB,iBAA0B;AAC1D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL;AAAA,MACE,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,OAAO,CAAC,6BAA6B,0BAA0B;AAAA,MAC/D,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA,QAKH,gCAAgC;AAAA,MAClC;AAAA,IACF;AAAA,IACA;AAAA,MACE,OAAO,CAAC,UAAU;AAAA,MAClB,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,sCAAsC;AAAA,QACtC,yCAAyC;AAAA,QACzC,8BAA8B;AAAA,QAC9B,wBAAwB;AAAA,UACtB;AAAA,UACA;AAAA,YACE,UAAU;AAAA,YACV,aAAa;AAAA,UACf;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -593,8 +593,8 @@ var asyncFunctionsReturnResult = {
593
593
  description: "Exige Promise<Result<...>> en funciones async de dominio."
594
594
  },
595
595
  messages: {
596
- missingReturnType: "La funcion async `{{name}}` debe declarar Promise<Result<...>> como tipo de retorno.",
597
- invalidReturnType: "La funcion async `{{name}}` debe retornar Promise<Result<...>> para modelar errores de forma explicita."
596
+ missingReturnType: "La funcion async `{{name}}` debe declarar Promise<Result<...>> como tipo de retorno: trySafe en la frontera, errores de dominio con `cause`, y el consumidor decide con `match()` de ts-pattern.",
597
+ invalidReturnType: "La funcion async `{{name}}` debe retornar Promise<Result<...>> para modelar errores de forma explicita: trySafe en la frontera, errores de dominio con `cause`, y el consumidor decide con `match()` de ts-pattern."
598
598
  },
599
599
  schema: [
600
600
  {
@@ -780,7 +780,7 @@ var noAdHocOkResult = {
780
780
  description: "Prohibe retornar contratos ad hoc con ok en funciones async exportadas."
781
781
  },
782
782
  messages: {
783
- adHocOkResult: "No retornes objetos ad hoc con `ok` desde la funcion async `{{name}}`. Usa Result.ok(...) / Result.err(...) con un error discriminado `{ type: ... }`."
783
+ adHocOkResult: "No retornes objetos ad hoc con `ok` desde la funcion async `{{name}}`. Usa Result.ok(...) / Result.err(...) de @skapxd/result con un error discriminado `{ type: ... }`: un unico contrato Result permite consumir cada variante con `match()` de ts-pattern y `.exhaustive()`."
784
784
  },
785
785
  schema: []
786
786
  },
@@ -807,8 +807,8 @@ var noAdHocOkResult = {
807
807
  }
808
808
  };
809
809
 
810
- // src/utils/get-await-requires-try-safe-options.ts
811
- function getAwaitRequiresTrySafeOptions(options = {}) {
810
+ // src/utils/get-await-requires-result-options.ts
811
+ function getAwaitRequiresResultOptions(options = {}) {
812
812
  return {
813
813
  allowFilePatterns: options.allowFilePatterns ?? [],
814
814
  trySafeCallNames: options.trySafeCallNames ?? ["trySafe"]
@@ -895,15 +895,15 @@ function isTrySafeCall(node, trySafeCallNames) {
895
895
  return node?.type === "CallExpression" && isCalleeNamed(node.callee, trySafeCallNames);
896
896
  }
897
897
 
898
- // src/rules/await-requires-try-safe.ts
899
- var awaitRequiresTrySafe = {
898
+ // src/rules/await-requires-result.ts
899
+ var awaitRequiresResult = {
900
900
  meta: {
901
901
  type: "problem",
902
902
  docs: {
903
- description: "Exige que los await esten protegidos por trySafe."
903
+ description: "Exige que todo await resuelva en un Result: una funcion que retorne Promise<Result<...>> o trySafe en el sitio."
904
904
  },
905
905
  messages: {
906
- unprotectedAwait: "El await dentro de `{{name}}` no esta protegido por trySafe. Envuelve la operacion asi: `{{suggestion}}`."
906
+ awaitWithoutResult: "El await dentro de `{{name}}` no resuelve en un Result. Mejor opcion: extrae la operacion a una funcion que retorne Promise<Result<...>> y modela ahi los errores de dominio con `{ type, message, cause }` (el trySafe vive dentro de esa funcion). Alternativa: envuelvela aqui mismo: `{{suggestion}}`. En ambos casos, consume el Result con `match()` de ts-pattern."
907
907
  },
908
908
  schema: [
909
909
  {
@@ -923,7 +923,7 @@ var awaitRequiresTrySafe = {
923
923
  ]
924
924
  },
925
925
  create(context) {
926
- const options = getAwaitRequiresTrySafeOptions(context.options[0]);
926
+ const options = getAwaitRequiresResultOptions(context.options[0]);
927
927
  const filename = context.filename ?? context.getFilename();
928
928
  const sourceCode = context.sourceCode ?? context.getSourceCode();
929
929
  const typeContext = getTypeContext(context);
@@ -958,7 +958,7 @@ var awaitRequiresTrySafe = {
958
958
  name: getAwaitScopeName(node),
959
959
  suggestion: getTrySafeAwaitSuggestion(node.argument, sourceCode)
960
960
  },
961
- messageId: "unprotectedAwait",
961
+ messageId: "awaitWithoutResult",
962
962
  node
963
963
  });
964
964
  }
@@ -1115,7 +1115,7 @@ var resultErrorRequiresCause = {
1115
1115
  description: "Exige preservar result.error como cause cuando una funcion que retorna Result transforma un Result fallido en Result.err."
1116
1116
  },
1117
1117
  messages: {
1118
- missingCause: "El error de `{{name}}` ya existe como `{{name}}.error`. Preservalo en Result.err con `cause: {{name}}.error`."
1118
+ missingCause: "El error de `{{name}}` ya existe como `{{name}}.error`. Preservalo en Result.err con `cause: {{name}}.error`: la cadena de causas conecta el error de dominio con la excepcion original que capturo `trySafe`; sin ella el debugging pierde el contexto."
1119
1119
  },
1120
1120
  schema: []
1121
1121
  },
@@ -1382,7 +1382,7 @@ var noTryCatch = {
1382
1382
  description: "Prohibe try/catch; usa trySafe de @skapxd/result para modelar el error como Result."
1383
1383
  },
1384
1384
  messages: {
1385
- noTryCatch: "Usa `trySafe` de @skapxd/result en lugar de try/catch. El error queda modelado como Result en vez de saltar como excepcion."
1385
+ noTryCatch: "Usa `trySafe` de @skapxd/result en lugar de try/catch: el error queda modelado como Result en vez de saltar como excepcion. Luego mapealo a un error de dominio `{ type, message, cause }` y consumelo con `match()` de ts-pattern."
1386
1386
  },
1387
1387
  schema: []
1388
1388
  },
@@ -1406,8 +1406,8 @@ var preferTsPattern = {
1406
1406
  description: "Prefiere match() de ts-pattern sobre switch/case y ternarios anidados."
1407
1407
  },
1408
1408
  messages: {
1409
- noSwitch: "Usa `match()` de ts-pattern en lugar de switch/case para un control de flujo exhaustivo y tipado.",
1410
- noNestedTernary: "Usa `match()` de ts-pattern en lugar de ternarios anidados; mejora la legibilidad y la exhaustividad."
1409
+ noSwitch: "Usa `match()` de ts-pattern en lugar de switch/case para un control de flujo exhaustivo y tipado. Es la pieza que cierra el sistema de errores: un Result con errores `{ type: ... }` se consume con una rama `.with()` por variante y `.exhaustive()` garantiza que ninguna quede sin manejar.",
1410
+ noNestedTernary: "Usa `match()` de ts-pattern en lugar de ternarios anidados; mejora la legibilidad y `.exhaustive()` obliga a cubrir todos los casos."
1411
1411
  },
1412
1412
  schema: []
1413
1413
  },
@@ -1483,7 +1483,7 @@ var noPromiseChain = {
1483
1483
  description: "Prohibe encadenar .then/.catch/.finally en promesas; usa await (envuelto en trySafe)."
1484
1484
  },
1485
1485
  messages: {
1486
- noPromiseChain: "No encadenes `.{{method}}()` en una promesa. La unica forma de tratar funciones asincronas es `await` (envuelto en `trySafe`)."
1486
+ noPromiseChain: "No encadenes `.{{method}}()` en una promesa. La unica forma de tratar funciones asincronas es `await`: o la funcion llamada ya retorna Promise<Result<...>> o envuelve la llamada en `trySafe` de @skapxd/result."
1487
1487
  },
1488
1488
  schema: [
1489
1489
  {
@@ -1533,7 +1533,16 @@ var rules = {
1533
1533
  "jsx-return-name-pascal-case": jsxReturnNamePascalCase,
1534
1534
  "async-functions-return-result": asyncFunctionsReturnResult,
1535
1535
  "no-ad-hoc-ok-result": noAdHocOkResult,
1536
- "await-requires-try-safe": awaitRequiresTrySafe,
1536
+ "await-requires-result": awaitRequiresResult,
1537
+ // Alias deprecado del nombre anterior; se elimina en una versión futura.
1538
+ "await-requires-try-safe": {
1539
+ ...awaitRequiresResult,
1540
+ meta: {
1541
+ ...awaitRequiresResult.meta,
1542
+ deprecated: true,
1543
+ replacedBy: ["skapxd/await-requires-result"]
1544
+ }
1545
+ },
1537
1546
  "result-error-requires-cause": resultErrorRequiresCause,
1538
1547
  "max-hook-size": maxHookSize,
1539
1548
  "no-deep-relative-imports": noDeepRelativeImports,
@@ -1547,4 +1556,4 @@ var rules = {
1547
1556
  export {
1548
1557
  rules
1549
1558
  };
1550
- //# sourceMappingURL=chunk-RP7BOODV.mjs.map
1559
+ //# sourceMappingURL=chunk-QNSHGNVQ.mjs.map