<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ildar Timerbaev</title>
    <description>The latest articles on DEV Community by Ildar Timerbaev (@dkildar).</description>
    <link>https://dev.to/dkildar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1219724%2F5efd4868-c29e-4e38-8d82-f1d6c5e5cc5e.jpeg</url>
      <title>DEV Community: Ildar Timerbaev</title>
      <link>https://dev.to/dkildar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dkildar"/>
    <language>en</language>
    <item>
      <title>I Built My Own Error Tracker</title>
      <dc:creator>Ildar Timerbaev</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:58:11 +0000</pubDate>
      <link>https://dev.to/dkildar/i-built-my-own-error-tracker-dh1</link>
      <guid>https://dev.to/dkildar/i-built-my-own-error-tracker-dh1</guid>
      <description>&lt;p&gt;Every production application eventually crashes. I don’t mind runtime errors. What I do mind is spending hours trying to understand what actually happened. There are many different tools, services, and SaaS platforms that can help with it. The problem is that many of them have become incredibly complex.&lt;/p&gt;

&lt;p&gt;As a senior engineer, I care not only about convenience, but also about performance, security, and privacy. That motivated me to explore the idea of building my own error-tracking platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;Every engineer can imagine dozens of features for a new project. I &lt;br&gt;
didn’t want to drown in an endless feature loop. I wanted to ship something useful as quickly as possible. And I didn’t want another endless stream of events. Then I asked myself one simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What do I actually need right now?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer was easy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to catch production errors without digging through a stream of raw events. There should be an entity that groups related events into a single issue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;I decided to keep the SDK tiny. It is written in TypeScript and has zero dependencies. The backend runs on Kotlin + Spring Boot, while the dashboard is built with SolidJS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs4vkgrejbiv7jie6cmyr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs4vkgrejbiv7jie6cmyr.png" alt=" " width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything revolves around issues. An issue is an entity built from related events. Each event contains information about the error and its environment: stack trace, metadata, URL, etc. (without collecting sensitive information). The system calculates the fingerprint of the error and either creates a new issue or finds an existing one, then attaches the event to it. That’s it.&lt;/p&gt;

&lt;p&gt;The magic happens later. Once the system has collected the data, it needs to present it in a useful way. I solved this with AI summaries. The AI agent knows everything about the issue and generates a human-readable summary that helps not only developers but also managers understand the problem. The result is an issue page with all the technical details and a concise summary that helps explain what happened and what impact it may have on the product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gsx3schap2m6vikismw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gsx3schap2m6vikismw.png" alt=" " width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After implementing this, I discovered another problem: having to open the dashboard just to check the project status. The solution was simple, I created a generic integration module for Telegram and our internal company messenger. Now it pushes new and regressed issues, along with AI summaries, directly to the tools the team already uses, so you know about problems instantly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwlilct8dl53c9uc5c3g0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwlilct8dl53c9uc5c3g0.png" alt=" " width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;p&gt;At this point, the project had become an issue-centric tracking platform. I decided to share it publicly, but soon faced one serious problem. Let’s deep dive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database boundaries
&lt;/h3&gt;

&lt;p&gt;Unfortunately, a database doesn’t have infinite storage. I had to decide how to keep the event stream clean and fresh. The idea was simple: keep only recent events. If an event hasn’t appeared for a while, the issue has probably been fixed or no longer occurs. I chose a simple FIFO retention strategy: no more than &lt;strong&gt;100,000 events per project over a rolling 7-day window&lt;/strong&gt;. It solved the problem.&lt;/p&gt;




&lt;p&gt;Instead of a conclusion. RetraceKit is still evolving. There are many things I want to improve, but today it already solves the problem that made me build it in the first place. If you decide to give it a try, I’d genuinely love to hear your feedback: what feels useful, what doesn’t, and what you’d improve.&lt;/p&gt;

