Repository navigation
perf(yet_another_json_isolate,postgrest,functions)!: encode request bodies to UTF-8 bytes on the calling isolate in slices - #2057
Conversation
…odies to UTF-8 bytes on the calling isolate in slices
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Codec contract and sliced encoders packages/yet_another_json_isolate/lib/src/async_json_codec.dart, packages/yet_another_json_isolate/lib/src/sliced_json_encoder.dart, packages/yet_another_json_isolate/test/*, packages/yet_another_json_isolate/pubspec.yaml, sdk-compliance.yaml |
AsyncJsonCodec adds encodeBytes and requires callers to keep input unchanged until encoding completes. String and byte encoders batch top-level lists longer than 8 elements, adjust batch sizes toward 500 microseconds, and yield between batches when a slice reaches 2,000 microseconds. Tests cover output, errors, and yielding. |
Platform codec implementation packages/yet_another_json_isolate/lib/src/json_isolate_*, packages/yet_another_json_isolate/lib/yet_another_json_isolate.dart, packages/yet_another_json_isolate/README.md, packages/yet_another_json_isolate/test/*, sdk-compliance.yaml |
IO and web implementations expose encodeBytes and use the sliced encoders for string and byte output. Documentation distinguishes sliced list encoding from decoding behavior. Tests cover lifecycle and encoding behavior. |
Request body encoding integration packages/postgrest/lib/src/postgrest_builder.dart, packages/postgrest/test/json_codec_test.dart, packages/supabase_functions/lib/src/functions_client.dart, packages/supabase_functions/test/functions_dart_test.dart, packages/supabase_flutter/test/* |
PostgREST sends encoded write bodies through bodyBytes. Functions uses byte encoding for JSON requests with UTF-8 encoding, and string encoding for other media types or encodings. Codec test implementations add encodeBytes. |
Priority:
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Bug fix
Sequence Diagram(s)
sequenceDiagram
participant Caller
participant FunctionsClient
participant AsyncJsonCodec
participant HTTPRequest
Caller->>FunctionsClient: invoke with request body
alt JSON media type and UTF-8 encoding
FunctionsClient->>AsyncJsonCodec: encodeBytes(value)
AsyncJsonCodec-->>FunctionsClient: UTF-8 JSON bytes
else Other media type or encoding
FunctionsClient->>AsyncJsonCodec: encode(value)
AsyncJsonCodec-->>FunctionsClient: JSON string
end
FunctionsClient->>HTTPRequest: send encoded request body
sequenceDiagram
participant Caller
participant PostgRESTBuilder
participant AsyncJsonCodec
participant HTTPRequest
Caller->>PostgRESTBuilder: build write request
PostgRESTBuilder->>AsyncJsonCodec: encodeBytes(value)
AsyncJsonCodec-->>PostgRESTBuilder: UTF-8 JSON bytes
PostgRESTBuilder->>HTTPRequest: set bodyBytes
Merge Risk: 
Minimal · up to 2b4aa
-
The reviewed encoding changes are mergeable after normal checks; no actionable risk remains.
Pre-merge checks: 
5
-
Full details: Title check
-
Explanation
The title uses the conventional type(scope)! prefix and names the main change: request bodies are now encoded to UTF-8 bytes on the calling isolate in slices. The changeset matches this. It adds encodeBytes and sliced encoders in yet_another_json_isolate, and it switches PostgrestBuilder and FunctionsClient to send bodyBytes for JSON requests. It also removes the isolate-based encode path. The title is specific and accurate. Its length of 122 characters is longer than ideal, and the scope lists three packages.
Full details: Docstring Coverage
-
Explanation
No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (16 skipped: 16 unsupported.)
-
Finishing Touches
-
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
…ast view (#2058) Stacked on #2057. Fixes [SDK-2402](https://linear.app/supabase/issue/SDK-2402/perfpostgrest-stop-wrapping-every-inserted-and-upserted-row-in-a-cast). ## Summary `_jsonRow`, which `insert()` and `upsert()` (and the typed `insertAll`/`upsertAll`) use to turn each row of a list into a JSON object, ended with `json.cast()`. `Map.cast` always returns a wrapping view, even when the row already is a `Map<String, dynamic>`, so the key check, the `columns` collection, `jsonEncode` and the copy of the body to the encoding isolate all went through a type-checking view. Rows that already are a `Map<String, dynamic>` are now returned as they are. Other maps with only String keys are copied once with `Map.from`. A row with a non-String key still throws `ArgumentError`. Measured with a canned HTTP transport, AOT compiled, on macOS arm64: | Insert, v3 default codec | Before | After | | --- | --: | --: | | 10000 rows, total | 10.78 ms | 8.25 ms | | 10000 rows, longest main isolate stall | 5.42 ms | 2.59 ms | | 1000 rows, total | 834 µs | 776 µs | ## Tests `insert()` and `upsert()` of a list holding a `Map<dynamic, dynamic>` and a `Map<String, Object?>` send the right body and `columns` parameter, and a row with a non-String key throws. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Bug Fixes** * Insert and upsert requests now correctly serialize rows provided as maps with string keys, including maps using different value types. * Request columns are correctly included based on the row keys. * Rows with non-string keys continue to be rejected with an `ArgumentError`. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Large inserts were slower in v3 than in v2, and moving their encoding to an isolate did not take the work off the calling isolate.
Isolate.runcopies the whole payload on the calling isolate before encoding it, and the copy grows with the payload. On top of that, every client encoded the body twice: to a JSON string, then to UTF-8 when the string was assigned torequest.body.This encodes request bodies straight to UTF-8 bytes on the calling isolate. A top-level list, such as the rows of a bulk insert, is encoded in batches that yield to the event loop every 2 ms, so a frame is never blocked for long however large the payload is. Decoding is unchanged and still runs on short-lived isolates.
Changes
AsyncJsonCodecgainsencodeBytes, the counterpart ofdecodeBytes. Both encode methods may now read the value until their future completes.YAJsonIsolateencodes on the calling isolate, slicing a top-level list. The isolate encode path and the pass that estimated the payload size beforehand are removed. Web uses the same encoder, so it no longer blocks for the whole encode either.PostgrestBuildersends the body withrequest.bodyBytes. The content type staysapplication/json.FunctionsClientsends a body with a JSON content type as bytes. Any other content type still goes through the string path, so a caller-provided charset such asiso-8859-1keeps working.Breaking change
An
AsyncJsonCodecimplementation has to addencodeBytes.AsyncJsonCodecis new in v3, so this only affects the v3 pre-releases.Measurements
From a benchmark of the client itself, using a canned HTTP transport, compiled ahead of time on macOS arm64. "Frame blocked" is the median of the longest time per request in which the calling isolate could not run a timer.
Together with SDK-2402, which stops wrapping every inserted row in a cast view, the 10000-row insert takes 5.48 ms and blocks a frame for 2.90 ms. Reads are unchanged.
Closes SDK-2401
Summary by CodeRabbit