This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/fory-site.git
commit 2cb94f79cc37f4cbaf000386e17d560367ba3b5d Author: chaokunyang <[email protected]> AuthorDate: Thu Apr 23 11:04:02 2026 +0000 π synced local 'docs/guide/' with remote 'docs/guide/' --- docs/guide/dart/cross-language.md | 2 +- docs/guide/dart/custom-serializers.md | 4 +- docs/guide/dart/field-configuration.md | 5 +- docs/guide/dart/index.md | 28 ++-- docs/guide/dart/supported-types.md | 32 +++-- docs/guide/dart/troubleshooting.md | 45 ++++++- docs/guide/dart/web-platform-support.md | 218 ++++++++++++++++++++++++++++++++ 7 files changed, 308 insertions(+), 26 deletions(-) diff --git a/docs/guide/dart/cross-language.md b/docs/guide/dart/cross-language.md index a659c9257..a4554b8c4 100644 --- a/docs/guide/dart/cross-language.md +++ b/docs/guide/dart/cross-language.md @@ -168,7 +168,7 @@ Fory matches fields by name or by stable field ID. For robust cross-language int Because Dart `int` is not itself a promise about the exact xlang wire width, prefer wrappers or numeric field annotations when exact cross-language interpretation matters: - `Int32` for xlang `int32` -- `UInt32` for xlang `uint32` +- `Uint32` for xlang `uint32` - `Float16`, `Bfloat16`, and `Float32` for reduced-width floating point - `Float16List` and `Bfloat16List` for 16-bit floating-point array payloads - `Timestamp`, `LocalDate`, and `Duration` for explicit temporal semantics diff --git a/docs/guide/dart/custom-serializers.md b/docs/guide/dart/custom-serializers.md index 280eb1ed8..8c8c1ee44 100644 --- a/docs/guide/dart/custom-serializers.md +++ b/docs/guide/dart/custom-serializers.md @@ -48,13 +48,13 @@ final class PersonSerializer extends Serializer<Person> { void write(WriteContext context, Person value) { final buffer = context.buffer; buffer.writeUtf8(value.name); - buffer.writeInt64(value.age); + buffer.writeInt64FromInt(value.age); } @override Person read(ReadContext context) { final buffer = context.buffer; - return Person(buffer.readUtf8(), buffer.readInt64()); + return Person(buffer.readUtf8(), buffer.readInt64AsInt()); } } ``` diff --git a/docs/guide/dart/field-configuration.md b/docs/guide/dart/field-configuration.md index 4f16f2b7a..5e0b439f0 100644 --- a/docs/guide/dart/field-configuration.md +++ b/docs/guide/dart/field-configuration.md @@ -110,7 +110,10 @@ class Sample { Available annotations: `@Int32Type`, `@Int64Type`, `@Uint8Type`, `@Uint16Type`, `@Uint32Type`, `@Uint64Type`. -Alternatively, use the explicit wrapper types (`Int32`, `UInt32`, etc.) described in [Supported Types](supported-types.md). +Alternatively, use explicit wrapper types such as `Int32`, `Int64`, `Uint32`, +and `Uint64` as described in [Supported Types](supported-types.md). Wrappers use +compact varint encodings by default; use annotations or generated field +metadata when a fixed-width or tagged encoding is required. ## Aligning Fields Across Languages diff --git a/docs/guide/dart/index.md b/docs/guide/dart/index.md index 0e814c447..cab137c48 100644 --- a/docs/guide/dart/index.md +++ b/docs/guide/dart/index.md @@ -24,6 +24,7 @@ Apache Foryβ’ Dart lets you serialize Dart objects to bytes and deserialize the ## Why Fory Dart? - **Cross-language**: serialize in Dart, deserialize in Java, Go, C#, and more without writing any glue code +- **Platform support**: use the same generated-serializer API on Dart VM/AOT, Flutter, and web - **Fast**: generated serializer code replaces reflection at runtime - **Schema evolution**: add or remove fields without breaking existing messages - **Circular references**: optional reference tracking handles shared or recursive object graphs @@ -114,25 +115,26 @@ dart run build_runner build --delete-conflicting-outputs - `fory.deserialize<T>(bytes)` β returns a `T` - `@ForyStruct()` β marks a class for code generation - `@ForyField(...)` β per-field options (skip, ID, nullability, references) -- Integer wrappers: `Int8`, `Int16`, `Int32`, `Uint8`, `Uint16`, `Uint32`, `Uint64` +- Integer wrappers: `Int8`, `Int16`, `Int32`, `Int64`, `Uint8`, `Uint16`, `Uint32`, `Uint64` - Float wrappers: `Float16`, `Bfloat16`, `Float32` - 16-bit float arrays: `Float16List`, `Bfloat16List` - Time types: `LocalDate`, `Timestamp`, `Duration` ## Documentation -| Topic | Description | -| --------------------------------------------- | --------------------------------------------------------------- | -| [Configuration](configuration.md) | Runtime options, compatible mode, and safety limits | -| [Basic Serialization](basic-serialization.md) | `serialize`, `deserialize`, generated registration, root graphs | -| [Code Generation](code-generation.md) | `@ForyStruct`, build runner, and generated namespaces | -| [Type Registration](type-registration.md) | ID-based vs name-based registration and registration rules | -| [Custom Serializers](custom-serializers.md) | Manual `Serializer<T>` implementations and unions | -| [Field Configuration](field-configuration.md) | `@ForyField`, field IDs, nullability, references, polymorphism | -| [Supported Types](supported-types.md) | Built-in xlang values, wrappers, collections, and structs | -| [Schema Evolution](schema-evolution.md) | Compatible structs and evolving schemas | -| [Cross-Language](cross-language.md) | Interoperability rules and field alignment | -| [Troubleshooting](troubleshooting.md) | Common errors, diagnostics, and validation steps | +| Topic | Description | +| ----------------------------------------------- | --------------------------------------------------------------- | +| [Configuration](configuration.md) | Runtime options, compatible mode, and safety limits | +| [Basic Serialization](basic-serialization.md) | `serialize`, `deserialize`, generated registration, root graphs | +| [Code Generation](code-generation.md) | `@ForyStruct`, build runner, and generated namespaces | +| [Type Registration](type-registration.md) | ID-based vs name-based registration and registration rules | +| [Custom Serializers](custom-serializers.md) | Manual `Serializer<T>` implementations and unions | +| [Field Configuration](field-configuration.md) | `@ForyField`, field IDs, nullability, references, polymorphism | +| [Supported Types](supported-types.md) | Built-in xlang values, wrappers, collections, and structs | +| [Schema Evolution](schema-evolution.md) | Compatible structs and evolving schemas | +| [Cross-Language](cross-language.md) | Interoperability rules and field alignment | +| [Web Platform Support](web-platform-support.md) | Dart VM/AOT, Flutter, and web support, limits, and validation | +| [Troubleshooting](troubleshooting.md) | Common errors, diagnostics, and validation steps | ## Related Resources diff --git a/docs/guide/dart/supported-types.md b/docs/guide/dart/supported-types.md index 3d309ee5b..e7ee82e73 100644 --- a/docs/guide/dart/supported-types.md +++ b/docs/guide/dart/supported-types.md @@ -37,20 +37,36 @@ The following Dart types serialize directly without any special handling: ## Integer Wrappers -Dart `int` is 64-bit at runtime. If the peer language expects a 32-bit integer (Java `int`, Go `int32`, C# `int`) and you send a Dart `int`, the deserialization may fail or silently truncate. +Dart VM/native `int` can represent signed 64-bit values, while Dart web `int` +is limited to JavaScript-safe integer precision. If the peer language expects a +32-bit integer (Java `int`, Go `int32`, C# `int`) and you send a Dart `int`, +the deserialization may fail or silently truncate. For browser and Flutter web +precision rules, see [Web Platform Support](web-platform-support.md). -Use an integer wrapper class to pin the exact wire width: +Use an integer wrapper or field annotation to select the wire type explicitly: ```dart final Int8 tiny = Int8(-1); // 8-bit signed final Int16 shortValue = Int16(7); // 16-bit signed -final Int32 age = Int32(36); // 32-bit signed β matches Java int, C# int, Go int32 -final UInt8 flags = UInt8(255); // 8-bit unsigned -final UInt16 port = UInt16(65535); // 16-bit unsigned -final UInt32 count = UInt32(4000000000); // 32-bit unsigned +final Int32 age = Int32(36); // 32-bit signed, varint by default +final Int64 seq = Int64(0); // signed 64-bit, varint by default +final Uint8 flags = Uint8(255); // 8-bit unsigned +final Uint16 port = Uint16(65535); // 16-bit unsigned +final Uint32 count = Uint32(4000000000); // 32-bit unsigned, varint by default +final Uint64 offset = Uint64(0); // unsigned 64-bit, varint by default ``` -Each wrapper clamps the stored value to the target bit width. +Each wrapper clamps or normalizes the stored value to the target bit width. +Root `Int32`, `Int64`, `Uint32`, and `Uint64` values use compact varint wire +types by default. Use `@Int64Type`, `@Uint32Type`, `@Uint64Type`, or generated +field metadata when a fixed-width or tagged encoding is required. + +On Dart VM, `Int64` and `Uint64` are extension types over `int`. Once a value is +passed through an `Object`-typed dynamic/root boundary, the VM cannot recover +whether it was originally a plain `int`, `Int64`, or `Uint64`. Use generated +field metadata or explicit `Buffer` APIs when native VM payloads must preserve +unsigned 64-bit identity across dynamic boundaries. Dart web uses wrapper +classes, so web root `Uint64` values keep `varuint64` metadata. ## Floating-Point Wrappers @@ -82,7 +98,7 @@ final timeout = const Duration(seconds: 30); The temporal wrappers expose conversion helpers: - `Timestamp.fromDateTime(...)` and `timestamp.toDateTime()` -- `LocalDate.fromEpochDay(...)`, `date.toEpochDay()` +- `LocalDate.fromEpochDay(Int64(...))`, `date.toEpochDay()` returns `Int64` - `LocalDate.fromDateTime(...)` and `date.toDateTime()` `Duration` support in Dart is exact to microseconds. Incoming xlang duration diff --git a/docs/guide/dart/troubleshooting.md b/docs/guide/dart/troubleshooting.md index 9266d05fb..95e2c1776 100644 --- a/docs/guide/dart/troubleshooting.md +++ b/docs/guide/dart/troubleshooting.md @@ -1,6 +1,6 @@ --- title: Troubleshooting -sidebar_position: 10 +sidebar_position: 11 id: dart_troubleshooting license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -78,6 +78,48 @@ Checklist: 4. `Timestamp` / `LocalDate` instead of raw `DateTime` for date/time fields. 5. `compatible: true` on **both** sides if using schema evolution. +## Int64 or Uint64 values fail on web + +On Dart VM builds, Dart `int` can represent signed 64-bit values. On Dart web +builds, Dart `int` values are backed by JavaScript numbers and are only precise +inside the JS-safe integer range: + +```text +-9007199254740991 <= value <= 9007199254740991 +``` + +If a generated serializer writes an `int64` field declared as Dart `int`, +web builds reject values outside that range instead of silently writing +corrupted bytes. To exchange full signed 64-bit values on web, declare the +field as Fory's `Int64` wrapper: + +```dart +@ForyStruct() +class LedgerEntry { + LedgerEntry(); + + Int64 sequence = Int64(0); // full signed 64-bit range on VM and web +} +``` + +For unsigned 64-bit values, prefer `Uint64` rather than Dart `int`. Dart `int` +cannot represent the full `uint64` range on either VM or web: + +```dart +@ForyStruct() +class FileBlock { + FileBlock(); + + Uint64 offset = Uint64(0); // full unsigned 64-bit range +} +``` + +`@Int64Type` changes the wire encoding for a Dart `int` field, but it does not +remove the web integer precision limit. Use `Int64` for full-range signed +values and `Uint64` for full-range unsigned values. See +[Web Platform Support](web-platform-support.md) for the full browser support +matrix and migration guidance. + ## Running Tests Locally Main Dart package: @@ -101,3 +143,4 @@ dart test - [Cross-Language](cross-language.md) - [Code Generation](code-generation.md) - [Custom Serializers](custom-serializers.md) +- [Web Platform Support](web-platform-support.md) diff --git a/docs/guide/dart/web-platform-support.md b/docs/guide/dart/web-platform-support.md new file mode 100644 index 000000000..3ca615426 --- /dev/null +++ b/docs/guide/dart/web-platform-support.md @@ -0,0 +1,218 @@ +--- +title: Web Platform Support +sidebar_position: 10 +id: dart_web_platform_support +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Fory Dart supports Dart VM/AOT, Flutter, browser, and Flutter web builds +through generated serializers and platform-specific runtime implementations. +The public API and registration flow are the same across these platforms, but +web builds have stricter integer precision rules because Dart `int` is +represented by JavaScript numbers. + +## Supported Targets + +The Dart runtime supports: + +- Dart VM/JIT applications. +- Dart AOT/native applications. +- Flutter mobile and desktop applications. +- Dart applications compiled to JavaScript for browsers. +- Flutter web applications. +- Generated `@ForyStruct` serializers and manually registered serializers on + all supported targets. + +## Code Generation Is Required + +Fory Dart uses explicit registration instead of runtime reflection. For +annotated structs, run code generation and register the generated serializer +before serializing or deserializing values: + +```dart +import 'package:fory/fory.dart'; + +part 'account.fory.dart'; + +@ForyStruct() +class Account { + Account(); + + String name = ''; + Int64 sequence = Int64(0); +} + +void main() { + final fory = Fory(); + AccountFory.register( + fory, + Account, + namespace: 'example', + typeName: 'Account', + ); + + final bytes = fory.serialize(Account()..name = 'web'); + final account = fory.deserialize<Account>(bytes); + print(account.name); +} +``` + +Generate the companion file before building or testing: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +``` + +The registration call is the same on VM/AOT, Flutter, and web. Manual +serializers use `registerSerializer(...)`; generated structs use the generated +`register` wrapper. + +## 64-Bit Integer Rules + +Dart VM `int` values are signed 64-bit values. Dart web `int` values are backed +by JavaScript numbers and are precise only in the JS-safe integer range: + +```text +-9007199254740991 <= value <= 9007199254740991 +``` + +Use this rule when choosing field types: + +| Logical value | Recommended Dart field type on web | Notes | +| ---------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | +| Signed 64-bit value within JS-safe range | `int` | Works with default `int64` mapping and `@Int64Type` encodings. | +| Full signed 64-bit range | `Int64` | Preserves values outside the JS-safe range. | +| Unsigned 64-bit value | `Uint64` | Required for values that do not fit in signed or JS-safe Dart `int`. | +| 8/16/32-bit integer | `Int8`, `Int16`, `Int32`, `Uint8`, `Uint16`, `Uint32` or annotations | Use wrappers or numeric annotations to match peer runtimes exactly. | + +`@Int64Type` controls the wire encoding of a Dart `int` field: + +```dart +@ForyStruct() +class SafeCounter { + SafeCounter(); + + @Int64Type(encoding: LongEncoding.tagged) + int count = 0; // keep web values inside the JS-safe range +} +``` + +It does not make Dart `int` capable of storing every 64-bit value on web. For +full-range signed values, use `Int64`: + +```dart +@ForyStruct() +class FullRangeCounter { + FullRangeCounter(); + + Int64 count = Int64(0); +} +``` + +For unsigned values, use `Uint64`: + +```dart +@ForyStruct() +class StorageExtent { + StorageExtent(); + + Uint64 byteOffset = Uint64(0); +} +``` + +## Custom Serializers + +Custom serializers can use the same `Buffer`, `WriteContext`, and `ReadContext` +APIs on VM/AOT, Flutter, and web. For 64-bit values: + +- Use `buffer.writeInt64(Int64(...))` and `buffer.readInt64()` for full-range + signed 64-bit values. +- Use `buffer.writeUint64(Uint64(...))` and `buffer.readUint64()` for full-range + unsigned 64-bit values. +- Use `writeInt64FromInt`, `writeVarInt64FromInt`, and matching `AsInt` reads + only when the value is intended to be a Dart `int` and therefore must stay + JS-safe on web. + +Example: + +```dart +final class OffsetSerializer extends Serializer<StorageExtent> { + const OffsetSerializer(); + + @override + void write(WriteContext context, StorageExtent value) { + context.buffer.writeUint64(value.byteOffset); + } + + @override + StorageExtent read(ReadContext context) { + return StorageExtent()..byteOffset = context.buffer.readUint64(); + } +} +``` + +## Collections And Typed Arrays + +`List`, `Set`, `Map`, `Uint8List`, numeric typed arrays, `Int64List`, and +`Uint64List` are supported on web. The `Int64List` and `Uint64List` +implementations preserve 64-bit values without depending on JavaScript integer +precision. Use the Fory wrapper list types when the wire type is `int64_array` +or `uint64_array`. + +## Testing Browser Builds + +Run the package tests in both VM and Chrome when changing code that must work on +web: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +dart test +dart test -p chrome +``` + +If Chrome tests fail with a stale generated file or missing part file, rerun +`build_runner` and then retry the test command from `dart/packages/fory`. + +## Common Web Failures + +### `Dart int value ... is outside the JS-safe signed int64 range` + +The serializer is trying to write a Dart `int` as a signed 64-bit value on web, +but the value is outside the range that JavaScript numbers can represent +exactly. Change the field type to `Int64`, or keep the value inside the JS-safe +range. + +### `Int64 value ... is not a JS-safe int` + +The deserializer read a full-range `Int64`, but the target field or custom +serializer asked for a Dart `int`. Change the field type to `Int64`, or decode +with `readInt64()` instead of an `AsInt` helper. + +### `Uint64 value ... is not a JS-safe int` + +The code is converting a `Uint64` to Dart `int` on web. Keep the value as +`Uint64` unless the application has already validated that it is in the +JS-safe non-negative range. + +## Related Topics + +- [Supported Types](supported-types.md) +- [Field Configuration](field-configuration.md) +- [Code Generation](code-generation.md) +- [Troubleshooting](troubleshooting.md) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