&lt;p&gt;Platform: &lt;a href="https://retracekit.cloud" rel="noopener noreferrer"&gt;https://retracekit.cloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Support on ProductHunt: &lt;a href="https://www.producthunt.com/products/retracekit-error-tracking-platform" rel="noopener noreferrer"&gt;https://www.producthunt.com/products/retracekit-error-tracking-platform&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>typescript</category>
      <category>development</category>
    </item>
    <item>
      <title>React query for data streaming</title>
      <dc:creator>Ildar Timerbaev</dc:creator>
      <pubDate>Mon, 27 Nov 2023 09:22:46 +0000</pubDate>
      <link>https://dev.to/dkildar/react-query-for-data-streaming-57i5</link>
      <guid>https://dev.to/dkildar/react-query-for-data-streaming-57i5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Everyone is familiar with the popular framework for managing API requests — @tanstack/react-query. This framework is designed for handling browser HTTP API requests. However, that’s not the only way to use it. React query also supports any promises within queries and mutations, which assists us in setting up data streaming and react query together. Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Theory
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Client setup
&lt;/h3&gt;

&lt;p&gt;Since We want to handle data streaming then We assume to use a some kind of WebSocket with STOMP for it. Let’s imagine that We have very simple data flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const stompClient = new StompClient()
stompClient.subscribe((event) =&amp;gt; {...})
stompClient.publish('mychannel, {...})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To gain access from any part of our application, we should position this client within the React context, which will be provided in the root component. Currently, this context will only contain a client instance for accessing our queries and mutations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const StompContext = createContext({
 client: undefined
})

function StompManager({ children }: PropsWithChildren) {
 const [client, setClient] = useState(new StompClient())

 return &amp;lt;StompContext.Provider value={{ client }}&amp;gt;{children}&amp;lt;/StompContext.Provider&amp;gt;
}

