Skip to content

v0.18.0

Choose a tag to compare

@medz medz released this 22 Oct 15:17
· 40 commits to main since this release
6a57743

This release lets you keep part of a class alias generic, carry JavaScript
payloads through value and resource classes, and use those payloads as Map keys.
Dart handles the surrounding structure while the original JavaScript values
keep their identity.

🧩 Keep class aliases partially generic

Fix the Dart type arguments your API needs and leave the payload type open:

class Pair<A, B> {
  Pair(this.first, this.second);
  A first;
  B second;
}

@NAPI()
typedef IntPair<T> = Pair<int, T>;
const payload = {name: 'example'};
const pair = new IntPair(1, payload);

pair.first;              // 1: a Dart int
pair.second === payload; // true: the original JavaScript object

TypeScript exposes IntPair<T>. The remaining T is an opaque payload, not a
runtime Dart specialization: Dart can store and forward it without inspecting
its JavaScript contents.

Aliases can also select nullable and nested payload shapes, such as
Pair<int, T?> or Pair<int, List<T?>>. Ordinary containers are copied;
opaque leaves retain their identity. Every alias parameter must occur in the
selected type, and ordinary open classes keep their separate specialization
limits.

Partially open class aliases

📦 Carry payloads through value and resource classes

Generic payloads are no longer limited to reference classes. Value classes can
accept and return them through their declared <T> parameters: the surrounding
plain object is copied, while its opaque payloads remain the original
JavaScript values.

Resource classes can store and forward these payloads too, with the existing
cleanup and operation-lease rules. Selected generic resource ancestors may
rename or reorder all type parameters when their cleanup contracts agree.

Opaque payloads and their limits

🗝️ Use JavaScript payloads as Map keys

Dart can now group or look up opaque payloads without converting them to
strings. For example, count repeated JavaScript objects by identity:

@NAPI()
Map<T, int> counts<T>(List<T> values) {
  final result = <T, int>{};
  for (final value in values) {
    result[value] = (result[value] ?? 0) + 1;
  }
  return result;
}
const first = {};
const second = {};
const result = counts([first, second, first]);

result.get(first);  // 2
result.get(second); // 1

The returned Map is an independent container; its opaque keys retain their
original identity. Opaque keys use JavaScript Map's SameValueZero equality,
including NaN and signed zero. Enclosing record and value-class keys still
use Dart equality, and keys that collapse during conversion fail rather than
overwrite an entry.

JS collection handles can also be keys in copied Dart Maps. JsMap now accepts
typed function keys under the existing signature-specific function conversion
and identity rules.

Map key behavior
and JS collection handles

Other improvements and fixes

  • Resource defaults in value-class constructors now participate in operation
    leases. Resources stored in constant defaults are retained, including
    inherited fields; discarded constructor arguments do not acquire extra leases.
  • Fix dual-target builds failing with an internal error for open generic value
    classes and partially open value aliases.
  • Fix sequential builds reporting an output as locked after the previous build
    has completed.

Regenerate bindings to use the new conversions. Both build targets still require
Dart 3.13.5; native packages support macOS arm64 with bridge ABI 15.