Skip to content

Commit 1d2bea8

Browse files
authored
feat(dart): support dart web platform (#3608)
## Why? Dart web builds use JavaScript number semantics, so raw Dart `int` cannot safely represent the full 64-bit value space used by Fory xlang integer encodings. The Dart runtime needs browser-compatible buffer, generated serializer, and 64-bit wrapper implementations that preserve wire compatibility instead of silently corrupting JS-unsafe values. ## What does this PR do? - Adds Dart web support through conditional native/web implementations for `Buffer`, generated cursors, and `Int64` / `Uint64` value and list wrappers. - Updates code generation and serializers to handle full-range signed and unsigned 64-bit fields on web, while keeping Dart `int` fast paths for JS-safe values. - Adds explicit range checks so web builds reject JS-unsafe Dart `int` conversions instead of writing or reading imprecise bytes. - Extends Dart serializer, buffer, generated-field, numeric-wrapper, typed-array, and time tests for 64-bit edge cases and browser behavior. - Adds Chrome test coverage to CI with `dart test -p chrome` for the Dart package. - Documents Dart web support, 64-bit integer rules, custom serializer guidance, and troubleshooting steps. ## Related issues Closes #3600 ## AI Contribution Checklist - [ ] Substantial AI assistance was used in this PR: `yes` / `no` - [ ] If `yes`, I included a completed [AI Contribution Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs) in this PR description and the required `AI Usage Disclosure`. - [ ] If `yes`, my PR description includes the required `ai_review` summary and screenshot evidence of the final clean AI review results from both fresh reviewers on the current PR diff or current HEAD after the latest code changes. ## Does this PR introduce any user-facing change? Yes. Dart users can now use generated serializers and manual serializers on web/browser targets. Full-range 64-bit values should use `Int64` or `Uint64`; Dart `int` fields remain limited to JS-safe values on web. - [x] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark <img width="4200" height="900" alt="throughput" src="https://github.com/user-attachments/assets/ca422bc6-5345-4560-a35a-2678f4c54b9c" /> | Datatype | Operation | Fory TPS | Protobuf TPS | Fastest | | ---------------- | ----------- | --------: | -----------: | ------------ | | Struct | Serialize | 5,041,693 | 2,073,839 | fory (2.43x) | | Struct | Deserialize | 6,395,290 | 4,991,881 | fory (1.28x) | | Sample | Serialize | 1,783,688 | 552,140 | fory (3.23x) | | Sample | Deserialize | 2,124,197 | 934,794 | fory (2.27x) | | MediaContent | Serialize | 952,498 | 438,419 | fory (2.17x) | | MediaContent | Deserialize | 1,649,039 | 737,340 | fory (2.24x) | | StructList | Serialize | 1,945,119 | 399,007 | fory (4.87x) | | StructList | Deserialize | 2,119,403 | 764,832 | fory (2.77x) | | SampleList | Serialize | 475,413 | 52,512 | fory (9.05x) | | SampleList | Deserialize | 508,939 | 116,236 | fory (4.38x) | | MediaContentList | Serialize | 224,925 | 84,860 | fory (2.65x) | | MediaContentList | Deserialize | 387,070 | 154,392 | fory (2.51x) |
1 parent ad80be2 commit 1d2bea8

77 files changed

Lines changed: 6216 additions & 1243 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/fory-code-review/SKILL.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,34 @@ Find the highest-value bugs, regressions, and missing verification in Apache For
1111

1212
## Start Here
1313

14-
1. If the target is a GitHub PR, create a new local git worktree for the review before checking out or fetching the PR branch.
15-
2. Do not switch the current branch or reuse the current worktree for PR review unless the user explicitly asks for that.
16-
3. If reviewing against main, run `git fetch apache main` before diffing.
17-
4. Inspect the changed files first and cluster them by subsystem.
18-
5. Load only the references needed for the touched areas:
14+
1. Always perform the code review in a subagent. The main agent coordinates scope, gathers the subagent's findings, sanity-checks them, and reports the final review.
15+
2. Reuse the same review subagent for later review passes on the same feature unless the user explicitly asks to review that feature in a new subagent.
16+
3. Do not reuse a review subagent across different features; start a new subagent for each distinct feature or review topic.
17+
4. The review subagent must be read-only: it must not write code, apply patches, create commits, push branches, fix tests, or update docs. It sends review findings or an explicit no-findings result back to the caller.
18+
5. If the target is a GitHub PR, create a new local git worktree for the review before checking out or fetching the PR branch.
19+
6. Do not switch the current branch or reuse the current worktree for PR review unless the user explicitly asks for that.
20+
7. If reviewing against main, run `git fetch apache main` before diffing.
21+
8. Inspect the changed files first and cluster them by subsystem.
22+
9. Load only the references needed for the touched areas:
1923
- `references/review-checklist.md`
2024
- `references/lesson-derived-red-flags.md`
2125
- `references/validation-command-matrix.md`
2226
- matching runtime docs under `../../languages/*.md` when the patch is language-specific
2327

28+
## Subagent Reuse
29+
30+
- Treat each feature under review as the reuse key. A feature may be a PR, issue, branch, commit range, local diff topic, or clearly named subsystem change.
31+
- When the user asks for another pass on the same feature, send the updated context to the existing review subagent and ask it to continue from its prior review state.
32+
- When the user asks to review a different feature, spawn a fresh review subagent even if the same files, language, or subsystem are involved.
33+
- If the user explicitly requests a new subagent for the same feature, honor that request and do not reuse the prior subagent.
34+
- Keep implementation work, CI fixing, and non-review exploration out of the review subagent. The review subagent reports comments to the caller; the caller decides whether any separate implementation task should happen.
35+
2436
## Review Workflow
2537

2638
1. Define the review target.
2739

2840
- Determine whether the user wants a review of a PR, branch, commit range, or local diff.
41+
- Assign the target to the correct review subagent using the Subagent Reuse rules before inspecting code.
2942
- For a GitHub PR, create and use a dedicated local worktree for the review. Keep the current worktree and branch unchanged unless the user explicitly requests otherwise.
3043
- In that worktree, fetch the PR head and review there instead of checking out the PR branch in the current workspace.
3144
- Prefer `git diff --stat` first, then inspect the full patch only for touched subsystems.
@@ -63,6 +76,7 @@ Find the highest-value bugs, regressions, and missing verification in Apache For
6376
## Hard Rules
6477

6578
- Do not lead with style nits when there are correctness or verification risks.
79+
- Do not write code, edit files, apply patches, commit, push, or fix tests from the review subagent.
6680
- Treat benchmark-shape tricks, payload-specific caches, and methodology changes as real findings.
6781
- Treat undocumented public API additions, compatibility shims, and one-line wrapper growth as findings when they increase maintenance surface without clear need.
6882
- Treat protocol or performance claims without verification evidence as incomplete.

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,10 @@ jobs:
999999
run: |
10001000
cd dart/packages/fory-test
10011001
dart test
1002+
- name: Run web tests
1003+
run: |
1004+
cd dart/packages/fory
1005+
dart test -p chrome
10021006
- name: Run code analysis
10031007
run: |
10041008
cd dart/packages/fory-test

dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ void _verifyDecimalCase() {
244244

245245
Uint8List _hashBytes(int low, int high) {
246246
final buffer = Buffer();
247-
buffer.writeInt64(low);
248-
buffer.writeInt64(high);
247+
buffer.writeInt64(Int64(low));
248+
buffer.writeInt64(Int64(high));
249249
return buffer.toBytes();
250250
}
251251

dart/packages/fory/README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ cases.
99
## Features
1010

1111
- Cross-language serialization with the Fory xlang format
12+
- Dart VM/AOT, Flutter, and web platform support
1213
- Generated serializers for annotated structs and enums
1314
- Compatible mode for schema evolution
1415
- Optional reference tracking for shared and circular object graphs
1516
- Manual serializers for external types, custom payloads, and unions
16-
- Explicit xlang value wrappers such as `Int32`, `Uint32`, `Float16`,
17-
`Bfloat16`, `Float32`, `LocalDate`, and `Timestamp`, plus `Duration`
17+
- Explicit xlang value class such as `Int32`, `Int64`, `Uint32`, `Uint64`,
18+
`Float16`, `Bfloat16`, `Float32`, `LocalDate`, and `Timestamp`, plus `Duration`
1819
support
1920

2021
## Getting Started
@@ -193,13 +194,13 @@ final class PersonSerializer extends Serializer<Person> {
193194
void write(WriteContext context, Person value) {
194195
final buffer = context.buffer;
195196
buffer.writeUtf8(value.name);
196-
buffer.writeInt64(value.age);
197+
buffer.writeInt64FromInt(value.age);
197198
}
198199
199200
@override
200201
Person read(ReadContext context) {
201202
final buffer = context.buffer;
202-
return Person(buffer.readUtf8(), buffer.readInt64());
203+
return Person(buffer.readUtf8(), buffer.readInt64AsInt());
203204
}
204205
}
205206
@@ -222,9 +223,9 @@ void main() {
222223

223224
Dart has no native fixed-width 8/16/32-bit integer, unsigned 64-bit integer,
224225
or single-precision float types. Fory Dart provides thin wrapper types
225-
(`Int8`, `Int16`, `Int32`, `Uint8`, `Uint16`, `Uint32`, `Uint64`, `Float16`,
226-
`Bfloat16`, `Float32`) imported from `package:fory/fory.dart` to represent
227-
these xlang wire types. For 16-bit floating-point arrays, Dart exposes
226+
(`Int8`, `Int16`, `Int32`, `Int64`, `Uint8`, `Uint16`, `Uint32`, `Uint64`,
227+
`Float16`, `Bfloat16`, `Float32`) imported from `package:fory/fory.dart` to
228+
represent these xlang wire types. For 16-bit floating-point arrays, Dart exposes
228229
`Float16List` and `Bfloat16List` as contiguous fixed-length buffers.
229230

230231
| Fory xlang type | Dart type |
@@ -233,7 +234,7 @@ these xlang wire types. For 16-bit floating-point arrays, Dart exposes
233234
| int8 | `fory.Int8` (wrapper) |
234235
| int16 | `fory.Int16` (wrapper) |
235236
| int32 | `fory.Int32` (wrapper) |
236-
| int64 | `int` |
237+
| int64 | `int` or `fory.Int64` |
237238
| uint8 | `fory.Uint8` (wrapper) |
238239
| uint16 | `fory.Uint16` (wrapper) |
239240
| uint32 | `fory.Uint32` (wrapper) |
@@ -257,6 +258,9 @@ these xlang wire types. For 16-bit floating-point arrays, Dart exposes
257258
| int16_array | `Int16List` |
258259
| int32_array | `Int32List` |
259260
| int64_array | `Int64List` |
261+
| uint16_array | `Uint16List` |
262+
| uint32_array | `Uint32List` |
263+
| uint64_array | `Uint64List` |
260264
| float16_array | `Float16List` |
261265
| bfloat16_array | `Bfloat16List` |
262266
| float32_array | `Float32List` |
@@ -276,8 +280,8 @@ The main exported API includes:
276280
annotations
277281
- `Int32Type`, `Int64Type`, `Uint32Type`, `Uint64Type` — numeric encoding
278282
overrides
279-
- Numeric wrappers: `Int8`, `Int16`, `Int32`, `Uint8`, `Uint16`, `Uint32`,
280-
`Uint64`, `Float16`, `Bfloat16`, `Float32`
283+
- Numeric wrappers: `Int8`, `Int16`, `Int32`, `Int64`, `Uint8`, `Uint16`,
284+
`Uint32`, `Uint64`, `Float16`, `Bfloat16`, `Float32`
281285
- Temporal types: `LocalDate`, `Timestamp`, `Duration`
282286

283287
## Cross-Language Notes

dart/packages/fory/example/manual_serializer.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ final class Person {
2323
Person(this.name, this.age);
2424

2525
final String name;
26-
final int age;
26+
final Int64 age;
2727
}
2828

2929
final class PersonSerializer extends Serializer<Person> {
@@ -52,7 +52,7 @@ void main() {
5252
typeName: 'Person',
5353
);
5454

55-
final person = Person('Ada', 36);
55+
final person = Person('Ada', Int64(36));
5656
final bytes = fory.serialize(person);
5757
final roundTrip = fory.deserialize<Person>(bytes);
5858
print(roundTrip.name);

dart/packages/fory/lib/fory.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export 'src/annotation/fory_struct.dart';
3030
export 'src/annotation/fory_union.dart';
3131
export 'src/annotation/type_spec.dart';
3232
export 'src/annotation/numeric_types.dart';
33-
export 'src/buffer.dart'
33+
export 'src/memory/buffer.dart'
3434
hide
3535
bufferByteData,
3636
bufferBytes,
@@ -55,6 +55,7 @@ export 'src/types/float16.dart';
5555
export 'src/types/float32.dart';
5656
export 'src/types/int16.dart';
5757
export 'src/types/int32.dart';
58+
export 'src/types/int64.dart';
5859
export 'src/types/int8.dart';
5960
export 'src/types/local_date.dart';
6061
export 'src/types/timestamp.dart';

0 commit comments

Comments
 (0)