@leo-h/create-nodejs-app 1.0.52 → 1.0.53

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leo-h/create-nodejs-app",
3
- "version": "1.0.52",
3
+ "version": "1.0.53",
4
4
  "packageManager": "pnpm@9.15.9",
5
5
  "author": "Leonardo Henrique <leonardo0507.business@gmail.com>",
6
6
  "description": "Create a modern Node.js app with TypeScript using one command.",
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "*.{js,ts,json,yaml,yml,md}": "prettier --write --cache",
3
- "*.{js,ts}": ["eslint --max-warnings 0 --fix --cache --no-ignore"]
3
+ "*.{js,ts}": ["eslint --max-warnings 0 --fix --cache --no-warn-ignored"]
4
4
  }
@@ -0,0 +1,20 @@
1
+ import { BaseError } from "@/http/errors";
2
+ import { Constructor } from "type-fest";
3
+
4
+ export type MiddlewareHandlerOutput<
5
+ Errors extends ReadonlyArray<Constructor<BaseError>>,
6
+ > = Promise<MiddlewareLeft<InstanceType<Errors[number]>> | void>;
7
+
8
+ export const middleware = Object.freeze({
9
+ left,
10
+ } as const);
11
+
12
+ class MiddlewareLeft<Error extends BaseError> {
13
+ public constructor(public readonly error: Error) {
14
+ throw error;
15
+ }
16
+ }
17
+
18
+ function left<Error extends BaseError>(error: Error) {
19
+ return new MiddlewareLeft<Error>(error);
20
+ }
@@ -17,3 +17,8 @@ export type ZodUnrestrictShape<Target> = {
17
17
  export type ZodUnrestrictFieldsShape<Target> = {
18
18
  [K in keyof Target | (string & {})]?: ZodTypeAny;
19
19
  };
20
+
21
+ export type ZodUnrestrictEnumFromLiterals<Target extends string> = [
22
+ Target,
23
+ ...Target[],
24
+ ];
@@ -1,5 +1,5 @@
1
1
  import { FastifyZodInstance } from "@/@types/fastify";
2
- import { createControllerResponseSchema } from "@/core/create-controller-response-schema";
2
+ import { createControllerResponseSchema } from "@/core/fastify/create-controller-response-schema";
3
3
  import { z } from "zod";
4
4
  import { InternalServerError, ValidationError } from "../errors";
5
5
  import {
@@ -1,5 +1,5 @@
1
1
  import { FastifyZodInstance } from "@/@types/fastify";
2
- import { createControllerResponseSchema } from "@/core/create-controller-response-schema";
2
+ import { createControllerResponseSchema } from "@/core/fastify/create-controller-response-schema";
3
3
  import { z } from "zod";
4
4
  import { InternalServerError, ValidationError } from "../errors";
5
5
 
@@ -1,4 +1,6 @@
1
1
  {
2
2
  "*.{js,jsx,ts,tsx,css,scss,html,json,yaml,yml,md}": "prettier --write --cache",
3
- "*.{js,jsx,ts,tsx}": ["eslint --max-warnings 0 --fix --cache"]
3
+ "*.{js,jsx,ts,tsx}": [
4
+ "eslint --max-warnings 0 --fix --cache --no-ignore --no-warn-ignored"
5
+ ]
4
6
  }
@@ -0,0 +1,9 @@
1
+ {
2
+ "editor.formatOnSave": true,
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
4
+ "editor.codeActionsOnSave": {
5
+ "source.organizeImports": "explicit",
6
+ "source.fixAll.eslint": "explicit"
7
+ },
8
+ "css.lint.unknownAtRules": "ignore"
9
+ }
@@ -6,9 +6,9 @@ export class ApiError {
6
6
  ) {}
7
7
  }
8
8
 
9
- export class ApiUnexpectedResponseError {
9
+ export class ApiUnknownError {
10
10
  static readonly message =
11
- "Houve uma falha inesperada ao se comunicar com nosso servidor. Aguarde alguns instantes ou contate nosso suporte.";
11
+ "Houve uma falha inesperada ao se comunicar com nosso servidor. Tente novamente em alguns segundos ou nos contate se o problema persistir.";
12
12
 
13
- constructor(public message = ApiUnexpectedResponseError.message) {}
13
+ constructor(public message = ApiUnknownError.message) {}
14
14
  }
@@ -1,7 +1,7 @@
1
1
  import { publicEnv } from "@/public-env";
2
- import { ApiError, ApiUnexpectedResponseError } from "./errors";
2
+ import { ApiError, ApiUnknownError } from "./errors";
3
3
 
4
- type SwrFetcherOutput = {
4
+ export type SwrFetcherOutput = {
5
5
  body: unknown;
6
6
  status: number;
7
7
  headers: Headers;
@@ -11,16 +11,19 @@ export async function swrFetcher<Output extends SwrFetcherOutput>(
11
11
  endpointUrl: RequestInfo | URL,
12
12
  options: RequestInit,
13
13
  ): Promise<Output> {
14
- const url = new URL(publicEnv.API_BASE_URL);
14
+ const url = getFullUrlFromApiByEndpoint(endpointUrl);
15
+ let response: Response;
15
16
 
16
- if (url.pathname.endsWith("/")) {
17
- url.pathname = endpointUrl.toString();
18
- } else {
19
- url.pathname += endpointUrl;
17
+ try {
18
+ response = await fetch(url, {
19
+ credentials: "include",
20
+ ...options,
21
+ });
22
+ } catch {
23
+ throw new ApiUnknownError();
20
24
  }
21
25
 
22
- const response = await fetch(endpointUrl, options);
23
- let body: unknown;
26
+ let body: unknown = null;
24
27
 
25
28
  if (
26
29
  response.headers.get("Content-Type")?.includes("application/json") &&
@@ -38,7 +41,7 @@ export async function swrFetcher<Output extends SwrFetcherOutput>(
38
41
 
39
42
  if (error) throw new ApiError(error.statusCode, error.error, error.message);
40
43
 
41
- throw new ApiUnexpectedResponseError();
44
+ throw new ApiUnknownError();
42
45
  }
43
46
 
44
47
  return {
@@ -47,3 +50,27 @@ export async function swrFetcher<Output extends SwrFetcherOutput>(
47
50
  headers: response.headers,
48
51
  } as Awaited<Output>;
49
52
  }
53
+
54
+ function getFullUrlFromApiByEndpoint(endpointUrl: RequestInfo | URL) {
55
+ const url = new URL(publicEnv.API_BASE_URL);
56
+ const [pathname, stringifiedSearchParams] = endpointUrl.toString().split("?");
57
+
58
+ if (url.pathname.endsWith("/")) {
59
+ url.pathname = pathname;
60
+ } else {
61
+ url.pathname += pathname;
62
+ }
63
+
64
+ if (stringifiedSearchParams) {
65
+ const searchParams = new URLSearchParams(stringifiedSearchParams);
66
+
67
+ for (const searchParamKey of Array.from(searchParams.keys())) {
68
+ const searchParamValue = searchParams.get(searchParamKey);
69
+
70
+ if (searchParamValue)
71
+ url.searchParams.set(searchParamKey, searchParamValue);
72
+ }
73
+ }
74
+
75
+ return url;
76
+ }
@@ -13,7 +13,6 @@ dist-ssr
13
13
  *.local
14
14
 
15
15
  # Editor directories and files
16
- .vscode/*
17
16
  !.vscode/extensions.json
18
17
  .idea
19
18
  .DS_Store
@@ -1,5 +1,5 @@
1
1
  import { RouterProvider } from "react-router/dom";
2
- import { router } from "./routes";
2
+ import { router } from "./lib/react-router-config";
3
3
 
4
4
  export function App() {
5
5
  return <RouterProvider router={router} />;
@@ -0,0 +1,24 @@
1
+ import { routeGroups } from "@/routes";
2
+ import { useLocation } from "react-router";
3
+
4
+ type RouteGroup = (typeof routeGroups)[number];
5
+
6
+ type Route = RouteGroup extends Record<string, infer Route> ? Route : never;
7
+
8
+ export function useCurrentRoute() {
9
+ const { pathname } = useLocation();
10
+ let currentRoute: Route | null = null;
11
+
12
+ for (const routeGroup of routeGroups) {
13
+ const matchedRoute = Object.values(routeGroup).find(route => {
14
+ return route.path === pathname;
15
+ });
16
+
17
+ if (matchedRoute) {
18
+ currentRoute = matchedRoute;
19
+ break;
20
+ }
21
+ }
22
+
23
+ return currentRoute;
24
+ }
@@ -0,0 +1,32 @@
1
+ import { AppLayout } from "@/pages/_layouts/app";
2
+ import { AuthLayout } from "@/pages/_layouts/auth";
3
+ import { privateRoutes, publicRoutes } from "@/routes";
4
+ import { createBrowserRouter, RouteObject } from "react-router";
5
+
6
+ export const router = createBrowserRouter([
7
+ {
8
+ element: <AuthLayout />,
9
+ children: [
10
+ ...Object.values(publicRoutes).map(
11
+ publicRoute =>
12
+ ({
13
+ path: publicRoute.path,
14
+ element: <publicRoute.element />,
15
+ index: true,
16
+ }) satisfies RouteObject,
17
+ ),
18
+ ],
19
+ },
20
+ {
21
+ element: <AppLayout />,
22
+ children: [
23
+ ...Object.values(privateRoutes).map(
24
+ publicRoute =>
25
+ ({
26
+ path: publicRoute.path,
27
+ element: <publicRoute.element />,
28
+ }) satisfies RouteObject,
29
+ ),
30
+ ],
31
+ },
32
+ ]);
@@ -1,15 +1,12 @@
1
- import { privateRoutes } from "@/routes";
2
- import { Outlet, useLocation } from "react-router";
1
+ import { useCurrentRoute } from "@/hooks/use-current-route";
2
+ import { Outlet } from "react-router";
3
3
 
4
4
  export function AppLayout() {
5
- const { pathname } = useLocation();
6
- const currentPublicRoute = Object.values(privateRoutes).find(privateRoute => {
7
- return privateRoute.path === pathname;
8
- });
5
+ const currentRoute = useCurrentRoute();
9
6
 
10
7
  return (
11
8
  <>
12
- {currentPublicRoute && <title>{currentPublicRoute.title}</title>}
9
+ {currentRoute && <title>{currentRoute.title}</title>}
13
10
 
14
11
  <div>
15
12
  <Outlet />
@@ -1,15 +1,12 @@
1
- import { publicRoutes } from "@/routes";
2
- import { Outlet, useLocation } from "react-router";
1
+ import { useCurrentRoute } from "@/hooks/use-current-route";
2
+ import { Outlet } from "react-router";
3
3
 
4
4
  export function AuthLayout() {
5
- const { pathname } = useLocation();
6
- const currentPublicRoute = Object.values(publicRoutes).find(publicRoute => {
7
- return publicRoute.path === pathname;
8
- });
5
+ const currentRoute = useCurrentRoute();
9
6
 
10
7
  return (
11
8
  <>
12
- {currentPublicRoute && <title>{currentPublicRoute.title}</title>}
9
+ {currentRoute && <title>{currentRoute.title}</title>}
13
10
 
14
11
  <div>
15
12
  <Outlet />
@@ -2,7 +2,7 @@ import { publicRoutes } from "@/routes";
2
2
  import { Link } from "react-router";
3
3
  import "./styles.css";
4
4
 
5
- export function Dashboard() {
5
+ export function DashboardPage() {
6
6
  return (
7
7
  <div className="dashboard-wrapper">
8
8
  <h1>Hello world!</h1>
@@ -12,7 +12,7 @@ const signInFormSchema = z.object({
12
12
 
13
13
  type SignInForm = z.infer<typeof signInFormSchema>;
14
14
 
15
- export function SignIn() {
15
+ export function SignInPage() {
16
16
  const navigate = useNavigate();
17
17
 
18
18
  const signInForm = useForm<SignInForm>({
@@ -0,0 +1,27 @@
1
+ import { DashboardPage } from "./pages/app/dashboard";
2
+ import { SignInPage } from "./pages/auth/sign-in";
3
+ import { publicEnv } from "./public-env";
4
+
5
+ export const publicRoutes = Object.freeze({
6
+ signIn: {
7
+ path: "/",
8
+ element: SignInPage,
9
+ title: createPageTitleTemplate("Sign In"),
10
+ label: "Sign In",
11
+ },
12
+ } as const);
13
+
14
+ export const privateRoutes = Object.freeze({
15
+ dashboard: {
16
+ path: "/dashboard",
17
+ element: DashboardPage,
18
+ title: createPageTitleTemplate("Dashboard"),
19
+ label: "Dashboard",
20
+ },
21
+ } as const);
22
+
23
+ export const routeGroups = [publicRoutes, privateRoutes] as const;
24
+
25
+ function createPageTitleTemplate(title: string) {
26
+ return `${title} | ${publicEnv.APP_NAME}`;
27
+ }
@@ -1,24 +0,0 @@
1
- import { RouteObject } from "react-router";
2
- import { OverrideProperties } from "type-fest";
3
-
4
- export type CustomRouteDefinition = {
5
- path: string;
6
- label: string;
7
- };
8
-
9
- export type ReactRouterRouteDefinition<Routes extends CustomRouteDefinition[]> =
10
- OverrideProperties<
11
- RouteObject,
12
- {
13
- path: Routes[number]["path"] | "*";
14
- handle: ReactRouterRouteHandleDefinition;
15
- }
16
- >;
17
-
18
- export type ReactRouterRouteHandleDefinition = {
19
- metadata: {
20
- title: string;
21
- description?: string;
22
- };
23
- [K: string]: unknown;
24
- };
@@ -1,49 +0,0 @@
1
- import { createBrowserRouter, RouteObject } from "react-router";
2
- import { AppLayout } from "./pages/_layouts/app";
3
- import { AuthLayout } from "./pages/_layouts/auth";
4
- import { Dashboard } from "./pages/app/dashboard/dashboard";
5
- import { SignIn } from "./pages/auth/sign-in";
6
- import { publicEnv } from "./public-env";
7
-
8
- export const publicRoutes = Object.freeze({
9
- signIn: {
10
- path: "/",
11
- element: <SignIn />,
12
- title: createTitleTemplate("Sign In"),
13
- },
14
- } as const);
15
-
16
- export const privateRoutes = Object.freeze({
17
- dashboard: {
18
- path: "/dashboard",
19
- element: <Dashboard />,
20
- title: createTitleTemplate("Dashboard"),
21
- },
22
- } as const);
23
-
24
- export const router = createBrowserRouter([
25
- {
26
- element: <AuthLayout />,
27
- children: [
28
- ...Object.values(publicRoutes).map<RouteObject>(publicRoute => ({
29
- path: publicRoute.path,
30
- element: publicRoute.element,
31
- index: true,
32
- })),
33
- ],
34
- },
35
- {
36
- element: <AppLayout />,
37
- children: [
38
- ...Object.values(privateRoutes).map<RouteObject>(publicRoute => ({
39
- path: publicRoute.path,
40
- element: publicRoute.element,
41
- index: true,
42
- })),
43
- ],
44
- },
45
- ]);
46
-
47
- function createTitleTemplate(title: string) {
48
- return `${title} | ${publicEnv.APP_NAME}`;
49
- }