TypeSafe Schemas

Ack generates extension types from top-level schemas annotated with @AckType(). The schema stays in your source file; the generator adds a typed wrapper around its validated representation.

Overview

  • Define schemas directly with the Ack fluent API.
  • Annotate the top-level schema variable or getter with @AckType().
  • Run dart run build_runner build.
  • Use the generated TypeName.parse() / TypeName.safeParse() helpers.

Basic usage

import 'package:ack/ack.dart';
import 'package:ack_annotations/ack_annotations.dart';

part 'user_schema.g.dart';

@AckType()
final addressSchema = Ack.object({
  'street': Ack.string(),
  'city': Ack.string(),
});

@AckType()
final userSchema = Ack.object({
  'id': Ack.string(),
  'email': Ack.string().email().nullable(),
  'address': addressSchema,
});

The generated part file contains:

  • AddressType(Map<String, Object?> _data)
  • UserType(Map<String, Object?> _data)
  • typed getters for each field
  • parse() and safeParse() factories

Supported schema shapes

@AckType() works with:

  • Ack.object(...)
  • primitive schemas such as Ack.string(), Ack.integer(), Ack.double(), Ack.boolean()
  • Ack.list(...)
  • Ack.literal(...), Ack.enumString(...), Ack.enumValues(...)
  • non-object transforms with explicit output types
  • Ack.discriminated(...) with the constraints below

Unsupported helpers include Ack.any() and Ack.anyOf().

Discriminated schemas

Ack.discriminated(...) works with @AckType() when:

  • schemas is a non-empty map literal
  • the base schema is not nullable
  • each branch is a top-level schema variable or getter
  • each branch is an @AckType object schema
  • each branch is non-nullable
  • each branch is declared in the same library as the base
  • the branch discriminator literal matches the branch key

Example:

@AckType()
final catSchema = Ack.object({
  'type': Ack.literal('cat'),
  'lives': Ack.integer(),
});

@AckType()
final dogSchema = Ack.object({
  'type': Ack.literal('dog'),
  'breed': Ack.string(),
});

@AckType()
final petSchema = Ack.discriminated(
  discriminatorKey: 'type',
  schemas: {
    'cat': catSchema,
    'dog': dogSchema,
  },
);

Resolution rules

@AckType() uses strict schema resolution.

  • Nested object fields must reference a named top-level schema.
  • Inline anonymous object fields are rejected for typed generation.
  • Ack.list(...) elements must be statically resolvable.
  • Cross-file references work for direct imports, prefixed imports, and re-exports.
  • Unannotated object schema references fail generation instead of silently falling back to raw maps.
  • Circular alias/reference chains fail generation with a clear error.

Limitations

  • @AckType() is only supported on top-level schema variables and getters.
  • Nullable top-level schemas do not emit extension types.
  • List element nullability may not be preserved in all chained modifier cases.
  • Use .transform<T>(...) with an explicit output type so the generator can infer the representation type.

Build checklist

  1. Add ack_annotations, ack_generator, and build_runner.
  2. Add a part '<file>.g.dart'; directive.
  3. Annotate top-level schema variables/getters with @AckType().
  4. Run dart run build_runner build.