jacob-ebey / turbo-stream

A streaming data transport format that aims to support built-in features such as Promises, Dates, RegExps, Maps, Sets and more.
https://turbo-stream.pages.dev/
MIT License
167 stars 6 forks source link

Data getting deduplicated incorrectly and corrupting #36

Closed nick-potts closed 1 month ago

nick-potts commented 2 months ago

I'm running single fetch and trying to pass back an object that has a bunch of deeply nested promises. each promise is similar, but different.

Each promise is suppose to return data in this format:

export type ReportMetricResponseDto = {
  id: string;
  name: string;
  value: number;
  formatted: string;
  errorMax: string;
  errorMin: string;
  errorPercentage: number;
  comparisonPercentage: number | null;
  positiveComparisonIsGood: boolean;
  inaccurateOrderCount: number;
  parentData: Modules.Reports.Dtos.ReportMetricParentData | null;
};

This is me just doing JSON.stringify(data.formatted). It should just contain a string, but the turbo stream is somehow putting other metrics inside formatted.

image

Some example scrambled data:

image image

If I JSON.stringify the promise's data, then decode it after the suspense resolves, it works perfectly fine.

lsthornt commented 1 month ago

I am running into a corruption issue as well, but it's difficult to know if it's the same as what's happening with @nick-potts.

The data I am returning in my Remix loader looks like this:

  const foo = sleep(2000).then(() => 'foo');
  const bar = sleep(2000).then(() => 'bar');
  return { data: Promise.all([foo, bar]) };

In the HTML body, I see the value seemingly correctly enqueued for hydration as "P153:[[229,230],\"foo\",\"bar\"]\n".

However, once it gets to the unflatten phase of decoding, the indexes are off. Here is a screenshot of the debugger state during unflattening of this array:

image

In this screenshot, parsed[0] = [229, 230], and startIndex is 229. So, the array ends up recursively referencing itself. Once decoded, data = [data, 'foo']

I have found that this behavior is non-deterministic in my setup, and likely depends on the resolution order of multiple deferred Remix loaders. Reducing the sleep timeouts closer to zero (ensuring that this loader resolves first) generally reduces the failure rate.

I am still unsuccessful in creating a minimal reproduction of this case, but I'll eagerly add any information that would be helpful in resolving this issue

jacob-ebey commented 1 month ago

Should be fixed.