function useStompManager() {
 const { client } = useContext(StompContext)
 return client
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;

&lt;p&gt;Let’s revisit how queries work. They consist of an identification key, a query function, and options. Since the identification key is self-explanatory, we’ll focus on the query function and options.&lt;/p&gt;

&lt;p&gt;Firstly, each query should only be activated when our client is ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const client = useStompManager()
...
useQuery([QueryKey], () =&amp;gt; {...}, { enabled: !!client })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secondly, each query requires a function to subscribe to the channel and wait for new data. There are several different subscription types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One event subscription — This is a pattern where we receive something from the channel and then complete the subscription (e.g., getting a user profile).&lt;/li&gt;
&lt;li&gt;Infinite event subscription — This is a pattern where we listen to the channel until it’s unsubscribed or cancelled (e.g., listening to chat messages). Each subscription should be stored in a subscriptions storage to avoid generating multiple subscriptions on each query call. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since we may have infinite queries, we need to create a subscriptions storage in the &lt;code&gt;StompContext&lt;/code&gt;. For this, I'll use the &lt;code&gt;useMap&lt;/code&gt; hook from the &lt;code&gt;react-use&lt;/code&gt; package. Let's establish a rule that the subscription identifier is the query key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [subscribers, { set, get }] = useMap&amp;lt;Record&amp;lt;string, boolean&amp;gt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mutation
&lt;/h3&gt;

&lt;p&gt;The implementation of mutations can vary depending on the protocol used. For instance, chat messages could return a sent message in a channel, indicating successful transmission. However, this is a unique case. Suppose the STOMP client has a &lt;code&gt;publish&lt;/code&gt; method that returns a promise. In that case, this mutation doesn't have any specific logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function useStompMutation(mutationKey: string, destination: string) {
  // getting client
  // default useMutation hook with sending the message in mutation function
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Sending without confirmation&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function useStompConfirmableMutation(mutationKey: string, channel: string, destination: string) {
  // getting client
  // default useMutation hook with next mutation function:
  // 0. Return the promise with resolve, reject parameters
  // 1. Subscribing to specific channel and resolving the promise there
  // 2. Sending a message based on mutation function parameters
  // 3. Handling error to reject the promise
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Sending with confirmation&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One event query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function listenOnce(client: StompClient, channel: string) {
 return new Promise((resolve, reject) =&amp;gt; {
 const sub = client.subscribe(channel, event =&amp;gt; {
   sub.unsubscribe()
   resolve(event)
  })
 })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function useStompQuery&amp;lt;DATA, ERROR&amp;gt;(
 queryKey: QueryKey, 
 channel: string,
 options?: UseQueryOptions&amp;lt;DATA, ERROR&amp;gt;
) {
 const client = useStompClient()
 return useQuery(
  queryKey, 
  async () =&amp;gt; listenOnce(),
  {
   ...options,
   enabled: !!client &amp;amp;&amp;amp; (options?.enabled ?? true),
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Infinite events query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function useStompQuery&amp;lt;DATA, ERROR&amp;gt;(
 queryKey: QueryKey, 
 channel: string,
 newDataResolver: (previousData: DATA[], newComingData: DATA) =&amp;gt; void,
 options?: UseQueryOptions&amp;lt;DATA[], ERROR&amp;gt;,
) {
 const client = useStompClient()
 const { setSubscriber, getSubscriber } = useContext(StompContext)
 const queryClient = useQueryClient()

 // Hook from react-use package
 useMount(() =&amp;gt; {
  if (getSubscriber(queryKey)) {
   return
  }

  const sub = client.subscribe(channel, event =&amp;gt; {
   const previousData = queryClient.getQueryData&amp;lt;DATA[]&amp;gt;(queryKey)
   let nextData = newDataResolver(previousData, event)
   queryClient.setQueryData(queryKey, nextData)
  })
  setSubscriber(queryKey, sub)
  })

 return useQuery&amp;lt;DATA[]&amp;gt;(
  queryKey,
  () =&amp;gt; queryClient.getQueryData&amp;lt;DATA[]&amp;gt;(queryKey),
  {
   ...options,
   enabled: !!client &amp;amp;&amp;amp; (options?.enabled ?? true),
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s highlight an implementation details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;newDataResolver&lt;/code&gt; – special mutating function that allow us to control each new event coming(e.g. whenever new message coming then We could mark it with sent status);&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;() =&amp;gt; queryClient.getQueryData&amp;lt;DATA[]&amp;gt;(queryKey)&lt;/code&gt; – as this query hasn’t one-time fetching function then We have to return the query itself data on each refetch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mutation without confirmation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function useStompMutation&amp;lt;T, DATA&amp;gt;(
 mutationKey: string, 
 destination: string,
 options?: UseMutationOptions&amp;lt;DATA&amp;gt;
) {
 const client = useStompClient()
 return useMutation&amp;lt;DATA&amp;gt;(
  mutationKey, 
  async (message: T) =&amp;gt; {
   if (!client) {
    throw new Error('Client not found')
   }
   client.publish(destination, message)
  },
  options
 ) 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mutation with confirmation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function useStompConfirmableMutation&amp;lt;T, DATA&amp;gt;(
 mutationKey: string, 
 destination: string,
 channel: string,
 confirmation?: (event: DATA) =&amp;gt; boolean,
 options?: UseMutationOptions&amp;lt;DATA&amp;gt;
) {
 const client = useStompClient()
 return useMutation&amp;lt;DATA&amp;gt;(
  mutationKey, 
  async (message: T) =&amp;gt; new Promise(async (resolve, reject) =&amp;gt; {
   if (!client) {
    throw new Error('Client not found')
   }

   client.subscribe(channel, event =&amp;gt; {
    if (confirmation(event)) {
     resolve(event)
    }
   })

   try {
    await client.publish(destination, message)
   } catch (e) {
    reject(e)
   }
  }),
  options
 ) 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The potential issue here is that if the event doesn’t return in the channel, it may cause an infinite promise. Implementing timeout rejection could resolve this, but I’ll leave that up to you 🙂&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We’ve successfully implemented a working solution for data streaming in React Query. However, this solution may not be ideal for all use-cases, so you might need to adjust it depending on your data streaming service implementation.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://medium.com/@dkildar/react-query-for-data-streaming-c926eb0a562e"&gt;Own article on Medium&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>reactquery</category>
      <category>websocket</category>
      <category>datastreaming</category>
    </item>
  </channel>
</rss>
