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
The following commit(s) were added to refs/heads/main by this push:
new bf637f5a35 π synced local 'docs/guide/' with remote 'docs/guide/'
bf637f5a35 is described below
commit bf637f5a3525116072e505a68f1eb2f27b114bc0
Author: chaokunyang <[email protected]>
AuthorDate: Tue Apr 14 13:24:15 2026 +0000
π synced local 'docs/guide/' with remote 'docs/guide/'
---
docs/guide/cpp/_category_.json | 2 +-
docs/guide/csharp/_category_.json | 2 +-
docs/guide/dart/_category_.json | 2 +-
docs/guide/dart/basic-serialization.md | 38 +++++----
docs/guide/dart/code-generation.md | 45 +++++-----
docs/guide/dart/configuration.md | 67 ++++++++-------
docs/guide/dart/cross-language.md | 48 ++++++-----
docs/guide/dart/custom-serializers.md | 63 ++++++--------
docs/guide/dart/field-configuration.md | 73 ++++++++---------
docs/guide/dart/index.md | 46 +++++------
docs/guide/dart/schema-evolution.md | 64 ++++++++-------
docs/guide/dart/supported-types.md | 92 ++++++++-------------
docs/guide/dart/troubleshooting.md | 60 +++++++-------
docs/guide/dart/type-registration.md | 54 ++++++------
docs/guide/go/_category_.json | 2 +-
docs/guide/java/_category_.json | 2 +-
docs/guide/{cpp => javascript}/_category_.json | 4 +-
docs/guide/javascript/basic-serialization.md | 18 ++--
docs/guide/javascript/cross-language.md | 85 +++++++++----------
docs/guide/javascript/index.md | 79 +++++++-----------
docs/guide/javascript/references.md | 22 +++--
docs/guide/javascript/schema-evolution.md | 47 +++++------
docs/guide/javascript/supported-types.md | 99 ++++++++++------------
docs/guide/javascript/troubleshooting.md | 50 +++++-------
docs/guide/javascript/type-registration.md | 109 +++++++++++--------------
docs/guide/kotlin/_category_.json | 2 +-
docs/guide/python/_category_.json | 2 +-
docs/guide/rust/_category_.json | 2 +-
docs/guide/scala/_category_.json | 2 +-
docs/guide/swift/_category_.json | 2 +-
docs/guide/xlang/_category_.json | 2 +-
31 files changed, 540 insertions(+), 645 deletions(-)
diff --git a/docs/guide/cpp/_category_.json b/docs/guide/cpp/_category_.json
index c543ab1388..9660bafa82 100644
--- a/docs/guide/cpp/_category_.json
+++ b/docs/guide/cpp/_category_.json
@@ -1,6 +1,6 @@
{
"label": "C++",
- "position": 3,
+ "position": 4,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/csharp/_category_.json
b/docs/guide/csharp/_category_.json
index 4ddedc6210..c5e03ee3c5 100644
--- a/docs/guide/csharp/_category_.json
+++ b/docs/guide/csharp/_category_.json
@@ -1,6 +1,6 @@
{
"label": "C#",
- "position": 9,
+ "position": 7,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/dart/_category_.json b/docs/guide/dart/_category_.json
index 47b8891a2e..b2d06cfcd2 100644
--- a/docs/guide/dart/_category_.json
+++ b/docs/guide/dart/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Dart",
- "position": 11,
+ "position": 9,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/dart/basic-serialization.md
b/docs/guide/dart/basic-serialization.md
index e7fa79d95d..1f261937ed 100644
--- a/docs/guide/dart/basic-serialization.md
+++ b/docs/guide/dart/basic-serialization.md
@@ -19,9 +19,11 @@ license: |
limitations under the License.
---
-This page covers the root serialization APIs in Apache Foryβ’ Dart.
+This page shows how to serialize and deserialize values with Apache Foryβ’ Dart.
-## Create and Reuse `Fory`
+## Create a `Fory` Instance
+
+Create one instance and reuse it β creating a new `Fory` for every call wastes
resources.
```dart
import 'package:fory/fory.dart';
@@ -29,8 +31,6 @@ import 'package:fory/fory.dart';
final fory = Fory();
```
-Reuse the same runtime instance across calls. `Fory` owns the reusable
`Buffer`, `WriteContext`, `ReadContext`, and `TypeResolver` services for that
runtime.
-
## Serialize and Deserialize Annotated Types
```dart
@@ -65,11 +65,11 @@ void main() {
}
```
-`deserialize<T>` uses the wire metadata first, then checks that the result is
assignable to `T`.
+`deserialize<T>` returns the decoded value cast to `T`. If the payload
describes a different type than `T`, it throws.
-## Null Root Values
+## Null Values
-The root xlang frame starts with a one-byte bitmap. A null root payload is
encoded by the root header, so serializing `null` works directly:
+Serializing `null` is supported directly:
```dart
final fory = Fory();
@@ -91,25 +91,27 @@ final bytes = fory.serialize(<Object?>[
final value = fory.deserialize<List<Object?>>(bytes);
```
-For heterogeneous collections, deserialize to `Object?`, `List<Object?>`,
`Map<Object?, Object?>`, or a generated struct type that matches the wire
schema.
+For heterogeneous collections, deserialize to `Object?`, `List<Object?>`, or
`Map<Object?, Object?>`.
+
+## Reference Tracking
-## Root Reference Tracking
+By default, Fory does not track object identity β if the same object appears
twice in a list, it is serialized twice. Enable reference tracking when your
data contains shared references or circular structures.
-Use `trackRef: true` only when the root value itself is a graph or container
with repeated references and there is no field metadata to request reference
tracking.
+For a top-level collection:
```dart
final fory = Fory();
final shared = String.fromCharCodes('shared'.codeUnits);
final bytes = fory.serialize(<Object?>[shared, shared], trackRef: true);
final roundTrip = fory.deserialize<List<Object?>>(bytes);
-print(identical(roundTrip[0], roundTrip[1]));
+print(identical(roundTrip[0], roundTrip[1])); // true
```
-Inside generated structs, prefer field-level reference metadata with
`@ForyField(ref: true)`.
+For fields inside a generated struct, use `@ForyField(ref: true)` on that
field instead.
-## Buffer-Based APIs
+## Reusing a Buffer
-Use `serializeTo` and `deserializeFrom` when you want explicit `Buffer` reuse.
+If you want to avoid allocating a new `Uint8List` on every call, use
`serializeTo` and `deserializeFrom` with an explicit `Buffer`:
```dart
final fory = Fory();
@@ -119,11 +121,11 @@ fory.serializeTo(Int32(42), buffer);
final value = fory.deserializeFrom<Int32>(buffer);
```
-`serializeTo` clears the target buffer before writing. `deserializeFrom`
consumes bytes from the buffer's current reader position.
+This is an optimization. For most applications the default
`serialize`/`deserialize` pair is fine.
-## Generated Registration Before Use
+## Register Your Types Before Serializing
-Generated and manual user-defined types must be registered before use.
+Before you can serialize a custom class or enum, register it with `Fory`. The
generated code makes this easy:
```dart
PersonFory.register(
@@ -133,7 +135,7 @@ PersonFory.register(
);
```
-See [Type Registration](type-registration.md) and [Code
Generation](code-generation.md).
+If you skip registration, you will get a `Type ... is not registered` error at
runtime. See [Type Registration](type-registration.md) and [Code
Generation](code-generation.md).
## Related Topics
diff --git a/docs/guide/dart/code-generation.md
b/docs/guide/dart/code-generation.md
index 5a65135a97..495a6dd898 100644
--- a/docs/guide/dart/code-generation.md
+++ b/docs/guide/dart/code-generation.md
@@ -19,11 +19,11 @@ license: |
limitations under the License.
---
-Fory Dart uses source generation for structs and enums defined in your Dart
libraries.
+Fory generates fast serializer code for your Dart classes at build time. You
annotate your models, run `build_runner`, and Fory takes care of the rest.
-## Annotate Struct Types
+## Step 1 β Annotate Your Models
-Mark classes with `@ForyStruct()` and include the generated part file.
+Add `@ForyStruct()` to each class you want to serialize. Include the generated
part directive at the top of the file.
```dart
import 'package:fory/fory.dart';
@@ -48,21 +48,21 @@ class User {
}
```
-Enums in the same library also participate in generated registration.
+Enums defined in the same file are automatically included in the generated
registration.
-## Run the Generator
+## Step 2 β Run the Generator
-From `dart/packages/fory` or the package that depends on the generator:
+From the directory that contains your `pubspec.yaml`:
```bash
dart run build_runner build --delete-conflicting-outputs
```
-The builder emits a `.fory.dart` part file next to the source library.
+This emits a `.fory.dart` file next to your source file. Re-run this command
any time you add or rename annotated types.
-## Register Through the Generated Namespace
+## Step 3 β Register and Use
-Generated code exposes a library-level namespace that knows how to install
generated metadata into `Fory`.
+The generator creates a namespace (named after your file) with a `register`
function. Call it before serializing:
```dart
final fory = Fory();
@@ -70,7 +70,7 @@ ModelsFory.register(fory, Address, id: 1);
ModelsFory.register(fory, User, id: 2);
```
-Or use named registration:
+Or use a stable name instead of a numeric ID (useful for cross-language
scenarios):
```dart
ModelsFory.register(
@@ -81,16 +81,17 @@ ModelsFory.register(
);
```
-Exactly one registration mode is required:
+See [Type Registration](type-registration.md) for guidance on choosing between
IDs and names.
-- `id: ...`
-- `namespace: ...` plus `typeName: ...`
+## Schema Evolution: `evolving`
-## Choosing `evolving`
+`@ForyStruct()` defaults to `evolving: true`, which is the right choice for
most applications.
-`@ForyStruct()` defaults to `evolving: true`.
+- `evolving: true` β Fory stores enough metadata so that if you add or remove
fields later, old and new code can still exchange messages. Enable this
whenever different versions of your app or service may be running at the same
time.
+- `evolving: false` β No extra metadata; marginally smaller payloads. Safe
only when both writer and reader are always updated together.
```dart
+// evolving: true is the default, you can omit it
@ForyStruct(evolving: true)
class Event {
Event();
@@ -99,19 +100,11 @@ class Event {
}
```
-Use `evolving: true` when you want compatible-mode field evolution. Use
`evolving: false` for a fixed schema when you want schema-consistent behavior.
+When using evolving structs, also assign stable field IDs with `@ForyField(id:
...)` before you ship your first payload β those IDs are how Fory matches
fields after a schema change.
-## Generated Registration Design
+## When Not to Use Code Generation
-Generated registration keeps serializer metadata private to the defining
library while exposing a public wrapper API. Applications should call the
generated wrapper instead of private helper functions.
-
-## When to Use Customized Serializers Instead
-
-Use [Custom Serializers](custom-serializers.md) when:
-
-- the type is external and cannot be annotated
-- you need custom payload layout
-- you are implementing a union or extension type manually
+If you cannot annotate a type (e.g., it comes from a package you do not own),
write a [Custom Serializer](custom-serializers.md) instead.
## Related Topics
diff --git a/docs/guide/dart/configuration.md b/docs/guide/dart/configuration.md
index a213cca77d..2cd0a94c46 100644
--- a/docs/guide/dart/configuration.md
+++ b/docs/guide/dart/configuration.md
@@ -19,89 +19,100 @@ license: |
limitations under the License.
---
-This page covers `Config` options and default runtime values for Apache Foryβ’
Dart.
+This page explains the `Fory` constructor options.
-## Create a Runtime
+## Creating a `Fory` Instance
+
+Pass options directly to the constructor:
```dart
import 'package:fory/fory.dart';
+// defaults β good for most single-service scenarios
+final fory = Fory();
+
+// cross-language service with schema evolution
final fory = Fory(
compatible: true,
maxDepth: 512,
- maxCollectionSize: 1 << 18,
- maxBinarySize: 16 * 1024 * 1024,
);
```
-The same `Fory` instance should be reused across many operations. `Fory`
resets operation-local read and write state for each root call.
+Create one instance per application and reuse it; there is no benefit to
creating a new `Fory` per request.
## Options
### `compatible`
-Enables compatible struct encoding and decoding.
+Set to `true` when your service needs to handle payloads from code that may
have a different version of the same model β for example, when you deploy
services independently and cannot guarantee that both sides update at the same
time.
```dart
final fory = Fory(compatible: true);
```
-In compatible mode, generated evolving structs exchange shared `TypeDef`
metadata rather than relying on the schema-consistent struct hash checks used
by fixed-schema mode.
+When `compatible: true`:
+
+- Adding or removing fields on one side does not break the other.
+- Peers must still use the same `namespace` + `typeName` (or numeric `id`) to
identify types.
+
+When `compatible: false` (default):
+
+- Both sides must have exactly the same schema. This is slightly faster and is
fine when you deploy Dart-only services or always update all sides together.
### `checkStructVersion`
-Controls struct schema-version validation in schema-consistent mode.
+Relevant only when `compatible: false`. When `true`, Fory validates that the
schema version in the payload matches the one the receiver knows about,
catching accidental schema mismatches at runtime.
```dart
final fory = Fory(
compatible: false,
- checkStructVersion: true,
+ checkStructVersion: true, // default
);
```
-This option is forced to `false` whenever `compatible` is `true`.
+This option has no effect when `compatible: true`.
### `maxDepth`
-Maximum nesting depth accepted during one serialization or deserialization
operation.
+Limits how deeply nested an object graph can be. Increase this if you have
legitimately deep trees; lower it to reject unexpectedly deep payloads fast.
```dart
-const config = Config(maxDepth: 128);
+final fory = Fory(maxDepth: 128);
```
-Use this to bound recursive payloads and fail fast on malformed or
unexpectedly deep graphs.
-
### `maxCollectionSize`
-Maximum number of entries accepted for a single collection or map payload.
+Maximum number of elements accepted in any single list, set, or map field.
Prevents runaway memory allocation from malformed messages.
```dart
-const config = Config(maxCollectionSize: 100000);
+final fory = Fory(maxCollectionSize: 100000);
```
### `maxBinarySize`
-Maximum number of bytes accepted for a single binary payload.
+Maximum number of bytes accepted for any single binary blob field.
```dart
-const config = Config(maxBinarySize: 8 * 1024 * 1024);
+final fory = Fory(maxBinarySize: 8 * 1024 * 1024);
```
## Defaults
-`Config()` defaults to:
-
-- `compatible: false`
-- `checkStructVersion: true`
-- `maxDepth: 256`
-- `maxCollectionSize: 1 << 20`
-- `maxBinarySize: 64 * 1024 * 1024`
+| Option | Default |
+| -------------------- | --------- |
+| `compatible` | `false` |
+| `checkStructVersion` | `true` |
+| `maxDepth` | 256 |
+| `maxCollectionSize` | 1 048 576 |
+| `maxBinarySize` | 64 MiB |
## Cross-Language Notes
-- The Dart runtime always reads and writes xlang frames.
-- Match `compatible` mode across communicating services when you depend on
schema evolution.
-- Registration choices still matter: use the same numeric IDs or the same
`namespace + typeName` mapping across all runtimes.
+When Fory is used to communicate between services written in different
languages:
+
+- Set `compatible: true` on **all** sides if any side needs schema evolution.
+- Use the same numeric IDs or `namespace + typeName` pairs on every side.
+- Match the `compatible` setting on both the writing and reading side β
mismatching modes will fail.
## Related Topics
diff --git a/docs/guide/dart/cross-language.md
b/docs/guide/dart/cross-language.md
index 2c700865b2..fb83116929 100644
--- a/docs/guide/dart/cross-language.md
+++ b/docs/guide/dart/cross-language.md
@@ -19,31 +19,37 @@ license: |
limitations under the License.
---
-Apache Foryβ’ Dart supports cross-language serialization with other Fory
runtimes.
+Apache Foryβ’ Dart serializes to the same binary format as the Java, Go, C#,
Python, Rust, and Swift Fory runtimes. You can write a message in Dart and read
it in Java β or any other direction β without any conversion layer.
-## Cross-Language Runtime
+## Setup
-The Dart runtime only supports xlang payloads, so you do not enable a separate
cross-language mode.
+Create a `Fory` instance as normal. There is no separate "cross-language mode"
to enable in Dart:
```dart
-final fory = Fory();
+final fory = Fory(); // or Fory(compatible: true) for schema evolution
```
-Configure only the runtime behavior that still varies, such as compatible mode
and safety limits.
+The key requirement is that both sides register the same type using the same
identity.
+
+## Registration Identity
-## Use Stable Registration Identity
+The most important rule: **use the same type identity on every side**. You
have two options:
-Choose one registration strategy and keep it stable across all peers.
+### Numeric ID
-### Numeric ID example
+Simpler for small, tightly-coordinated teams:
```dart
+// Dart
ModelsFory.register(fory, Person, id: 100);
```
-### Name-based example
+### Namespace + Type Name
+
+Better when multiple teams define types independently:
```dart
+// Dart
ModelsFory.register(
fory,
Person,
@@ -52,6 +58,8 @@ ModelsFory.register(
);
```
+Do not mix the two strategies for the same type across runtimes.
+
## Dart to Java Example
### Dart
@@ -146,15 +154,14 @@ _ = f.Deserialize(bytesFromDart, &person)
## Field Matching Rules
-The xlang spec is the source of truth, but for application authors the
practical rules are:
-
-1. Register the same logical type identity on every side.
-2. Keep field meaning aligned across languages.
-3. For evolving schemas, keep field IDs stable.
-4. Use compatible type mappings for numeric widths, timestamps, collections,
and nullability.
-5. Validate real payload round trips.
+Fory matches fields by name or by stable field ID. For robust cross-language
interop:
-Dart model fields often use lowerCamelCase. Go fields are exported PascalCase.
C# commonly uses PascalCase properties. What matters is that the peer runtimes
agree on the logical field mapping and wire schema. Stable field IDs are the
safest option when models evolve independently.
+1. Use the same type identity on every side (same numeric ID or same
`namespace + typeName`).
+2. Assign stable `@ForyField(id: ...)` values to all fields before shipping
the first payload.
+3. Keep field names consistent or rely on IDs, since Dart typically uses
`lowerCamelCase` while Go uses `PascalCase` for exported fields and C# often
uses `PascalCase` properties.
+4. Use compatible numeric types: `Int32` in Dart for Java `int`, Go `int32`,
and C# `int`; `double` in Dart for 64-bit floats; `Float32` for 32-bit.
+5. Use `Timestamp` and `LocalDate` for date/time fields rather than raw
`DateTime`.
+6. Validate real round trips across all languages before shipping.
## Type Mapping Notes for Dart
@@ -167,14 +174,13 @@ Because Dart `int` is not itself a promise about the
exact xlang wire width, pre
See [Supported Types](supported-types.md) and [xlang type
mapping](../../specification/xlang_type_mapping.md).
-## Validation Advice
+## Validation
-Before relying on an interop contract, test the same payload through every
runtime you support.
+Before relying on a cross-language contract in production, test a payload
end-to-end through every runtime you support.
-At minimum for Dart runtime work:
+Run the Dart side:
```bash
-cd dart
dart run build_runner build --delete-conflicting-outputs
dart analyze
dart test
diff --git a/docs/guide/dart/custom-serializers.md
b/docs/guide/dart/custom-serializers.md
index 91ddc69455..280eb1ed8f 100644
--- a/docs/guide/dart/custom-serializers.md
+++ b/docs/guide/dart/custom-serializers.md
@@ -19,19 +19,18 @@ license: |
limitations under the License.
---
-Use customized serializers when generated struct support is not the right fit.
+A custom serializer lets you control exactly how a type is encoded and
decoded. You only need one when:
-## When Customized Serializers Make Sense
+- the type comes from a package you cannot modify and cannot be annotated with
`@ForyStruct()`
+- you need a completely custom binary layout
+- you are implementing a union/discriminated type
-Typical cases include:
-
-- external types you cannot annotate
-- custom payload layouts
-- customized extension types
-- unions built on the `UnionSerializer<T>` base class
+For your own models, `@ForyStruct()` with code generation is almost always the
better choice.
## Implement `Serializer<T>`
+Subclass `Serializer<T>` and implement `write` and `read`. Use
`context.buffer` to read and write raw bytes:
+
```dart
import 'package:fory/fory.dart';
@@ -60,7 +59,7 @@ final class PersonSerializer extends Serializer<Person> {
}
```
-Register it before use:
+Register the serializer before you use it:
```dart
final fory = Fory();
@@ -72,11 +71,9 @@ fory.registerSerializer(
);
```
-## Use `WriteContext` and `ReadContext`
-
-Manual serializers should do nested xlang work through the context rather than
calling root APIs recursively.
+## Writing Nested Objects
-### Write nested values with reference handling
+When your serializer has a field that is itself a Fory-managed type, use
`context.writeRef` and `context.readRef` rather than calling `fory.serialize`
recursively. This keeps reference tracking correct and avoids writing a full
root frame inside a nested payload.
```dart
@override
@@ -90,27 +87,15 @@ Wrapper read(ReadContext context) {
}
```
-### Write values without seeding references
-
-`writeNonRef` writes a non-reference payload and does not seed later
back-references.
+If you do not need reference identity tracking for a nested value (i.e., you
know the value will never appear more than once in a graph), use `writeNonRef`:
```dart
context.writeNonRef(value.child);
```
-### Fine-grained ref/value control
-
-Use `writeRefValueFlag` when your serializer needs explicit control over
whether payload bytes follow.
-
-```dart
-if (context.writeRefValueFlag(value.payload)) {
- context.writeNonRef(value.payload);
-}
-```
-
## Unions
-Manual union serializers should extend `UnionSerializer<T>`.
+For a discriminated/tagged union, extend `UnionSerializer<T>` instead of
`Serializer<T>`. Write a discriminant value first, then the active variant;
read the discriminant and dispatch accordingly.
```dart
final class ShapeSerializer extends UnionSerializer<Shape> {
@@ -118,36 +103,34 @@ final class ShapeSerializer extends
UnionSerializer<Shape> {
@override
void write(WriteContext context, Shape value) {
- // write active alternative
+ // write active variant
}
@override
Shape read(ReadContext context) {
- // read active alternative
+ // read discriminant, return correct variant
throw UnimplementedError();
}
}
```
-The xlang spec defines `UNION`, `TYPED_UNION`, `NAMED_UNION`, and `NONE` wire
types. Use registrations that match the peers you interoperate with.
-
-## Early Reference Binding During Reads
+## Circular References in Custom Serializers
-If your serializer allocates an object before all nested fields are read, bind
it early so back-references can resolve to that instance.
+If your serializer can encounter circular object graphs, bind the object to
the reference tracker **before** reading its nested fields:
```dart
final value = Node.empty();
-context.reference(value);
-value.next = context.readRef() as Node?;
+context.reference(value); // register the object first
+value.next = context.readRef() as Node?; // now nested reads can refer back
to it
return value;
```
-## Best Practices
+Skipping this step causes back-references to that object to resolve to `null`.
+
+## Tips
-- Keep payload code focused on the payload only.
-- Let `Fory` own the root frame and top-level reset lifecycle.
-- Prefer direct buffer access for repeated primitive IO inside hot serializers.
-- Register serializers consistently across all peers.
+- Use `context.buffer` for direct byte reads/writes in hot paths.
+- Register the serializer with the same identity (`id` or `namespace +
typeName`) on every side.
## Related Topics
diff --git a/docs/guide/dart/field-configuration.md
b/docs/guide/dart/field-configuration.md
index 8a9402de01..4f16f2b7ad 100644
--- a/docs/guide/dart/field-configuration.md
+++ b/docs/guide/dart/field-configuration.md
@@ -19,23 +19,23 @@ license: |
limitations under the License.
---
-Use `@ForyField(...)` to override generated serializer behavior for individual
fields.
+Add `@ForyField(...)` to a field inside a `@ForyStruct()` class to change how
that field is serialized.
-## Options Overview
+## Quick Reference
```dart
@ForyField(
- skip: false,
- id: 10,
- nullable: true,
- ref: true,
- dynamic: false,
+ skip: false, // exclude the field from serialization
+ id: 10, // stable field ID for schema evolution
+ nullable: true, // override nullability detection
+ ref: true, // enable reference tracking for this field
+ dynamic: false, // control whether the runtime type is written
)
```
## `skip`
-Exclude a field from generated serialization.
+Exclude a field from serialization entirely. Useful for cached, computed, or
UI-only values that should not land in a persisted or transmitted message.
```dart
@ForyField(skip: true)
@@ -44,90 +44,81 @@ String cachedDisplayName = '';
## `id`
-Provides a stable field identifier for compatible structs.
+Assigns a stable identity to the field so that Fory can match it by ID after a
schema change (a field rename or reorder). **If you plan to add, remove, or
rename fields in the future, assign IDs to all fields now** β before you ship
the first payload.
```dart
@ForyField(id: 1)
String name = '';
```
-In compatible mode, stable field IDs matter more than declaration order. Keep
IDs fixed once payloads are shared.
+Once a payload is shared across services, never reuse an `id` for a different
field.
## `nullable`
-Overrides inferred nullability.
+Explicitly marks a field as nullable or non-nullable, overriding what Fory
infers from the Dart type. Use this when the Dart type is non-nullable but you
want Fory to accept `null` on the wire (e.g., reading messages from an older
producer that can omit the field).
```dart
@ForyField(nullable: true)
String nickname = '';
```
-`null` means "use the Dart type as written." In cross-language scenarios, make
sure your nullability contract also matches peer runtimes.
+In cross-language scenarios, make sure the nullability contract also matches
what peer runtimes expect.
## `ref`
-Enables reference tracking for that field.
+Enables reference tracking for a specific field. Use this when multiple
objects in the graph can point to the same instance, or when the field type can
be circular. Without `ref: true`, Fory serializes the same object value twice
if it appears in two fields.
```dart
@ForyField(ref: true)
List<Object?> sharedNodes = <Object?>[];
```
-Basic scalar values never track references even if `ref` is set to `true`.
+Note: scalar types like `int`, `double`, and `bool` never benefit from
reference tracking even if `ref: true` is set.
## `dynamic`
-Controls whether generated code writes runtime type metadata for the field.
+Controls whether Fory writes the concrete runtime type of the field value into
the payload.
+
+- `null` (default) β Fory decides automatically based on the declared type.
+- `false` β always use the declared field type; more compact but the
deserializer must know the exact type.
+- `true` β always write the actual runtime type; needed when the field is
declared as `Object?` or a base class but can hold different concrete types at
runtime (polymorphism).
```dart
@ForyField(dynamic: true)
-Object? payload;
+Object? payload; // can hold any registered type at runtime
```
-- `null`: auto
-- `false`: use the declared field type
-- `true`: write runtime type information
-
-This is the key knob for polymorphic fields and heterogeneous object payloads.
+## Numeric Field Annotations
-## Numeric Encoding Overrides
-
-Use numeric annotations to control the xlang wire type used for integer fields.
+Dart `int` is a 64-bit value at runtime. When exchanging messages with Java,
Go, or C#, the receiving side may expect a narrower integer. Use a numeric
annotation to pin the exact wire format:
```dart
@ForyStruct()
class Sample {
Sample();
- @Int32Type(compress: false)
+ @Int32Type(compress: false) // always writes 4 bytes
int fixedWidthInt = 0;
- @Int64Type(encoding: LongEncoding.tagged)
+ @Int64Type(encoding: LongEncoding.tagged) // variable-length encoding
int compactLong = 0;
- @Uint32Type(compress: true)
+ @Uint32Type(compress: true) // variable-length unsigned
int smallUnsigned = 0;
}
```
-Available numeric annotations include:
-
-- `@Int32Type(...)`
-- `@Int64Type(...)`
-- `@Uint8Type()`
-- `@Uint16Type()`
-- `@Uint32Type(...)`
-- `@Uint64Type(...)`
+Available annotations: `@Int32Type`, `@Int64Type`, `@Uint8Type`,
`@Uint16Type`, `@Uint32Type`, `@Uint64Type`.
-These should be chosen to match the intended xlang wire type and peer-language
expectations.
+Alternatively, use the explicit wrapper types (`Int32`, `UInt32`, etc.)
described in [Supported Types](supported-types.md).
-## Field Alignment Across Languages
+## Aligning Fields Across Languages
-Cross-language decoding depends on matching field names or stable field IDs.
When models differ across languages:
+When the same model is defined in multiple languages:
-- keep equivalent logical fields aligned
-- prefer stable `id` values for evolving structs
-- use `dynamic: true` only when the field is genuinely polymorphic
+- Assign stable `id` values to every field that might change over time.
+- Use `dynamic: true` for fields that are genuinely polymorphic.
+- Keep the logical meaning of each field consistent across languages β Fory
matches fields by name or ID, but cannot reconcile semantic differences.
## Related Topics
diff --git a/docs/guide/dart/index.md b/docs/guide/dart/index.md
index e52ef5f187..b5517810e9 100644
--- a/docs/guide/dart/index.md
+++ b/docs/guide/dart/index.md
@@ -19,36 +19,30 @@ license: |
limitations under the License.
---
-Apache Foryβ’ Dart is a cross-language serialization runtime for Dart. It reads
and writes the xlang wire format defined by the [xlang serialization
specification](../../specification/xlang_serialization_spec.md) and is designed
around generated serializers with an advanced customized serializer escape
hatch.
+Apache Foryβ’ Dart lets you serialize Dart objects to bytes and deserialize
them back β including across services written in Java, Go, C#, Python, and
other Fory-supported languages.
## Why Fory Dart?
-- Cross-language compatibility with other Fory xlang runtimes
-- Generated serializers for annotated Dart models
-- Compatible mode for schema evolution
-- Optional reference tracking for shared and circular object graphs
-- Manual serializer support for external types, custom wire behavior, and
unions
-- A small public API centered on `Fory`, `Config`, annotations, and xlang
value wrappers
-
-## Runtime Model
-
-The Dart runtime only supports xlang payloads. There is no separate
native-mode builder. Root operations happen through `Fory`, while nested
payload work stays in explicit `WriteContext` and `ReadContext` instances. This
mirrors the ownership model described in the [xlang implementation
guide](../../specification/xlang_implementation_guide.md).
+- **Cross-language**: serialize in Dart, deserialize in Java, Go, C#, and more
without writing any glue code
+- **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
+- **Escape hatch**: write a manual serializer for any type that cannot be
annotated
## Quick Start
### Requirements
- Dart SDK 3.6 or later
-- `build_runner` for generated serializers
+- `build_runner` (generates the serializer code)
### Install
-Add the package from your Dart workspace or package source. In this
repository, the runtime lives under `dart/packages/fory`.
+Add the dependency to your `pubspec.yaml`:
```yaml
dependencies:
- fory:
- path: packages/fory
+ fory: ^0.17.0
dev_dependencies:
build_runner: ^2.4.0
@@ -56,6 +50,8 @@ dev_dependencies:
### Basic Example
+Define your model, run the generator once, then serialize:
+
```dart
import 'package:fory/fory.dart';
@@ -106,19 +102,21 @@ void main() {
Generate the companion file before running the program:
```bash
-cd dart/packages/fory
dart run build_runner build --delete-conflicting-outputs
```
-## Core API
+`PersonFory` is generated by `build_runner`. The `namespace` and `typeName`
values are how peers in other languages identify the same type β keep them
stable once your service is in production.
+
+## API Overview
-- `Fory({bool compatible = false, bool checkStructVersion = true, int maxDepth
= Config.defaultMaxDepth, int maxCollectionSize =
Config.defaultMaxCollectionSize, int maxBinarySize =
Config.defaultMaxBinarySize})`
-- `serialize(Object? value, {bool trackRef = false})`
-- `deserialize<T>(Uint8List bytes)`
-- `register(Type type, {int? id, String? namespace, String? typeName})`
-- `registerSerializer(Type type, Serializer serializer, ...)`
-- `@ForyStruct()` and `@ForyField(...)` for generated serializers
-- xlang value wrappers such as `Int8`, `Int16`, `Int32`, `UInt8`, `UInt16`,
`UInt32`, `Float16`, `Float32`, `LocalDate`, and `Timestamp`
+- `Fory(...)` β create a serializer instance; create once and reuse it
+- `fory.serialize(value)` β returns `Uint8List` bytes
+- `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`
+- Float wrappers: `Float16`, `Float32`
+- Time wrappers: `LocalDate`, `Timestamp`
## Documentation
diff --git a/docs/guide/dart/schema-evolution.md
b/docs/guide/dart/schema-evolution.md
index 778c39bce0..8d33297a91 100644
--- a/docs/guide/dart/schema-evolution.md
+++ b/docs/guide/dart/schema-evolution.md
@@ -19,36 +19,31 @@ license: |
limitations under the License.
---
-Fory Dart supports schema evolution through compatible structs.
+Schema evolution lets different versions of your app exchange messages safely
β a v2 writer can produce a message that a v1 reader can still decode, and vice
versa.
-## Two Struct Modes
+## Two Modes
-### Schema-Consistent Mode
+### Compatible Mode (recommended for evolving services)
-This is the default when `Config.compatible` is `false`.
-
-- Structs use the schema-consistent path.
-- `checkStructVersion` can validate schema-version compatibility.
-- This mode is appropriate when both sides evolve in lockstep or when you want
stricter validation.
-
-### Compatible Mode
-
-Enable compatible mode when you want evolving field metadata on the wire.
+Enable this when services may run different versions at the same time β for
example, during a rolling deployment or when clients are not updated
immediately.
```dart
final fory = Fory(compatible: true);
```
-In compatible mode:
+In compatible mode, Fory includes enough field metadata in each message so
that the reader can skip unknown fields and use defaults for missing ones. Use
stable field IDs (see below) to anchor the schema across changes.
-- evolving structs use compatible struct encoding
-- `checkStructVersion` is disabled
-- field IDs become the stability anchor for changed schemas
-- TypeDef metadata is shared and reused across payloads
+### Schema-Consistent Mode (default)
-## Generated Struct Controls
+Both sides must have the same model. Fory validates that the schemas match and
will reject messages from a different schema version. Use this when all
services are always updated together and you want schema mismatches to be
caught as fast errors.
-`@ForyStruct(evolving: true)` is the default and is the right choice when the
type may evolve over time.
+```dart
+final fory = Fory(); // compatible: false by default
+```
+
+## Setting Up for Evolution
+
+To use compatible mode safely, mark your structs with `@ForyStruct(evolving:
true)` (the default) and assign a stable `@ForyField(id: ...)` to every field
**before you ship your first payload**:
```dart
@ForyStruct(evolving: true)
@@ -63,25 +58,32 @@ class UserProfile {
}
```
-Use explicit stable field IDs when a schema may change.
+If you add field IDs after payloads are already in production, existing stored
messages won't have them and evolution won't work correctly.
+
+## What You Can Safely Change
+
+**Safe changes** (compatible on both sides):
-## Safe Evolution Practices
+- Add a new optional field with a new, unused field ID.
+- Rename a field β as long as the `@ForyField(id: ...)` stays the same.
+- Remove a field β the peer will just ignore the missing value and use the
Dart default.
-Typical compatible changes include:
+**Unsafe changes** (may break existing messages):
-- adding a new optional field with a new field ID
-- keeping existing field IDs stable
-- widening consumer behavior to tolerate missing data
+- Reuse an existing field ID for a different field.
+- Change a field's type to an incompatible type (e.g., `Int32` β `String`).
+- Change the registration identity (`id`, `namespace`, or `typeName`) of a
type after messages are in production.
+- Change a field's logical meaning without changing its ID.
-Changes that require extra care include:
+## Cross-Language Notes
-- reusing an old field ID for different semantics
-- changing a field's logical cross-language meaning
-- changing registration identity for an existing type
+Evolution only works when **all** runtimes that exchange messages agree on:
-## Cross-Language Guidance
+1. The same `compatible` setting.
+2. The same type registration identity (numeric ID or `namespace + typeName`).
+3. The logical meaning of field IDs.
-Schema evolution only works when all runtimes agree on the type registration
identity and the logical meaning of field IDs. Validate rolling upgrades with
real round trips across the languages you support.
+Test rolling-upgrade scenarios with real round trips before deploying.
## Related Topics
diff --git a/docs/guide/dart/supported-types.md
b/docs/guide/dart/supported-types.md
index edf2f29f99..a6acc5f2d7 100644
--- a/docs/guide/dart/supported-types.md
+++ b/docs/guide/dart/supported-types.md
@@ -19,70 +19,61 @@ license: |
limitations under the License.
---
-This page summarizes the main xlang-facing types in Apache Fory Dart.
+This page lists the Dart types you can use in Fory messages, and flags where
you need to be careful for cross-language compatibility.
-## Primitive-Like Dart Values
+## Built-in Primitive Types
-The runtime supports common Dart values directly where they map cleanly to
xlang types:
+The following Dart types serialize directly without any special handling:
-- `bool`
-- `int`
-- `double`
-- `String`
-- `Uint8List` and other supported typed byte containers
-- `List`, `Set`, and `Map`
-- `DateTime`-adjacent values through explicit wrappers such as `Timestamp` and
`LocalDate`
-
-Because Dart `int` and `double` are broader runtime concepts than many xlang
scalar types, wrapper types and field annotations are used when the exact wire
type matters.
+| Dart type | Cross-language notes
|
+| -------------------- |
-----------------------------------------------------------------------------------------------------------
|
+| `bool` | Direct mapping
|
+| `int` | Serialized as 64-bit by default. Use wrappers or
`@Int32Type` etc. when the peer expects a narrower integer |
+| `double` | Maps to 64-bit float. Use `Float32` wrapper when the
peer expects 32-bit |
+| `String` | Direct mapping
|
+| `Uint8List` | Binary blob
|
+| `List`, `Set`, `Map` | Supported; element types must also be supported
|
+| `DateTime` | Use `Timestamp` or `LocalDate` wrappers for explicit
semantics |
## Integer Wrappers
-Use wrapper classes when you need an exact xlang fixed-width integer type.
+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.
+
+Use an integer wrapper class to pin the exact wire width:
```dart
-final Int8 tiny = Int8(-1);
-final Int16 shortValue = Int16(7);
-final Int32 age = Int32(36);
-final UInt8 flags = UInt8(255);
-final UInt16 port = UInt16(65535);
-final UInt32 count = UInt32(4000000000);
+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
```
-Available wrappers:
-
-- `Int8`
-- `Int16`
-- `Int32`
-- `UInt8`
-- `UInt16`
-- `UInt32`
-
-Each wrapper normalizes the stored value to the target bit width.
+Each wrapper clamps the stored value to the target bit width.
## Floating-Point Wrappers
-Use explicit wrapper types when you need a precise non-`float64` wire type.
-
-- `Float16`
-- `Float32`
+Dart `double` maps to 64-bit float. If the peer uses a 32-bit float, use a
wrapper:
-Dart `double` maps naturally to the xlang `float64` family.
+- `Float32` β 32-bit float (matches Java `float`, C# `float`, Go `float32`)
+- `Float16` β half-precision, for specialized numeric payloads
## Time and Date Types
-The runtime exposes explicit xlang-friendly temporal wrappers:
+Avoid sending raw `DateTime` across languages β time zone handling and epoch
differences vary. Use the explicit wrappers instead:
-- `Timestamp` for seconds-plus-nanoseconds UTC instants
-- `LocalDate` for timezone-free calendar dates
+- `Timestamp` β a UTC instant with nanosecond precision (seconds + nanoseconds)
+- `LocalDate` β a calendar date without time or time zone
```dart
-final timestamp = Timestamp.fromDateTime(DateTime.now().toUtc());
+final now = Timestamp.fromDateTime(DateTime.now().toUtc());
final birthday = LocalDate(1990, 12, 1);
```
## Structs and Enums
-User-defined structs and enums are supported through generated registration or
customized serializers.
+Annotate classes with `@ForyStruct()` and run `build_runner` to make them
serializable. Enums in the same file are included automatically.
```dart
@ForyStruct()
@@ -90,30 +81,19 @@ class User {
User();
String name = '';
- Int32 age = Int32(0);
+ Int32 age = Int32(0); // use Int32 when peers expect a 32-bit integer
}
```
-## Collections
-
-Fory Dart supports the core xlang collection shapes:
-
-- `List<T>`
-- `Set<T>`
-- `Map<K, V>`
-
-Cross-language compatibility still depends on element and key types having
compatible peer-language mappings. Avoid mutable collection keys.
+See [Code Generation](code-generation.md).
-## Typed Arrays and Binary Values
-
-The xlang spec defines dedicated wire types for binary payloads and numeric
arrays. In Dart, use the typed values exported by the runtime and test the
exact round trip you need with your peer languages.
+## Collections
-## Exact Mapping Rules
+Fory supports `List<T>`, `Set<T>`, and `Map<K, V>`. Element and key types must
also be serializable types. Avoid using mutable objects as map keys.
-For the complete cross-language mapping, see:
+## Compatibility Tip
-- [Xlang type mapping](../../specification/xlang_type_mapping.md)
-- [Cross-Language](cross-language.md)
+When in doubt about whether a Dart type will match what the peer expects, use
the explicit wrapper types. Guessing the wrong numeric width is one of the most
common cross-language bugs.
## Related Topics
diff --git a/docs/guide/dart/troubleshooting.md
b/docs/guide/dart/troubleshooting.md
index 1d9fd2fc2b..9266d05fb8 100644
--- a/docs/guide/dart/troubleshooting.md
+++ b/docs/guide/dart/troubleshooting.md
@@ -23,21 +23,19 @@ This page covers common Dart runtime issues and fixes.
## `Only xlang payloads are supported by the Dart runtime.`
-The Dart runtime only deserializes xlang frames. Make sure the peer runtime is
writing xlang payloads rather than a language-native format.
+The writer is sending a native-mode (non-xlang) payload. Make sure every
service uses the xlang-compatible path:
-- Java must use `withLanguage(Language.XLANG)`.
-- Go must enable `WithXlang(true)`.
-- Other peers must use their xlang-compatible path.
+- **Java**: add `.withLanguage(Language.XLANG)` to the Fory builder.
+- **Go**: use `WithXlang(true)` in the Fory options.
+- **Other runtimes**: check their respective guides for enabling
cross-language mode.
## `Type ... is not registered.`
-Generated and manual user-defined types must be registered before reading or
writing them.
+Fory does not know how to serialize or deserialize this type. Fix it by:
-Checklist:
-
-1. Run code generation.
-2. Register the type through the generated namespace or `registerSerializer`.
-3. Register all nested user-defined types, not only the root type.
+1. Running code generation if you haven't: `dart run build_runner build
--delete-conflicting-outputs`
+2. Calling the generated `register` function (or `registerSerializer`) for the
type **before** calling `serialize` or `deserialize`.
+3. Registering **all** types that appear in a message, not just the root type.
For example, if `Order` contains an `Address`, register both.
## Generated part file is missing or stale
@@ -52,45 +50,45 @@ If you moved files or renamed types, rebuild before
re-running analysis or tests
## `Deserialized value has type ..., expected ...`
-`deserialize<T>` validates the runtime result after decoding. This error
usually means:
+The payload describes a different type than `T` in `deserialize<T>`. Common
causes:
-- the payload describes a different root type than the requested `T`
-- registration points to a different logical type than expected
-- a dynamic payload should be decoded as `Object?` or a container type first
+- You registered the type on the writing side with a different ID or name than
on the reading side.
+- The payload was produced by a different code path that serializes a
different root object.
+- You are trying to deserialize a heterogeneous container β decode it as
`Object?` or `List<Object?>` first, then cast.
-## Unexpected reference identity behavior
+## Objects aren't the same instance after deserialization
-Check whether reference tracking is enabled in the correct place:
+Fory does not track object identity by default, so two fields pointing to the
same object will produce two independent copies after a round trip.
-- root graph or root container: `trackRef: true`
-- generated field graph: `@ForyField(ref: true)`
-- customized serializer: use `writeRef`, `readRef`, and
`context.reference(...)` correctly
+To preserve identity:
-`writeNonRef` intentionally does not seed later back-references.
+- For fields inside a `@ForyStruct`, add `@ForyField(ref: true)` to those
fields.
+- For a top-level collection, pass `trackRef: true` to `fory.serialize(...)`.
+- In a custom serializer, use `context.writeRef` / `context.readRef` and call
`context.reference(obj)` before reading nested fields.
-## Cross-language field mismatch
+## Cross-language field mismatch (missing data or wrong values)
-Symptoms include missing data, default values, or type errors on peers.
+Symptoms: fields come back as default values or wrong types after a round trip
to another language.
-Check:
+Checklist:
-- same registration identity on both sides
-- stable field IDs for evolving structs
-- matching logical field names and meanings
-- compatible numeric widths and temporal types
+1. Same registration identity on both sides (same numeric ID **or** same
`namespace + typeName`).
+2. Stable `@ForyField(id: ...)` assigned before the first payload was produced.
+3. Compatible numeric widths β use `Int32` in Dart when the peer field is
`int` (Java), `int32` (Go), or `int` (C#).
+4. `Timestamp` / `LocalDate` instead of raw `DateTime` for date/time fields.
+5. `compatible: true` on **both** sides if using schema evolution.
-## Validation Commands
+## Running Tests Locally
-For the main Dart workspace:
+Main Dart package:
```bash
-cd dart
dart run build_runner build --delete-conflicting-outputs
dart analyze
dart test
```
-For the integration-style test package:
+Integration test package:
```bash
cd dart/packages/fory-test
diff --git a/docs/guide/dart/type-registration.md
b/docs/guide/dart/type-registration.md
index 5d74560251..a5f93e1c0e 100644
--- a/docs/guide/dart/type-registration.md
+++ b/docs/guide/dart/type-registration.md
@@ -19,25 +19,30 @@ license: |
limitations under the License.
---
-This page covers how to register user-defined types in Apache Foryβ’ Dart.
+Fory needs to know which class corresponds to which type in a serialized
message. You do this by registering each class before you serialize or
deserialize it.
-## Registration Modes
+## Choosing a Registration Strategy
-Fory Dart supports the same two registration styles used by other xlang
runtimes.
+Fory offers two strategies. Pick one and use it consistently across every
language that reads or writes the type.
-### Register by Numeric ID
+### Strategy 1: Numeric ID
-Numeric IDs are compact on the wire and fast to resolve.
+Compact and fast. Good when a small team can coordinate IDs across services.
```dart
ModelsFory.register(fory, User, id: 100);
```
-Use the same numeric ID for the same logical type in every language.
+The same number must be used in every other language:
-### Register by Namespace and Type Name
+```java
+// Java side
+fory.register(User.class, 100);
+```
+
+### Strategy 2: Namespace + Type Name
-Name-based registration avoids global numeric ID coordination and is often
easier across multiple teams.
+More self-describing. Good when multiple teams or packages define types
independently and numeric ID coordination is impractical.
```dart
ModelsFory.register(
@@ -48,21 +53,21 @@ ModelsFory.register(
);
```
-The same `namespace + typeName` pair must be used by every runtime that reads
or writes the type.
+Every runtime that reads or writes this type must use the same `namespace` and
`typeName`.
-## Generated Type Registration
+> **Do not mix strategies for the same type.** If one side uses a numeric ID
and the other uses a name, deserialization will fail.
-Generated types should normally be registered through the generated namespace
wrapper:
+## Registering Generated Types
+
+Call the generated `register` function from the `.fory.dart` file. It installs
all the serializer metadata for you:
```dart
UserModelsFory.register(fory, User, id: 100);
```
-Internally, the wrapper installs generated metadata and then calls
`Fory.register(...)`.
-
-## Register a Manual Serializer
+## Registering a Custom Serializer
-Customized serializers register through `registerSerializer`:
+For types that you cannot annotate with `@ForyStruct()`, pass a serializer
instance directly:
```dart
fory.registerSerializer(
@@ -73,21 +78,18 @@ fory.registerSerializer(
);
```
-## Cross-Language Requirements
-
-Registration must match across runtimes:
+See [Custom Serializers](custom-serializers.md) for how to implement a
serializer.
-- same numeric ID, or
-- same namespace and type name
+## Rules to Follow
-The wire format distinguishes built-in xlang type IDs from user-registered
type IDs. The user ID is encoded separately as an unsigned varint32, as
described in the [xlang serialization
specification](../../specification/xlang_serialization_spec.md).
+- Register **before** the first `serialize` or `deserialize` call.
+- Register **every** class that can appear in a message, not only the root
type.
+- Keep IDs (or names) **stable** once payloads are persisted or exchanged
across services. Changing them will break deserialization of old messages.
+- Do not mix a numeric ID on one side with a name on the other for the same
type.
-## Common Rules
+## Cross-Language Requirements
-- Register before the first serialize or deserialize call.
-- Register nested user-defined types as well as root types.
-- Keep IDs stable once payloads are persisted or exchanged across services.
-- Do not mix a numeric ID on one side with a name-based registration on the
other side for the same type.
+The same numeric ID or `namespace + typeName` pair must be used in every
runtime that reads or writes the type. See [Cross-Language](cross-language.md)
for examples.
## Related Topics
diff --git a/docs/guide/go/_category_.json b/docs/guide/go/_category_.json
index 796c0a4dd6..e4d176a762 100644
--- a/docs/guide/go/_category_.json
+++ b/docs/guide/go/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Go",
- "position": 4,
+ "position": 5,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/java/_category_.json b/docs/guide/java/_category_.json
index 3cb119d373..3508cf9008 100644
--- a/docs/guide/java/_category_.json
+++ b/docs/guide/java/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Java",
- "position": 0,
+ "position": 1,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/cpp/_category_.json
b/docs/guide/javascript/_category_.json
similarity index 52%
copy from docs/guide/cpp/_category_.json
copy to docs/guide/javascript/_category_.json
index c543ab1388..2434e92ab8 100644
--- a/docs/guide/cpp/_category_.json
+++ b/docs/guide/javascript/_category_.json
@@ -1,6 +1,6 @@
{
- "label": "C++",
- "position": 3,
+ "label": "JavaScript",
+ "position": 6,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/javascript/basic-serialization.md
b/docs/guide/javascript/basic-serialization.md
index 077ba9c541..ced490fd29 100644
--- a/docs/guide/javascript/basic-serialization.md
+++ b/docs/guide/javascript/basic-serialization.md
@@ -19,9 +19,9 @@ license: |
limitations under the License.
---
-This guide covers the core serialization flow in Apache Fory JavaScript.
+This guide covers the core serialization APIs in Apache Fory JavaScript.
-## Create and Reuse a `Fory` Instance
+## Create a `Fory` Instance
```ts
import Fory from "@apache-fory/core";
@@ -29,7 +29,7 @@ import Fory from "@apache-fory/core";
const fory = new Fory();
```
-Create one instance, register your schemas, and reuse it. A `Fory` instance
caches generated serializers and type metadata, so recreating it for every
request adds unnecessary overhead.
+Create one instance, register your schemas, and reuse it. Fory caches the
generated serializers after the first `register` call, so recreating it on
every request wastes that work.
## Define a Schema with `Type.struct`
@@ -109,15 +109,15 @@ fory.deserialize(fory.serialize(new
Date("2021-10-20T09:13:00Z")));
// Date
```
-### Number and `bigint` behavior
+### Number and `bigint`
-JavaScript has both `number` and `bigint`, but xlang distinguishes between
32-bit, 64-bit, floating-point, and tagged integer representations. For any
cross-language or long-lived contract, prefer explicit field types in schemas
instead of depending on dynamic root-type inference.
+JavaScript `number` is a 64-bit float, which cannot exactly represent all
64-bit integers. For cross-language contracts or anywhere exact integer sizes
matter, use explicit field types in your schema:
-- use `Type.int32()` for 32-bit integers
-- use `Type.int64()` for 64-bit integers and pass `bigint`
-- use `Type.float32()` or `Type.float64()` for floating-point values
+- `Type.int32()` β 32-bit integer; use JavaScript `number`
+- `Type.int64()` β 64-bit integer; use JavaScript `bigint`
+- `Type.float32()` / `Type.float64()` β floating-point
-Dynamic root serialization is convenient, but the exact heuristic for whether
a value comes back as `number` or `bigint` should not be treated as a stable
API contract.
+Dynamic root serialization (calling `fory.serialize(someNumber)` without a
schema) will infer a type, but the inferred type is not guaranteed by the API.
Use a schema for any stable contract.
## Arrays, Maps, and Sets
diff --git a/docs/guide/javascript/cross-language.md
b/docs/guide/javascript/cross-language.md
index 06f5c3e2cc..d4fb159aa2 100644
--- a/docs/guide/javascript/cross-language.md
+++ b/docs/guide/javascript/cross-language.md
@@ -19,34 +19,31 @@ license: |
limitations under the License.
---
-Apache Fory JavaScript always uses the xlang wire format. Interoperability
depends on matching schemas and compatible type mappings rather than on
enabling a separate mode switch.
+Fory JavaScript serializes to the same binary format as the Java, Python, Go,
Rust, Swift, and C++ Fory runtimes. You can write a message in JavaScript and
read it in Java β or any other direction β without any conversion layer.
-Current limits to keep in mind:
+Things to keep in mind:
-- JavaScript deserializes xlang payloads only
-- out-of-band mode is not currently supported in the JavaScript runtime
+- The Fory JavaScript runtime reads and writes cross-language payloads only
(it does not support any language-native format).
+- Out-of-band mode is not currently supported.
-## Interop rules
+## Requirements for a Successful Round Trip
-For a payload to round-trip between JavaScript and another runtime:
+For a message to survive a round trip between JavaScript and another runtime:
-1. **Register the same logical type identity** on both sides.
- - same numeric type ID, or
- - same namespace + type name
-2. **Use compatible field types** according to the [type mapping
spec](../../specification/xlang_type_mapping.md).
-3. **Match nullability and reference-tracking expectations** where object
graphs require them.
-4. **Use compatible mode** when independent schema evolution is expected.
+1. **Same type identity** on both sides β same numeric ID, or same `namespace
+ typeName`.
+2. **Compatible field types** β a `Type.int32()` field in JavaScript matches
Java `int`, Go `int32`, C# `int`.
+3. **Same nullability** β if one side marks a field nullable, the other should
too.
+4. **Same `compatible` mode** if using schema evolution.
+5. **Same reference tracking config** if your data has shared or circular
references.
-## Interoperability workflow
+## Step-by-Step: JavaScript to Another Runtime
-When wiring JavaScript to another runtime in production code:
+1. Define the JavaScript schema with the same type name or numeric ID used by
the other runtime.
+2. Register the schema in both runtimes.
+3. Match field types, nullability, and `compatible` settings.
+4. Test a real payload end-to-end before shipping.
-1. define the JavaScript schema with the same type identity used by the other
runtime
-2. register the same type name or type ID in every participating runtime
-3. keep field types, nullability, enum layout, and schema-evolution settings
aligned
-4. validate the end-to-end payload before relying on it in a shared contract
-
-Example JavaScript side:
+JavaScript side:
```ts
import Fory, { Type } from "@apache-fory/core";
@@ -68,40 +65,36 @@ const bytes = serialize({
});
```
-On the receiving side, register the same `example.message` type identity and
compatible field types using that runtime's public API and guide:
+On the other side, register the same `example.message` type (same name or same
numeric ID) using the peer runtime's API:
-- [Go guide](../go/index.md)
-- [Python guide](../python/index.md)
- [Java guide](../java/index.md)
+- [Python guide](../python/index.md)
+- [Go guide](../go/index.md)
- [Rust guide](../rust/index.md)
-## Field naming and ordering
-
-Cross-language matching depends on consistent field identity. When multiple
runtimes model the same struct, use a naming scheme that maps cleanly across
languages.
+## Field Naming
-Practical guidance:
+Fory matches fields by name. When models are defined in multiple languages,
keep field names consistent β or at minimum use a naming scheme that maps
unambiguously across languages (e.g. `snake_case` everywhere).
-- prefer stable snake_case or clearly corresponding names across runtimes
-- avoid accidental renames without updating every participating runtime
-- use compatible mode when fields may be added or removed over time
+When using `compatible: true` for schema evolution, field order differences
are tolerated, but the names themselves must still match.
-## Numeric mapping guidance
+## Numeric Types
-JavaScript needs extra care because `number` is IEEE 754 double precision.
+JavaScript `number` is a 64-bit float, which does not map cleanly to every
integer type in other languages. Use explicit schema types:
-- use `Type.int64()` with `bigint` for true 64-bit integers
-- use `Type.float32()` or `Type.float64()` for floating-point fields
-- avoid assuming that a dynamic JavaScript `number` maps cleanly to every
integer width in another language
+- `Type.int32()` for 32-bit integers (Java `int`, Go `int32`, C# `int`)
+- `Type.int64()` with `bigint` values for 64-bit integers (Java `long`, Go
`int64`)
+- `Type.float32()` or `Type.float64()` for floating-point values
-## Time mapping guidance
+## Date and Time
-- `Type.timestamp()` maps to a point in time and deserializes as `Date`
-- `Type.duration()` should be treated as a duration value, but JavaScript
currently exposes typed duration fields as numeric time values rather than a
dedicated duration class
-- `Type.date()` corresponds to a date-without-timezone type in the
specification and deserializes as `Date`
+- `Type.timestamp()` β a point in time; round-trips as a JavaScript `Date`
+- `Type.date()` β a date without time; deserializes as `Date`
+- `Type.duration()` β exposed as a numeric millisecond value in JavaScript
-## Polymorphism and `Type.any()`
+## Polymorphic Fields
-Use `Type.any()` only when you genuinely need runtime-dispatched values.
+`Type.any()` lets a field hold different types at runtime, but it is harder to
keep in sync across languages. Prefer explicit field schemas whenever possible.
```ts
const wrapperType = Type.struct(
@@ -112,11 +105,9 @@ const wrapperType = Type.struct(
);
```
-This works for polymorphic values, but explicit field schemas are easier to
keep aligned across languages.
-
## Enums
-Enums must also be registered consistently across languages.
+Enum member **order** must match across languages. Fory encodes enums by
ordinal position, not by value.
```ts
const Color = { Red: 1, Green: 2, Blue: 3 };
@@ -124,11 +115,11 @@ const fory = new Fory();
fory.register(Type.enum({ typeId: 210 }, Color));
```
-Use the same type ID or type name in Java, Python, Go, and other runtimes.
+Use the same type ID or type name in every peer runtime.
-## Limits and safety
+## Safety Limits
-Deserialization guardrails such as `maxDepth`, `maxBinarySize`, and
`maxCollectionSize` are local runtime protections. They do not affect the wire
format, but they can reject payloads that exceed local policy.
+The `maxDepth`, `maxBinarySize`, and `maxCollectionSize` options protect the
JavaScript runtime from overly large payloads. They do not change the binary
format β they only control what the local runtime is willing to accept.
## Related Topics
diff --git a/docs/guide/javascript/index.md b/docs/guide/javascript/index.md
index c715ad8cf1..170588baa4 100644
--- a/docs/guide/javascript/index.md
+++ b/docs/guide/javascript/index.md
@@ -19,18 +19,16 @@ license: |
limitations under the License.
---
-Apache Fory JavaScript provides cross-language serialization for JavaScript
and TypeScript applications. It uses the xlang wire format described in the
[xlang serialization
specification](../../specification/xlang_serialization_spec.md), so it is
designed to interoperate with other Fory runtimes such as Java, Python, Go,
Rust, Swift, and C++.
-
-The JavaScript runtime is schema-driven: you describe your data shape with
`Type.*` builders or TypeScript decorators, register that schema with `Fory`,
and then reuse the returned serializer pair for serialization and
deserialization.
+Apache Fory JavaScript lets you serialize JavaScript and TypeScript objects to
bytes and deserialize them back β including across services written in Java,
Python, Go, Rust, Swift, and other Fory-supported languages.
## Why Fory JavaScript?
-- **Cross-language by design**: JavaScript uses the xlang wire format and
interoperates with other Fory runtimes.
-- **Generated hot paths**: serializers are generated at runtime and cached per
`Fory` instance.
-- **Reference-aware object graphs**: shared references and circular structures
are supported when enabled.
-- **Explicit schemas**: field types, nullability, dynamic dispatch, and type
identity are declared up front.
-- **Guardrails for untrusted data**: configurable depth, binary size, and
collection size limits help bound deserialization work.
-- **Modern numeric support**: `bigint`, typed arrays, `Map`, `Set`, `Date`,
`float16`, and `bfloat16` are supported.
+- **Cross-language**: serialize in JavaScript, deserialize in Java, Python,
Go, and more without writing glue code
+- **Fast**: serializer code is generated and cached the first time you
register a schema, not on every call
+- **Reference-aware**: shared references and circular object graphs are
supported when enabled
+- **Explicit schemas**: field types, nullability, and polymorphism are
declared once with `Type.*` builders or TypeScript decorators
+- **Safe defaults**: configurable depth, binary size, and collection size
limits reject unexpectedly large or deep payloads
+- **Modern types**: `bigint`, typed arrays, `Map`, `Set`, `Date`, `float16`,
and `bfloat16` are supported
## Installation
@@ -76,49 +74,29 @@ console.log(user);
// { id: 1n, name: 'Alice', age: 30 }
```
-## Core Model
-
-### `Fory`
-
-A `Fory` instance owns:
-
-- the type registry
-- generated serializers
-- read/write contexts
-- configuration such as reference tracking and guardrails
-
-Reuse the same `Fory` instance across many operations. Registration is per
instance.
-
-### `Type`
-
-`Type` is the schema DSL. It is used to describe:
+## How it works
-- primitive fields such as `Type.int32()` and `Type.string()`
-- collections such as `Type.array(...)`, `Type.map(...)`, and `Type.set(...)`
-- user types such as `Type.struct(...)` and `Type.enum(...)`
-- field-level metadata such as nullability, reference tracking, and dynamic
dispatch
-
-### Registration
-
-`fory.register(...)` compiles and registers a serializer. It returns:
-
-- `serializer`: the generated serializer object
-- `serialize(value)`: serialize using that schema
-- `deserialize(bytes)`: deserialize using that schema
+Fory is schema-driven. You describe the shape of your data once with `Type.*`
builders (or TypeScript decorators), then call `fory.register(schema)`. This
returns a `{ serialize, deserialize }` pair that is fast to call repeatedly.
```ts
+// 1. Define the schema
const personType = Type.struct("example.person", {
name: Type.string(),
email: Type.string().setNullable(true),
});
+// 2. Register once
const fory = new Fory();
-const personSerde = fory.register(personType);
+const { serialize, deserialize } = fory.register(personType);
+
+// 3. Use as many times as needed
+const bytes = serialize({ name: "Alice", email: null });
+const person = deserialize(bytes);
```
-## Configuration
+Create one `Fory` instance per application and reuse it β creating a new one
for every request wastes the work of schema registration.
-The JavaScript runtime always uses xlang serialization. The most important
options are:
+## Configuration
```ts
import Fory from "@apache-fory/core";
@@ -130,21 +108,20 @@ const fory = new Fory({
maxDepth: 100,
maxBinarySize: 64 * 1024 * 1024,
maxCollectionSize: 1_000_000,
- useSliceString: false,
hps,
});
```
-| Option | Default | Meaning
|
-| -------------------------- | ------------------ |
---------------------------------------------------------------------------------------------------------------------------------
|
-| `ref` | `false` | Enables reference tracking
for graphs with shared or circular references.
|
-| `compatible` | `false` | Uses compatible struct
encoding for schema evolution.
|
-| `maxDepth` | `50` | Maximum nesting depth
accepted during deserialization. Must be `>= 2`.
|
-| `maxBinarySize` | `64 * 1024 * 1024` | Maximum allowed binary
payload for guarded binary reads.
|
-| `maxCollectionSize` | `1_000_000` | Maximum number of elements
accepted for lists, sets, and maps.
|
-| `useSliceString` | `false` | Optional string-reading
mode for Node.js environments. Leave it at the default unless you have
benchmarked a reason to change it. |
-| `hps` | unset | Optional Node.js string
fast-path helper from `@apache-fory/hps`.
|
-| `hooks.afterCodeGenerated` | unset | Callback to inspect or
transform generated serializer code. Useful for debugging.
|
+| Option | Default | Description
|
+| -------------------------- | ----------- |
-------------------------------------------------------------------------------------
|
+| `ref` | `false` | Enable reference tracking for
shared or circular object graphs |
+| `compatible` | `false` | Allow field additions/removals
without breaking existing messages |
+| `maxDepth` | `50` | Maximum nesting depth. Must be
`>= 2`. Increase for deeply nested structures |
+| `maxBinarySize` | 64 MiB | Maximum bytes accepted for any
single binary field |
+| `maxCollectionSize` | `1_000_000` | Maximum elements accepted in any
list, set, or map |
+| `useSliceString` | `false` | Optional string-reading
optimization for Node.js. Leave at default unless benchmarked |
+| `hps` | unset | Optional fast string helper from
`@apache-fory/hps` (Node.js 20+) |
+| `hooks.afterCodeGenerated` | unset | Callback to inspect the generated
serializer code β useful for debugging |
## Documentation
diff --git a/docs/guide/javascript/references.md
b/docs/guide/javascript/references.md
index 583dcc52c9..258e83a643 100644
--- a/docs/guide/javascript/references.md
+++ b/docs/guide/javascript/references.md
@@ -19,19 +19,23 @@ license: |
limitations under the License.
---
-Fory can preserve shared references and circular object graphs, but in
JavaScript you should enable reference tracking globally and mark the specific
struct fields that participate in shared or circular references.
+By default Fory treats every value as a separate copy β if the same object
appears in two fields it gets serialized twice, and after deserialization you
get two independent copies. Enable reference tracking when:
-## Enable reference tracking globally
+- the same object instance is referenced from multiple places in the graph
+- your data contains a circular structure (e.g. a node that points to itself)
+- object identity must be preserved after a round trip
+
+Leave reference tracking off for plain tree-shaped data; it adds a small
overhead.
+
+## Step 1: Enable Reference Tracking on the `Fory` Instance
```ts
const fory = new Fory({ ref: true });
```
-Without `ref: true`, Fory treats values as ordinary tree-shaped data.
+## Step 2: Mark the Fields That Can Have Shared or Circular References
-## Mark reference-capable fields
-
-For schema-based structs, mark fields that can hold shared or circular
references with `.setTrackingRef(true)`.
+For each field whose value may be shared or circular, call
`.setTrackingRef(true)` on the field's schema:
```ts
const nodeType = Type.struct("example.node", {
@@ -40,6 +44,8 @@ const nodeType = Type.struct("example.node", {
});
```
+You need **both** the global flag and the field-level flag. Missing either one
results in values being copied rather than referenced.
+
## Circular self-reference example
```ts
@@ -94,9 +100,9 @@ Leave it disabled when:
- you want the lowest overhead
- object identity does not matter
-## Cross-language note
+## Cross-Language Note
-Reference tracking is part of the xlang protocol, but every participating
runtime still needs compatible schema and configuration. If one side models a
graph as plain values and the other depends on object identity, behavior may
not match your expectations.
+Reference tracking is part of the Fory binary protocol and works across
runtimes. Both sides must enable reference tracking and mark the same fields as
reference-tracked for the behavior to be consistent.
## Related Topics
diff --git a/docs/guide/javascript/schema-evolution.md
b/docs/guide/javascript/schema-evolution.md
index 7956e7a074..4387e7cc3d 100644
--- a/docs/guide/javascript/schema-evolution.md
+++ b/docs/guide/javascript/schema-evolution.md
@@ -19,22 +19,24 @@ license: |
limitations under the License.
---
-Fory JavaScript supports two struct encodings:
+Schema evolution lets different versions of your service exchange messages
safely β a v2 writer can produce a message a v1 reader still understands, and
vice versa.
-- **schema-consistent mode**: more compact, assumes the schema matches exactly
-- **compatible mode**: writes extra metadata so readers can tolerate added or
missing fields
+## Two Modes
-## Enable compatible mode
+- **Schema-consistent mode** (default): more compact, but both sides must have
exactly the same schema. Good when all services update together.
+- **Compatible mode**: writes extra field metadata so readers can skip unknown
fields and tolerate missing ones. Good for independent deployments or rolling
upgrades.
+
+## Enable Compatible Mode
```ts
const fory = new Fory({ compatible: true });
```
-Compatible mode is the right choice when:
+Use this when:
-- multiple services roll out schema changes independently
+- services deploy schema changes independently
- older readers may see newer payloads
-- newer readers may see older payloads
+- newer readers may see older payloads from before a field was added
## Example
@@ -61,11 +63,11 @@ const readerType = Type.struct(
);
```
-With `compatible: true`, the reader can ignore fields it does not know about.
+With `compatible: true`, the reader ignores fields it does not know about, and
fills unknown fields with default values.
-## Per-struct evolving override
+## Opting Out of Evolution for One Struct
-In JavaScript, struct schemas can explicitly disable evolving metadata even
when type identity is otherwise compatible.
+You can disable evolution metadata for a specific struct even inside a
`compatible: true` instance:
```ts
const fixedType = Type.struct(
@@ -76,25 +78,20 @@ const fixedType = Type.struct(
);
```
-This produces a smaller payload than an otherwise evolving-compatible struct,
but the reader must then assume the schema matches. In cross-language use, both
sides must agree on whether that struct is evolving; if one side treats the
type as compatible and the other uses `evolving: false`, the on-wire type kind
no longer matches.
-
-## Practical guidance
-
-Choose **schema-consistent mode** when:
-
-- both ends deploy together
-- size and throughput matter most
-- schema drift is tightly controlled
+`evolving: false` produces smaller messages for that struct, but **both the
writer and reader must agree** on this setting. If one side writes with
`evolving: false` and the other reads expecting compatible metadata,
deserialization will fail.
-Choose **compatible mode** when:
+## When to Use Each Mode
-- services evolve independently
-- you need forward/backward tolerance
-- operational safety is more important than the last bytes of payload size
+| | Schema-consistent | Compatible |
+| ------------------------------- | ----------------- | ------------------- |
+| Services always update together | β best choice | works, but wasteful |
+| Independent deployments | will break | β best choice |
+| Smallest possible messages | β | slightly larger |
+| Rolling upgrades | risky | β safe |
-## Cross-language requirement
+## Cross-Language Requirement
-Compatible mode only helps when the logical type identity still matches across
runtimes. The same struct must still be registered with the same numeric ID or
name across languages.
+Compatible mode only protects you from schema differences in the _fields_ of a
type. You still need the same type identity (same numeric ID or same `namespace
+ typeName`) on every side. See [Cross-Language](cross-language.md).
## Related Topics
diff --git a/docs/guide/javascript/supported-types.md
b/docs/guide/javascript/supported-types.md
index 0186a18726..d61f135e5b 100644
--- a/docs/guide/javascript/supported-types.md
+++ b/docs/guide/javascript/supported-types.md
@@ -19,49 +19,45 @@ license: |
limitations under the License.
---
-This page summarizes the main JavaScript and TypeScript values supported by
Fory JavaScript.
+This page lists the JavaScript and TypeScript types supported by Fory, and
explains when you need to be deliberate about type choices for cross-language
compatibility.
## Primitive and Scalar Types
-| JavaScript/TypeScript | Fory schema
| Notes
|
-| ------------------------ |
---------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------
|
-| `boolean` | `Type.bool()`
| Boolean value
|
-| `number` | `Type.int8()` / `Type.int16()` / `Type.int32()` /
`Type.float32()` / `Type.float64()` / unsigned variants | Pick explicit numeric
width/encoding in schemas |
-| `bigint` | `Type.int64()` / `Type.varInt64()` /
`Type.uint64()` / tagged variants | Use for
64-bit integer fields |
-| `string` | `Type.string()`
| Encoded as Latin1,
UTF-16, or UTF-8 depending on content/runtime path |
-| `Uint8Array` | `Type.binary()`
| Variable-length
binary payload |
-| `Date` | `Type.timestamp()` / dynamic root date
| Timestamps round-trip
as `Date` |
-| `Date` or day count | `Type.date()`
| Date values
deserialize as `Date`; some typed APIs also accept numeric day counts |
-| duration in milliseconds | `Type.duration()`
| JavaScript currently
exposes duration fields as numeric millisecond values |
-| `BFloat16` or `number` | `Type.bfloat16()`
| Deserializes to
`BFloat16` |
-| `number` | `Type.float16()`
| Half-precision
floating-point support |
+| JavaScript value | Fory schema
| Notes
|
+| --------------------- |
-------------------------------------------------------------------------------------
| ---------------------------------------------------- |
+| `boolean` | `Type.bool()`
|
|
+| `number` | `Type.int8()` / `Type.int16()` / `Type.int32()` /
`Type.float32()` / `Type.float64()` | Pick the width that matches the peer
language |
+| `bigint` | `Type.int64()` / `Type.varInt64()` / `Type.uint64()`
| Use `bigint` for 64-bit integers
|
+| `string` | `Type.string()`
|
|
+| `Uint8Array` | `Type.binary()`
| Binary blob
|
+| `Date` | `Type.timestamp()`
| Serializes/deserializes as `Date`
|
+| `Date` | `Type.date()`
| Date without time; deserializes as `Date`
|
+| duration (ms) | `Type.duration()`
| Exposed as a numeric millisecond value in
JavaScript |
+| `number` | `Type.float16()`
| Half-precision float
|
+| `BFloat16` / `number` | `Type.bfloat16()`
| Deserializes to `BFloat16`
|
## Integer Types
-Use explicit integer schema helpers when the wire contract matters.
+JavaScript `number` is a 64-bit float. It cannot safely represent all 64-bit
integers (integers above `Number.MAX_SAFE_INTEGER` lose precision). Use
explicit schemas to match the width expected by the peer language:
```ts
-Type.int8();
-Type.int16();
-Type.int32();
-Type.varInt32();
-Type.int64();
+Type.int8(); // -128 to 127
+Type.int16(); // -32,768 to 32,767
+Type.int32(); // matches Java int, Go int32, C# int
+Type.varInt32(); // variable-length encoding; smaller for small values
+Type.int64(); // use with bigint; matches Java long, Go int64
Type.varInt64();
Type.sliInt64();
Type.uint8();
Type.uint16();
Type.uint32();
Type.varUInt32();
-Type.uint64();
+Type.uint64(); // use with bigint
Type.varUInt64();
Type.taggedUInt64();
```
-### Important JavaScript notes
-
-- `number` cannot safely represent all 64-bit integers.
-- For 64-bit integer fields, prefer `bigint` values in application code.
-- Dynamic root deserialization may return `bigint` for integer values that
exceed JavaScript's safe integer range.
+**Rule of thumb**: anything that maps to a 64-bit integer in another language
should use `Type.int64()` or `Type.uint64()` on the JavaScript side and be
passed as a `bigint` value.
## Floating-Point Types
@@ -89,33 +85,22 @@ Type.array(
These map to JavaScript arrays.
-### Optimized typed arrays
+## Optimized Numeric Arrays
-Fory JavaScript supports specialized array schemas for compact numeric and
boolean arrays.
+For arrays of numbers, use the specialized typed array schemas. They are more
compact and map to native typed arrays:
```ts
-Type.boolArray();
-Type.int16Array();
-Type.int32Array();
-Type.int64Array();
-Type.float16Array();
-Type.bfloat16Array();
-Type.float32Array();
-Type.float64Array();
+Type.boolArray(); // boolean[] in JS
+Type.int16Array(); // Int16Array
+Type.int32Array(); // Int32Array
+Type.int64Array(); // BigInt64Array
+Type.float32Array(); // Float32Array
+Type.float64Array(); // Float64Array
+Type.float16Array(); // number[]
+Type.bfloat16Array(); // BFloat16[]
```
-Typical runtime values are:
-
-| Schema | Typical JavaScript value
|
-| ---------------------- |
-------------------------------------------------------------------- |
-| `Type.boolArray()` | `boolean[]`
|
-| `Type.int16Array()` | `Int16Array`
|
-| `Type.int32Array()` | `Int32Array`
|
-| `Type.int64Array()` | `BigInt64Array`
|
-| `Type.float32Array()` | `Float32Array`
|
-| `Type.float64Array()` | `Float64Array`
|
-| `Type.float16Array()` | `number[]`
|
-| `Type.bfloat16Array()` | `BFloat16Array` or `number[]` as input;
deserializes to `BFloat16[]` |
+For non-numeric or struct arrays, use `Type.array(elementType)` instead.
## Maps and Sets
@@ -148,7 +133,7 @@ Type.enum("example.color", {
});
```
-Both numeric and string enum values are supported. JavaScript encodes enum
members by ordinal position in the declared enum object order and maps that
ordinal back to the corresponding JavaScript value on read, so cross-language
peers must agree on the enum member order and shape, not only the literal
values.
+Fory encodes enum values by their ordinal position in the object (not their
value). Both sides must declare enum members in the same order. When
interoperating with another language, make sure the member order matches, not
just the values.
## Nullable fields
@@ -158,9 +143,9 @@ Use `.setNullable(true)` when a field may be `null`.
Type.string().setNullable(true);
```
-## Dynamic fields
+## Dynamic Fields
-Use `Type.any()` for dynamically typed content.
+Use `Type.any()` when a field can hold values of different types at runtime.
```ts
const eventType = Type.struct("example.event", {
@@ -169,23 +154,21 @@ const eventType = Type.struct("example.event", {
});
```
-This is useful for polymorphic payload slots, but more explicit field types
are preferable when the schema is stable.
+Explicit field schemas are preferable when the type is known β `Type.any()` is
harder to keep aligned across languages.
-## Reference-tracked fields
+## Reference-Tracked Fields
-Fields that can participate in shared or circular graphs should opt in:
+When the same object instance can appear in multiple fields, or when your
graph is circular, opt individual fields into reference tracking:
```ts
Type.struct("example.node").setTrackingRef(true).setNullable(true);
```
-This requires `new Fory({ ref: true })` at the instance level.
-
-## Extension types and advanced areas
+This requires `new Fory({ ref: true })`. See [References](references.md).
-JavaScript supports extension types through `Type.ext(...)` plus a custom
serializer passed to `fory.register(...)`.
+## Extension Types
-The xlang specification also includes additional kinds such as unions. If you
plan to depend on advanced features beyond the documented JavaScript surface,
validate the exact API and interoperability behavior in your target runtime
versions before committing to a shared wire contract.
+For types that need completely custom encoding, use `Type.ext(...)` and pass a
custom serializer to `fory.register(...)`. This is an advanced use case; the
standard `Type.struct` covers most scenarios.
## Related Topics
diff --git a/docs/guide/javascript/troubleshooting.md
b/docs/guide/javascript/troubleshooting.md
index fa2dc61358..275780d7b7 100644
--- a/docs/guide/javascript/troubleshooting.md
+++ b/docs/guide/javascript/troubleshooting.md
@@ -21,90 +21,82 @@ license: |
This page covers common problems when using Fory JavaScript.
-## Non-xlang payloads cannot be deserialized
+## Cannot deserialize a non-cross-language payload
-The JavaScript runtime reads xlang payloads only. If you try to deserialize a
non-xlang payload, deserialization fails.
+The Fory JavaScript runtime only reads Fory cross-language payloads. If the
producer is a Java or Go service using a language-native format, the JavaScript
side cannot decode it.
-Make sure the producer is writing Fory xlang data.
+Fix: switch the producer to the cross-language mode. For Java, use
`.withLanguage(Language.XLANG)`; for Go, use `WithXlang(true)`.
## `maxDepth must be an integer >= 2`
-`maxDepth` protects the deserializer from excessive nesting.
+This means you passed an invalid `maxDepth` value. It must be a positive
integer of at least 2.
```ts
new Fory({ maxDepth: 100 });
```
-Use a larger value only when your payloads genuinely need it.
+Increase this only if your data is legitimately deeply nested.
## `Binary size ... exceeds maxBinarySize`
-A binary field or payload exceeded the configured safety limit.
+A binary field or the overall message exceeded the safety limit. If the size
is expected and the source is trusted, increase the limit:
```ts
new Fory({ maxBinarySize: 128 * 1024 * 1024 });
```
-Increase the limit only if the input size is expected and trusted.
-
## `Collection size ... exceeds maxCollectionSize`
-A list, set, or map exceeded the configured collection limit.
+A list, set, or map has more elements than the configured limit. This often
means the data is unexpectedly large. If it is legitimate, increase the limit:
```ts
new Fory({ maxCollectionSize: 2_000_000 });
```
-This is commonly hit when a producer sends unexpectedly large arrays or maps.
-
## `Field "..." is not nullable`
-A schema field was written as `null` but was not marked nullable.
+You are passing `null` to a field that was not declared nullable. Fix: add
`.setNullable(true)` to the field schema:
```ts
const userType = Type.struct("example.user", {
name: Type.string(),
- email: Type.string().setNullable(true),
+ email: Type.string().setNullable(true), // β this field can be null
});
```
-Mark nullable fields explicitly.
+## Objects are not the same instance after deserialization
-## Reference graphs do not preserve identity
+Fory does not preserve object identity by default. Two fields pointing to the
same object will become two independent copies.
-Check both conditions:
+Fix: enable **both** of these:
-1. `new Fory({ ref: true })` is enabled
-2. the relevant schema fields use `.setTrackingRef(true)`
+1. `new Fory({ ref: true })` on the instance
+2. `.setTrackingRef(true)` on the specific fields
-Missing either one will usually turn the graph into ordinary value-based
serialization.
+See [References](references.md).
## Large integers come back as `bigint`
-This is expected for 64-bit integer values or for dynamic numbers that exceed
JavaScript's safe integer range. Use explicit numeric schemas and `bigint` in
your application when exact 64-bit integer semantics matter.
+This is expected. Fory uses `bigint` for any 64-bit integer field
(`Type.int64()`, `Type.uint64()`). If you need a `number`, use a smaller
integer type like `Type.int32()` β but only if the value actually fits in 32
bits.
-## Debugging generated serializers
+## Inspecting Generated Serializer Code
-Use `hooks.afterCodeGenerated` to inspect generated code.
+If you need to debug what Fory is doing under the hood, inspect the generated
serializer code with a hook:
```ts
const fory = new Fory({
hooks: {
afterCodeGenerated(code) {
- console.error(code);
+ console.log(code);
return code;
},
},
});
```
-## Optional `@apache-fory/hps` install issues
-
-`@apache-fory/hps` is optional and Node-specific. If installation fails,
remove it from your config and continue with `@apache-fory/core` alone.
+## `@apache-fory/hps` Install Fails
-```ts
-const fory = new Fory();
-```
+`@apache-fory/hps` is an optional Node.js accelerator. If it fails to install
(e.g. on a platform without native module support), just remove it from your
dependencies. Fory still works correctly without it.
## Related Topics
diff --git a/docs/guide/javascript/type-registration.md
b/docs/guide/javascript/type-registration.md
index 07e908aec8..e922e8ff30 100644
--- a/docs/guide/javascript/type-registration.md
+++ b/docs/guide/javascript/type-registration.md
@@ -19,13 +19,15 @@ license: |
limitations under the License.
---
-Type registration tells Fory JavaScript how to identify and encode
user-defined structs and enums.
+Every struct and enum you serialize must be registered with the `Fory`
instance before use. Registration tells Fory how to identify the type in a
message and how to encode and decode it.
## Registering Structs
-You can register a struct by numeric ID or by name.
+You can identify a struct with a numeric ID or with a name. Pick one strategy
and use it consistently across all languages that share the same messages.
-### Register by numeric type ID
+### Register by numeric ID
+
+Smaller wire representation. Good when a small team can coordinate IDs.
```ts
const userType = Type.struct(
@@ -37,17 +39,15 @@ const userType = Type.struct(
);
const fory = new Fory();
-const userSerde = fory.register(userType);
+const { serialize, deserialize } = fory.register(userType);
```
-Use numeric IDs when:
-
-- you want compact metadata on the wire
-- you control IDs across all participating languages
-- the same logical type is registered everywhere with the same ID
+The same number must be used in every runtime that reads or writes this type.
### Register by name
+Easier to coordinate across teams. Slightly larger metadata in the message.
+
```ts
const userType = Type.struct(
{ typeName: "example.user" },
@@ -58,19 +58,14 @@ const userType = Type.struct(
);
const fory = new Fory();
-const userSerde = fory.register(userType);
+const { serialize, deserialize } = fory.register(userType);
```
-Named registration is usually easier to evolve operationally because it avoids
central numeric ID allocation, but it writes more metadata than numeric IDs.
-
-### Explicit namespace and type name
+You can also split namespace and type name explicitly:
```ts
const userType = Type.struct(
- {
- namespace: "example",
- typeName: "user",
- },
+ { namespace: "example", typeName: "user" },
{
id: Type.int64(),
name: Type.string(),
@@ -78,7 +73,7 @@ const userType = Type.struct(
);
```
-This corresponds to the named xlang type identity carried in metadata.
+> **Do not mix strategies for the same type across runtimes.** If one side
uses a numeric ID and the other uses a name, deserialization will fail.
## Registering with Decorators
@@ -129,41 +124,29 @@ fory.register(Type.enum("example.status", Status));
## Registration Scope
-Registration is per `Fory` instance.
+Registration is per `Fory` instance. If you create two instances, you need to
register schemas in both.
-```ts
-const fory1 = new Fory();
-const fory2 = new Fory();
+## What `register` Returns
-fory1.register(
- Type.struct("example.user", {
- id: Type.int64(),
- }),
-);
-
-// fory2 does not know that schema until you register it again there.
-```
-
-## Schema Handles Returned by `register`
-
-`register` returns a bound serializer pair:
+`fory.register(schema)` returns a bound serializer pair:
```ts
-const orderType = Type.struct("example.order", {
- id: Type.int64(),
- total: Type.float64(),
-});
+const { serialize, deserialize } = fory.register(orderType);
-const { serializer, serialize, deserialize } = new Fory().register(orderType);
+// serialize returns Uint8Array bytes
+const bytes = serialize({ id: 1n, total: 99.99 });
+
+// deserialize returns the decoded value
+const order = deserialize(bytes);
```
-Use these bound functions for repeated operations on the same type.
+Store and reuse this pair β it is the fast path.
-## Field Metadata
+## Field Options
-Each field type can be refined with schema metadata.
+### Nullable fields
-### Nullability
+If a field can be `null`, mark it explicitly. Passing `null` to a non-nullable
field throws.
```ts
Type.string().setNullable(true);
@@ -171,44 +154,44 @@ Type.string().setNullable(true);
### Reference tracking on a field
+Needed when the same object instance can appear in multiple fields (see
[References](references.md)):
+
```ts
Type.struct("example.node").setTrackingRef(true);
```
-This matters only when `new Fory({ ref: true })` is also enabled globally.
+This only has an effect when `new Fory({ ref: true })` is also set.
-### Dynamic dispatch
+### Polymorphic fields
-```ts
-import { Dynamic, Type } from "@apache-fory/core";
+Use `Type.any()` when a field can hold different types at runtime:
-Type.struct("example.child").setDynamic(Dynamic.FALSE);
+```ts
+const eventType = Type.struct("example.event", {
+ kind: Type.string(),
+ payload: Type.any(),
+});
```
-Use `dynamic` when the runtime type behavior must be controlled explicitly. In
this implementation, `Dynamic.FALSE` forces monomorphic handling,
`Dynamic.TRUE` forces polymorphic handling, and `Dynamic.AUTO` leaves the
decision to the runtime. This is especially relevant for polymorphic fields in
xlang payloads, while most users should keep the default behavior unless they
are tuning a specific schema edge case.
+For fine-grained control over how a specific struct field handles its runtime
type, you can call `.setDynamic(Dynamic.FALSE)` (always treat as the declared
type) or `.setDynamic(Dynamic.TRUE)` (always write the runtime type). The
default (`Dynamic.AUTO`) is correct for the vast majority of cases.
-## Choosing IDs vs names
+## Choosing IDs vs Names
Use **numeric IDs** when:
-- you want the smallest wire representation
-- your organization can keep IDs stable and unique
-- multiple runtimes are coordinated tightly
+- you want the smallest possible message size
+- your organization can keep IDs stable and globally unique
+- services are tightly coordinated
Use **names** when:
-- you want easier decentralized coordination
-- schemas are shared by package/module name already
-- slightly larger metadata is acceptable
-
-## Cross-language requirement
-
-For xlang interoperability, the serializer and deserializer must agree on the
same type identity:
+- teams define types independently
+- schemas are already identified by package/module name
+- slightly larger metadata overhead is acceptable
-- same numeric ID, or
-- same namespace + type name
+## Cross-Language
-The field schema must also match in a cross-language-compatible way. See
[Cross-Language](cross-language.md).
+For a message to round-trip between JavaScript and another runtime, both sides
must use the same identity for a given type: same numeric ID, or same
`namespace + typeName`. See [Cross-Language](cross-language.md).
## Related Topics
diff --git a/docs/guide/kotlin/_category_.json
b/docs/guide/kotlin/_category_.json
index c20ca242fa..18540b4801 100644
--- a/docs/guide/kotlin/_category_.json
+++ b/docs/guide/kotlin/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Kotlin",
- "position": 12,
+ "position": 11,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/python/_category_.json
b/docs/guide/python/_category_.json
index a964bce6e9..b37a319075 100644
--- a/docs/guide/python/_category_.json
+++ b/docs/guide/python/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Python",
- "position": 1,
+ "position": 2,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/rust/_category_.json b/docs/guide/rust/_category_.json
index 845d58f46c..e891086214 100644
--- a/docs/guide/rust/_category_.json
+++ b/docs/guide/rust/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Rust",
- "position": 2,
+ "position": 3,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/scala/_category_.json b/docs/guide/scala/_category_.json
index 7e42919800..a0191e2873 100644
--- a/docs/guide/scala/_category_.json
+++ b/docs/guide/scala/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Scala",
- "position": 11,
+ "position": 10,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/swift/_category_.json b/docs/guide/swift/_category_.json
index 1e17c92270..dfdaf59968 100644
--- a/docs/guide/swift/_category_.json
+++ b/docs/guide/swift/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Swift",
- "position": 10,
+ "position": 8,
"collapsible": true,
"collapsed": true
}
diff --git a/docs/guide/xlang/_category_.json b/docs/guide/xlang/_category_.json
index c356a9b915..ab2d183de0 100644
--- a/docs/guide/xlang/_category_.json
+++ b/docs/guide/xlang/_category_.json
@@ -1,6 +1,6 @@
{
"label": "Cross Language",
- "position": 15,
+ "position": 12,
"collapsible": true,
"collapsed": true
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]