diff --git a/apps/dokploy/__test__/compose/network/network-root.test.ts b/apps/dokploy/__test__/compose/network/network-root.test.ts index 0d3c841d4..1a6817913 100644 --- a/apps/dokploy/__test__/compose/network/network-root.test.ts +++ b/apps/dokploy/__test__/compose/network/network-root.test.ts @@ -292,7 +292,7 @@ networks: dokploy-network: `; -test("It shoudn't add suffix to dokploy-network", () => { +test("It shouldn't add suffix to dokploy-network", () => { const composeData = parse(composeFile7) as ComposeSpecification; const suffix = generateRandomHash(); diff --git a/apps/dokploy/__test__/compose/network/network-service.test.ts b/apps/dokploy/__test__/compose/network/network-service.test.ts index e07fa1546..91eb5da4e 100644 --- a/apps/dokploy/__test__/compose/network/network-service.test.ts +++ b/apps/dokploy/__test__/compose/network/network-service.test.ts @@ -1,7 +1,7 @@ import type { ComposeSpecification } from "@dokploy/server"; import { - addSuffixToServiceNetworks, - generateRandomHash, + addSuffixToServiceNetworks, + generateRandomHash, } from "@dokploy/server"; import { expect, test } from "vitest"; import { parse } from "yaml"; @@ -23,30 +23,30 @@ services: `; test("Add suffix to networks in services", () => { - const composeData = parse(composeFile) as ComposeSpecification; + const composeData = parse(composeFile) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const services = addSuffixToServiceNetworks(composeData.services, suffix); - const actualComposeData = { ...composeData, services }; + if (!composeData?.services) { + return; + } + const services = addSuffixToServiceNetworks(composeData.services, suffix); + const actualComposeData = { ...composeData, services }; - expect(actualComposeData?.services?.web?.networks).toContain( - `frontend-${suffix}`, - ); + expect(actualComposeData?.services?.web?.networks).toContain( + `frontend-${suffix}`, + ); - expect(actualComposeData?.services?.api?.networks).toContain( - `backend-${suffix}`, - ); + expect(actualComposeData?.services?.api?.networks).toContain( + `backend-${suffix}`, + ); - const apiNetworks = actualComposeData?.services?.api?.networks; + const apiNetworks = actualComposeData?.services?.api?.networks; - expect(apiNetworks).toBeDefined(); - expect(actualComposeData?.services?.api?.networks).toContain( - `backend-${suffix}`, - ); + expect(apiNetworks).toBeDefined(); + expect(actualComposeData?.services?.api?.networks).toContain( + `backend-${suffix}`, + ); }); // Caso 2: Objeto con aliases @@ -67,29 +67,29 @@ networks: `; test("Add suffix to networks in services with aliases", () => { - const composeData = parse(composeFile2) as ComposeSpecification; + const composeData = parse(composeFile2) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const services = addSuffixToServiceNetworks(composeData.services, suffix); - const actualComposeData = { ...composeData, services }; + if (!composeData?.services) { + return; + } + const services = addSuffixToServiceNetworks(composeData.services, suffix); + const actualComposeData = { ...composeData, services }; - expect(actualComposeData.services?.api?.networks).toHaveProperty( - `frontend-${suffix}`, - ); + expect(actualComposeData.services?.api?.networks).toHaveProperty( + `frontend-${suffix}`, + ); - const networkConfig = actualComposeData?.services?.api?.networks as { - [key: string]: { aliases?: string[] }; - }; - expect(networkConfig[`frontend-${suffix}`]).toBeDefined(); - expect(networkConfig[`frontend-${suffix}`]?.aliases).toContain("api"); + const networkConfig = actualComposeData?.services?.api?.networks as { + [key: string]: { aliases?: string[] }; + }; + expect(networkConfig[`frontend-${suffix}`]).toBeDefined(); + expect(networkConfig[`frontend-${suffix}`]?.aliases).toContain("api"); - expect(actualComposeData.services?.api?.networks).not.toHaveProperty( - "frontend-ash", - ); + expect(actualComposeData.services?.api?.networks).not.toHaveProperty( + "frontend-ash", + ); }); const composeFile3 = ` @@ -107,19 +107,19 @@ networks: `; test("Add suffix to networks in services (Object with simple networks)", () => { - const composeData = parse(composeFile3) as ComposeSpecification; + const composeData = parse(composeFile3) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const services = addSuffixToServiceNetworks(composeData.services, suffix); - const actualComposeData = { ...composeData, services }; + if (!composeData?.services) { + return; + } + const services = addSuffixToServiceNetworks(composeData.services, suffix); + const actualComposeData = { ...composeData, services }; - expect(actualComposeData.services?.redis?.networks).toHaveProperty( - `backend-${suffix}`, - ); + expect(actualComposeData.services?.redis?.networks).toHaveProperty( + `backend-${suffix}`, + ); }); const composeFileCombined = ` @@ -153,36 +153,36 @@ networks: `; test("Add suffix to networks in services (combined case)", () => { - const composeData = parse(composeFileCombined) as ComposeSpecification; + const composeData = parse(composeFileCombined) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const services = addSuffixToServiceNetworks(composeData.services, suffix); - const actualComposeData = { ...composeData, services }; + if (!composeData?.services) { + return; + } + const services = addSuffixToServiceNetworks(composeData.services, suffix); + const actualComposeData = { ...composeData, services }; - // Caso 1: ListOfStrings - expect(actualComposeData.services?.web?.networks).toContain( - `frontend-${suffix}`, - ); - expect(actualComposeData.services?.web?.networks).toContain( - `backend-${suffix}`, - ); + // Caso 1: ListOfStrings + expect(actualComposeData.services?.web?.networks).toContain( + `frontend-${suffix}`, + ); + expect(actualComposeData.services?.web?.networks).toContain( + `backend-${suffix}`, + ); - // Caso 2: Objeto con aliases - const apiNetworks = actualComposeData.services?.api?.networks as { - [key: string]: unknown; - }; - expect(apiNetworks).toHaveProperty(`frontend-${suffix}`); - expect(apiNetworks[`frontend-${suffix}`]).toBeDefined(); - expect(apiNetworks).not.toHaveProperty("frontend"); + // Caso 2: Objeto con aliases + const apiNetworks = actualComposeData.services?.api?.networks as { + [key: string]: unknown; + }; + expect(apiNetworks).toHaveProperty(`frontend-${suffix}`); + expect(apiNetworks[`frontend-${suffix}`]).toBeDefined(); + expect(apiNetworks).not.toHaveProperty("frontend"); - // Caso 3: Objeto con redes simples - const redisNetworks = actualComposeData.services?.redis?.networks; - expect(redisNetworks).toHaveProperty(`backend-${suffix}`); - expect(redisNetworks).not.toHaveProperty("backend"); + // Caso 3: Objeto con redes simples + const redisNetworks = actualComposeData.services?.redis?.networks; + expect(redisNetworks).toHaveProperty(`backend-${suffix}`); + expect(redisNetworks).not.toHaveProperty("backend"); }); const composeFile7 = ` @@ -195,19 +195,19 @@ services: - dokploy-network `; -test("It shoudn't add suffix to dokploy-network in services", () => { - const composeData = parse(composeFile7) as ComposeSpecification; +test("It shouldn't add suffix to dokploy-network in services", () => { + const composeData = parse(composeFile7) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const networks = addSuffixToServiceNetworks(composeData.services, suffix); - const service = networks.web; + if (!composeData?.services) { + return; + } + const networks = addSuffixToServiceNetworks(composeData.services, suffix); + const service = networks.web; - expect(service).toBeDefined(); - expect(service?.networks).toContain("dokploy-network"); + expect(service).toBeDefined(); + expect(service?.networks).toContain("dokploy-network"); }); const composeFile8 = ` @@ -241,35 +241,35 @@ services: dokploy-network: aliases: - apid - + `; -test("It shoudn't add suffix to dokploy-network in services multiples cases", () => { - const composeData = parse(composeFile8) as ComposeSpecification; +test("It shouldn't add suffix to dokploy-network in services multiples cases", () => { + const composeData = parse(composeFile8) as ComposeSpecification; - const suffix = generateRandomHash(); + const suffix = generateRandomHash(); - if (!composeData?.services) { - return; - } - const networks = addSuffixToServiceNetworks(composeData.services, suffix); - const service = networks.web; - const api = networks.api; - const redis = networks.redis; - const db = networks.db; + if (!composeData?.services) { + return; + } + const networks = addSuffixToServiceNetworks(composeData.services, suffix); + const service = networks.web; + const api = networks.api; + const redis = networks.redis; + const db = networks.db; - const dbNetworks = db?.networks as { - [key: string]: unknown; - }; + const dbNetworks = db?.networks as { + [key: string]: unknown; + }; - const apiNetworks = api?.networks as { - [key: string]: unknown; - }; + const apiNetworks = api?.networks as { + [key: string]: unknown; + }; - expect(service).toBeDefined(); - expect(service?.networks).toContain("dokploy-network"); + expect(service).toBeDefined(); + expect(service?.networks).toContain("dokploy-network"); - expect(redis?.networks).toHaveProperty("dokploy-network"); - expect(dbNetworks["dokploy-network"]).toBeDefined(); - expect(apiNetworks["dokploy-network"]).toBeDefined(); + expect(redis?.networks).toHaveProperty("dokploy-network"); + expect(dbNetworks["dokploy-network"]).toBeDefined(); + expect(apiNetworks["dokploy-network"]).toBeDefined(); }); diff --git a/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts b/apps/dokploy/__test__/compose/service/service-volumes-from.test.ts similarity index 100% rename from apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts rename to apps/dokploy/__test__/compose/service/service-volumes-from.test.ts diff --git a/apps/dokploy/__test__/env/stack-environment.test.ts b/apps/dokploy/__test__/env/stack-environment.test.ts index 13f5adb53..cde7be9db 100644 --- a/apps/dokploy/__test__/env/stack-environment.test.ts +++ b/apps/dokploy/__test__/env/stack-environment.test.ts @@ -1,4 +1,4 @@ -import { getEnviromentVariablesObject } from "@dokploy/server/index"; +import { getEnvironmentVariablesObject } from "@dokploy/server/index"; import { describe, expect, it } from "vitest"; const projectEnv = ` @@ -15,29 +15,29 @@ DATABASE_NAME=dev_database SECRET_KEY=env-secret-123 `; -describe("getEnviromentVariablesObject with environment variables (Stack compose)", () => { - it("resolves environment variables correctly for Stack compose", () => { - const serviceEnv = ` +describe("getEnvironmentVariablesObject with environment variables (Stack compose)", () => { + it("resolves environment variables correctly for Stack compose", () => { + const serviceEnv = ` FOO=\${{environment.NODE_ENV}} BAR=\${{environment.API_URL}} BAZ=test `; - const result = getEnviromentVariablesObject( - serviceEnv, - projectEnv, - environmentEnv, - ); + const result = getEnvironmentVariablesObject( + serviceEnv, + projectEnv, + environmentEnv, + ); - expect(result).toEqual({ - FOO: "development", - BAR: "https://api.dev.example.com", - BAZ: "test", - }); - }); + expect(result).toEqual({ + FOO: "development", + BAR: "https://api.dev.example.com", + BAZ: "test", + }); + }); - it("resolves both project and environment variables for Stack compose", () => { - const serviceEnv = ` + it("resolves both project and environment variables for Stack compose", () => { + const serviceEnv = ` ENVIRONMENT=\${{project.ENVIRONMENT}} NODE_ENV=\${{environment.NODE_ENV}} API_URL=\${{environment.API_URL}} @@ -45,140 +45,140 @@ DATABASE_URL=\${{project.DATABASE_URL}} SERVICE_PORT=4000 `; - const result = getEnviromentVariablesObject( - serviceEnv, - projectEnv, - environmentEnv, - ); + const result = getEnvironmentVariablesObject( + serviceEnv, + projectEnv, + environmentEnv, + ); - expect(result).toEqual({ - ENVIRONMENT: "staging", - NODE_ENV: "development", - API_URL: "https://api.dev.example.com", - DATABASE_URL: "postgres://postgres:postgres@localhost:5432/project_db", - SERVICE_PORT: "4000", - }); - }); + expect(result).toEqual({ + ENVIRONMENT: "staging", + NODE_ENV: "development", + API_URL: "https://api.dev.example.com", + DATABASE_URL: "postgres://postgres:postgres@localhost:5432/project_db", + SERVICE_PORT: "4000", + }); + }); - it("handles multiple environment references in single value for Stack compose", () => { - const multiRefEnv = ` + it("handles multiple environment references in single value for Stack compose", () => { + const multiRefEnv = ` HOST=localhost PORT=5432 USERNAME=postgres PASSWORD=secret123 `; - const serviceEnv = ` + const serviceEnv = ` DATABASE_URL=postgresql://\${{environment.USERNAME}}:\${{environment.PASSWORD}}@\${{environment.HOST}}:\${{environment.PORT}}/mydb `; - const result = getEnviromentVariablesObject(serviceEnv, "", multiRefEnv); + const result = getEnvironmentVariablesObject(serviceEnv, "", multiRefEnv); - expect(result).toEqual({ - DATABASE_URL: "postgresql://postgres:secret123@localhost:5432/mydb", - }); - }); + expect(result).toEqual({ + DATABASE_URL: "postgresql://postgres:secret123@localhost:5432/mydb", + }); + }); - it("throws error for undefined environment variables in Stack compose", () => { - const serviceWithUndefined = ` + it("throws error for undefined environment variables in Stack compose", () => { + const serviceWithUndefined = ` UNDEFINED_VAR=\${{environment.UNDEFINED_VAR}} `; - expect(() => - getEnviromentVariablesObject(serviceWithUndefined, "", environmentEnv), - ).toThrow("Invalid environment variable: environment.UNDEFINED_VAR"); - }); + expect(() => + getEnvironmentVariablesObject(serviceWithUndefined, "", environmentEnv), + ).toThrow("Invalid environment variable: environment.UNDEFINED_VAR"); + }); - it("allows service variables to override environment variables in Stack compose", () => { - const serviceOverrideEnv = ` + it("allows service variables to override environment variables in Stack compose", () => { + const serviceOverrideEnv = ` NODE_ENV=production API_URL=\${{environment.API_URL}} `; - const result = getEnviromentVariablesObject( - serviceOverrideEnv, - "", - environmentEnv, - ); + const result = getEnvironmentVariablesObject( + serviceOverrideEnv, + "", + environmentEnv, + ); - expect(result).toEqual({ - NODE_ENV: "production", - API_URL: "https://api.dev.example.com", - }); - }); + expect(result).toEqual({ + NODE_ENV: "production", + API_URL: "https://api.dev.example.com", + }); + }); - it("resolves complex references with project, environment, and service variables for Stack compose", () => { - const complexServiceEnv = ` + it("resolves complex references with project, environment, and service variables for Stack compose", () => { + const complexServiceEnv = ` FULL_DATABASE_URL=\${{project.DATABASE_URL}}/\${{environment.DATABASE_NAME}} API_ENDPOINT=\${{environment.API_URL}}/\${{project.ENVIRONMENT}}/api SERVICE_NAME=my-service COMPLEX_VAR=\${{SERVICE_NAME}}-\${{environment.NODE_ENV}}-\${{project.ENVIRONMENT}} `; - const result = getEnviromentVariablesObject( - complexServiceEnv, - projectEnv, - environmentEnv, - ); + const result = getEnvironmentVariablesObject( + complexServiceEnv, + projectEnv, + environmentEnv, + ); - expect(result).toEqual({ - FULL_DATABASE_URL: - "postgres://postgres:postgres@localhost:5432/project_db/dev_database", - API_ENDPOINT: "https://api.dev.example.com/staging/api", - SERVICE_NAME: "my-service", - COMPLEX_VAR: "my-service-development-staging", - }); - }); + expect(result).toEqual({ + FULL_DATABASE_URL: + "postgres://postgres:postgres@localhost:5432/project_db/dev_database", + API_ENDPOINT: "https://api.dev.example.com/staging/api", + SERVICE_NAME: "my-service", + COMPLEX_VAR: "my-service-development-staging", + }); + }); - it("maintains precedence: service > environment > project in Stack compose", () => { - const conflictingProjectEnv = ` + it("maintains precedence: service > environment > project in Stack compose", () => { + const conflictingProjectEnv = ` NODE_ENV=production-project API_URL=https://project.api.com DATABASE_NAME=project_db `; - const conflictingEnvironmentEnv = ` + const conflictingEnvironmentEnv = ` NODE_ENV=development-environment API_URL=https://environment.api.com DATABASE_NAME=env_db `; - const serviceWithConflicts = ` + const serviceWithConflicts = ` NODE_ENV=service-override PROJECT_ENV=\${{project.NODE_ENV}} ENV_VAR=\${{environment.API_URL}} DB_NAME=\${{environment.DATABASE_NAME}} `; - const result = getEnviromentVariablesObject( - serviceWithConflicts, - conflictingProjectEnv, - conflictingEnvironmentEnv, - ); + const result = getEnvironmentVariablesObject( + serviceWithConflicts, + conflictingProjectEnv, + conflictingEnvironmentEnv, + ); - expect(result).toEqual({ - NODE_ENV: "service-override", - PROJECT_ENV: "production-project", - ENV_VAR: "https://environment.api.com", - DB_NAME: "env_db", - }); - }); + expect(result).toEqual({ + NODE_ENV: "service-override", + PROJECT_ENV: "production-project", + ENV_VAR: "https://environment.api.com", + DB_NAME: "env_db", + }); + }); - it("handles empty environment variables in Stack compose", () => { - const serviceWithEmpty = ` + it("handles empty environment variables in Stack compose", () => { + const serviceWithEmpty = ` SERVICE_VAR=test PROJECT_VAR=\${{project.ENVIRONMENT}} `; - const result = getEnviromentVariablesObject( - serviceWithEmpty, - projectEnv, - "", - ); + const result = getEnvironmentVariablesObject( + serviceWithEmpty, + projectEnv, + "", + ); - expect(result).toEqual({ - SERVICE_VAR: "test", - PROJECT_VAR: "staging", - }); - }); + expect(result).toEqual({ + SERVICE_VAR: "test", + PROJECT_VAR: "staging", + }); + }); }); diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx index 172c042f1..603a6d6c8 100644 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx @@ -149,7 +149,7 @@ export const HandleRedirect = ({ const onDialogToggle = (open: boolean) => { setIsOpen(open); - // commented for the moment because not reseting the form if accidentally closed the dialog can be considered as a feature instead of a bug + // commented for the moment because not resetting the form if accidentally closed the dialog can be considered as a feature instead of a bug // setPresetSelected(""); // form.reset(); }; diff --git a/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx b/apps/dokploy/components/dashboard/application/environment/show-environment.tsx similarity index 100% rename from apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx rename to apps/dokploy/components/dashboard/application/environment/show-environment.tsx diff --git a/apps/dokploy/components/dashboard/application/logs/show.tsx b/apps/dokploy/components/dashboard/application/logs/show.tsx index cbb6bce09..06b257766 100644 --- a/apps/dokploy/components/dashboard/application/logs/show.tsx +++ b/apps/dokploy/components/dashboard/application/logs/show.tsx @@ -91,7 +91,7 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => { }, [option, services, containers]); const isLoading = option === "native" ? containersLoading : servicesLoading; - const containersLenght = + const containersLength = option === "native" ? containers?.length : services?.length; return ( @@ -167,7 +167,7 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => { )} - Containers ({containersLenght}) + Containers ({containersLength}) diff --git a/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx b/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx index 159ab3485..4c3067b15 100644 --- a/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx +++ b/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx @@ -77,7 +77,7 @@ export const ShowDockerLogsStack = ({ appName, serverId }: Props) => { }, [option, services, containers]); const isLoading = option === "native" ? containersLoading : servicesLoading; - const containersLenght = + const containersLength = option === "native" ? containers?.length : services?.length; return ( @@ -152,7 +152,7 @@ export const ShowDockerLogsStack = ({ appName, serverId }: Props) => { )} - Containers ({containersLenght}) + Containers ({containersLength}) diff --git a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx index 00647aea7..7b212acb9 100644 --- a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx +++ b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx @@ -225,7 +225,7 @@ export const RestoreBackup = ({ resolver: zodResolver(RestoreBackupSchema), }); - const destionationId = form.watch("destinationId"); + const destinationId = form.watch("destinationId"); const currentDatabaseType = form.watch("databaseType"); const metadata = form.watch("metadata"); @@ -240,12 +240,12 @@ export const RestoreBackup = ({ const { data: files = [], isPending } = api.backup.listBackupFiles.useQuery( { - destinationId: destionationId, + destinationId: destinationId, search: debouncedSearchTerm, serverId: serverId ?? "", }, { - enabled: isOpen && !!destionationId, + enabled: isOpen && !!destinationId, }, ); diff --git a/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx b/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx index 9d4f47c4a..bed5c6f5d 100644 --- a/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx @@ -103,7 +103,7 @@ export function TerminalLine({ log, noTimestamp, searchTerm }: LogLineProps) { > {" "}
- {/* Icon to expand the log item maybe implement a colapsible later */} + {/* Icon to expand the log item maybe implement a collapsible later */} {/* */} {tooltip(color, rawTimestamp)} {!noTimestamp && ( diff --git a/apps/dokploy/components/dashboard/docker/show/colums.tsx b/apps/dokploy/components/dashboard/docker/show/columns.tsx similarity index 100% rename from apps/dokploy/components/dashboard/docker/show/colums.tsx rename to apps/dokploy/components/dashboard/docker/show/columns.tsx diff --git a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx index 69b0a0da2..8a19566e8 100644 --- a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx +++ b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx @@ -35,7 +35,7 @@ import { TableRow, } from "@/components/ui/table"; import { api, type RouterOutputs } from "@/utils/api"; -import { columns } from "./colums"; +import { columns } from "./columns"; export type Container = NonNullable< RouterOutputs["docker"]["getContainers"] >[0]; diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx index 0d4d3a44f..570955fc8 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx @@ -161,7 +161,7 @@ export const SetupServer = ({ serverId, asButton = false }: Props) => {