---
title: API Reference
---

This page is a curated quick reference for the core Ack classes, methods, and
annotations. Use the [generated API documentation](https://pub.dev/documentation/ack/latest/ack/)
for every public declaration and exact signatures; use the linked guides here
for explanations and examples.

## Core `Ack` Class

Entry point for creating schemas. See [Schema Types](../core-concepts/schemas.mdx).

- `Ack.string()`: Creates a `StringSchema` for validating `String` values.
- `Ack.integer()`: Creates an `IntegerSchema` for finite numeric values with no
  fractional part. Losslessly normalizes inputs such as `42.0` to `int`.
- `Ack.double()`: Creates a `DoubleSchema` for finite numeric values. Losslessly
  normalizes inputs such as `42` to `double`.
- `Ack.number()`: Creates a `NumberSchema` for finite `num` values while
  preserving their `int` or `double` representation.
- `Ack.boolean()`: Creates a `BooleanSchema` for validating `bool` values.
- `Ack.list(AckSchema itemSchema)`: Creates a `ListSchema` for validating arrays. Nullable item schemas are not supported; make the list itself nullable instead.
- `Ack.object(Map<String, AckSchema> properties)`: Creates an `ObjectSchema` for validating objects.
- `Ack.enumValues(List<T> values)`: Creates an `EnumSchema<T>` for Dart enum
  types. Parses enum `.name` strings into typed enum values. Pass typed enum
  values to `encode` or `safeEncode` for the reverse direction. **Preferred
  over `enumString` when a Dart enum exists.**
- `Ack.enumCodec(List<T> values)`: Like `enumValues`, but returns a
  `CodecSchema<String, T>` instead of an `EnumSchema<T>`. Use this when
  downstream code expects every value-shape to be a `CodecSchema` (e.g. a
  registry of codecs). The decode and encode functions are identity — the
  underlying `EnumSchema` still maps between `T` and the enum's `.name`.
- `Ack.enumString(List<String> values)`: Creates a `StringSchema` constrained
  to the given values. For ad-hoc string lists without a backing Dart enum.
- `Ack.anyOf(List<AckSchema> schemas)`: Creates an `AnyOfSchema` for union types.
- `Ack.any()`: Creates an `AnySchema` that accepts any non-null JSON-safe
  value. Chain `.nullable()` to allow `null`.
- `Ack.fromJsonSchema(Object document, {Uri? baseUri, Map<Uri, Object>
  documents = const {}})`: Strictly imports a decoded draft 2020-12
  JSON Schema. Referenced documents must be supplied explicitly; Ack never
  fetches files or URLs. The supported `date-time` format is asserted; see
  [format import](../guides/json-schema-integration.mdx#importing-format).
- `Ack.map(AckSchema valueSchema)`: Creates a `MapSchema` for a JSON object
  with arbitrary string keys whose values all match `valueSchema`. Use
  `Ack.map(Ack.any().nullable())` for a `JsonMap`.
- `Ack.instance<T extends Object>()`: Creates a schema that checks a value is a
  Dart instance of `T` (runtime type check; handy as a codec `output` schema).
- `Ack.codec(...)` / `Ack.date()` / `Ack.datetime()` / `Ack.uri()` /
  `Ack.duration()` / `Ack.enumCodec(...)`: Create codecs. See
  [Codecs](../core-concepts/codecs.mdx).
- `Ack.lazy(String name, AckSchema Function() builder, {int maxDepth = 100})`:
  Creates a memoized deferred schema reference for recursive schema graphs.
  `maxDepth` must be at least `1` and limits parsing, runtime validation, and
  encoding. JSON Schema export renders Draft-7 `definitions` / `$ref` entries
  using `name` but warns that the runtime-only depth limit was omitted.
- `Ack.discriminated<T extends Object>(...)`: Creates a discriminated union
  schema. Branches may be plain `ObjectSchema` or transformed schemas whose
  base is an `ObjectSchema`. The union owns the discriminator: branches normally
  omit it, boundary payloads must include it, compatible branch discriminator
  fields are allowed, and conflicts are rejected. Exported/generated branches
  expose the exact branch literal.

## JSON Schema Import

See [JSON Schema Integration](../guides/json-schema-integration.mdx) for the
supported draft 2020-12 subset, reference-bundle behavior, diagnostics, and
examples.

- `JsonSchemaImportDiagnostic`: Describes one import issue through `code`,
  `documentUri`, `pointer`, `keyword`, and `message`.
- `JsonSchemaImportException`: Thrown when an import encounters an unsupported
  assertion or cannot be completed safely. Its
  `diagnostics` list identifies every reported issue.
- `JsonSchemaValidationError`: A `SchemaValidationError` returned when a value
  fails an imported schema. `keyword` names the failing keyword, and
  `documentUri` and `pointer` locate it after `$ref` resolution.

## `AckSchema<Boundary, Runtime>` (Base Class)

Base class for all schema types.

### Primary Validation Methods

- `SchemaResult<Runtime> safeParse(Object? data, {String? debugName})`: Validates
  `data` and returns a `SchemaResult`. Invalid input and recoverable `Exception`
  values thrown by constraint/refinement callbacks become failures. `Error`
  values from those callbacks are rethrown with their original stack trace.
  Codec/transform decoder failures, including `Error` values, become
  `SchemaTransformError` failures.
- `Runtime? parse(Object? data, {String? debugName})`: Validates `data` and returns the value; throws `AckException` on failure.
- `SchemaResult<TOut> safeParseAs<TOut extends Object>(Object? data, TOut Function(Runtime?) map, {String? debugName})`: Parses and maps the validated value to `TOut`. Mapper `Exception`s become `SchemaTransformError` failures; mapper `Error` values are rethrown with their original stack trace.
- `TOut parseAs<TOut extends Object>(Object? data, TOut Function(Runtime?) map, {String? debugName})`: Throwing variant of `safeParseAs`.
- `SchemaResult<Boundary> safeEncode(Runtime? value, {String? debugName})`: Encodes a runtime value to the boundary representation.
- `Boundary? encode(Runtime? value, {String? debugName})`: Throwing variant of `safeEncode`.

### Schema Modification Methods

- `AckSchema<Boundary, Runtime> nullable({bool value = true})`: Returns a new schema that also accepts `null`.
- `AckSchema<Boundary, Runtime> optional({bool value = true})`: Returns a new schema marked as optional (for object fields).
- `AckSchema<Boundary, Runtime> describe(String description)`: Attaches a description for documentation and JSON Schema generation.
- `DefaultSchema<Boundary, Runtime> withDefault(Runtime value)`: Wraps the schema in a `DefaultSchema` that supplies `value` when the parse input is `null`.

String and boolean schemas require their exact Dart runtime type. Numeric
schemas require a `num` and follow JSON Schema value semantics instead of the
platform-specific `int`/`double` representation: `IntegerSchema` normalizes
lossless zero-fraction values to `int`, `DoubleSchema` normalizes exactly
representable values to `double`, and `NumberSchema` preserves the input
representation. Integer and double branches therefore overlap for integral
values in `Ack.anyOf`; the first matching branch determines the runtime value.
For non-`num` boundary types (e.g. numeric strings), use
[`transform`](../core-concepts/schemas.mdx#transformations) or
[`codec`](#codecschemaboundary-runtime) to convert before validation.

### Custom Validation Methods

- `AckSchema<Boundary, Runtime> constrain(Constraint<Runtime> constraint, {String? message})`: Adds a constraint and optionally overrides its message. The constraint must mix in `Validator<Runtime>`, or an `ArgumentError` is thrown.
- `AckSchema<Boundary, Runtime> withConstraint(Constraint<Runtime> constraint)`: Adds a constraint directly (no message override; `constrain` delegates here).
- `AckSchema<Boundary, Runtime> refine(bool Function(Runtime) validate, {String message = 'The value did not pass the custom validation.'})`: Adds a custom validation predicate with an optional error message.
- `CodecSchema<Boundary, R> transform<R>(R Function(Runtime) transformer)`: Transforms validated runtime values to `R` (parse-only; encode fails).

### Utility Methods

- `Map<String, Object?> toJsonSchema()`: Returns a Draft-7 JSON Schema map via
  the canonical `AckSchemaModel` boundary.
- `AckSchemaModel toSchemaModel()`: Returns the canonical, target-independent
  boundary model for schema adapters, including export warnings.
- `Map<String, Object?> toMap()`: Serializes the schema for debugging.

See also [Schema Types](../core-concepts/schemas.mdx) for detailed usage examples.

## `StringSchema`

Schema for validating strings. See [String Validation](../core-concepts/validation.mdx#string-constraints).

### Length Constraints

- `minLength(int min)`: Minimum string length
- `maxLength(int max)`: Maximum string length
- `length(int exact)`: Exact string length
- `notEmpty()`: String must not be empty (equivalent to `minLength(1)`)

### Pattern Matching

- `matches(String pattern, {String? example, String? message})`: Must match a regex pattern. Patterns are not automatically anchored — use `^...$` for full-string matching. See [String validation](../core-concepts/validation.mdx#string-constraints) for details.
- `contains(String pattern, {String? example, String? message})`: Must contain the pattern anywhere in the string.
- `startsWith(String value)`: Must start with `value`.
- `endsWith(String value)`: Must end with `value`.

### Format Validation

- `email()`: Must be valid email format
- `url()`: Must be valid URL format (alias for `uri()`)
- `uri()`: Must be a valid absolute URI with a scheme and host
- `uuid()`: Must be valid UUID format
- `ip({int? version})`: Must be valid IP address (version 4 or 6)
- `ipv4()`: Must be valid IPv4 address
- `ipv6()`: Must be valid IPv6 address

### Date and Time

- `date()`: Must be valid ISO 8601 date (YYYY-MM-DD)
- `datetime()`: Must be a valid ISO 8601 datetime; announced RFC leap seconds
  are accepted and preserved as strings
- `time()`: Must be valid time format (HH:MM:SS)

### Transformations

- `trim()`: Removes leading and trailing whitespace
- `toLowerCase()`: Converts to lowercase
- `toUpperCase()`: Converts to uppercase

## `IntegerSchema` / `DoubleSchema` / `NumberSchema` (Number Schemas)

Schemas for validating numeric values. `IntegerSchema` accepts finite `num`
values with no fractional part when conversion to `int` is lossless.
`DoubleSchema` accepts finite `num` values when conversion to `double` is
lossless. `NumberSchema` accepts finite `num` values without changing their
runtime representation. See
[Number Validation](../core-concepts/validation.mdx#number-constraints).

Each method's parameter type matches the schema's runtime type: `int` for `IntegerSchema`, `double` for `DoubleSchema`, and `num` for `NumberSchema`.

- `min(N limit)`: Minimum value (inclusive)
- `max(N limit)`: Maximum value (inclusive)
- `greaterThan(N limit)`: Must be greater than limit (exclusive)
- `lessThan(N limit)`: Must be less than limit (exclusive)
- `positive()`: Must be greater than 0
- `negative()`: Must be less than 0
- `multipleOf(N factor)`: Must be a multiple of the factor
- `finite()`: Must be finite (`DoubleSchema` and `NumberSchema`; already the default)
- `safe()`: Must be within safe integer range (`IntegerSchema` only)

## `BooleanSchema`

Schema for validating booleans. Validates `true` and `false` values strictly — non-boolean inputs are rejected. For boundary types that arrive as strings (e.g. `"true"`/`"false"`), use a `transform` or `codec` to convert before validation.

## `ListSchema<T>`

Schema for validating arrays. See [List Validation](../core-concepts/validation.mdx#list-constraints).

- `minItems(int min)`: Minimum number of items (alias: `minLength`)
- `maxItems(int max)`: Maximum number of items (alias: `maxLength`)
- `exactLength(int exact)`: Exact number of items (alias: `length`)
- `nonEmpty()`: List must have at least one item (alias: `notEmpty`)
- `unique()`: All items must be unique

## `ObjectSchema`

Schema for validating objects (maps). See [Object Validation](../core-concepts/schemas.mdx#object).

- Constructed using `Ack.object(Map<String, AckSchema> properties, {bool additionalProperties = false})`.
- Use `.pick(List<String> keys)` to create schema with only specified properties.
- Use `.omit(List<String> keys)` to create schema excluding specified properties.
- Use `.extend(Map<String, AckSchema> newProperties)` to add more properties.
- Use `.partial()` to make all properties optional.
- Use `.strict()` to disallow additional properties.
- Use `.passthrough()` to allow and preserve additional properties not defined
  in the schema.
- Use `.merge(ObjectSchema other)` to combine with another object schema.

## `deepUnmodifiableJsonMap(JsonMap value)`

Creates a detached, recursively unmodifiable snapshot of nested maps, lists,
and sets. It does not validate JSON shape or values. Use it in hand-written
constructors that store dynamic JSON maps and need stable equality and hashes.
## `SchemaResult<T>`

Object returned by `safeParse()`. See [Error Handling](../core-concepts/error-handling.mdx).

- `bool isOk`: `true` if validation succeeded.
- `bool isFail`: `true` if validation failed.
- `T? getOrThrow()`: Returns the validated value (which can be `null` for a nullable schema), or throws `AckException` on failure.
- `T? getOrNull()`: Returns the validated value, or `null` on failure.
- `SchemaError getError()`: Returns the validation error; only valid when `isFail` is `true`.
- `T? getOrElse(T? Function() orElse)`: Returns the validated value, or calls `orElse` on failure.
- `SchemaResult<R> map<R>(R Function(T?) transform)`: Maps the successful value to a new result type; propagates failures unchanged.
- `R match<R>({required R Function(T?) onOk, required R Function(SchemaError) onFail})`: Pattern-matches on success or failure.
- `void ifOk(void Function(T?) action)`: Executes `action` only when the result is successful.
- `void ifFail(void Function(SchemaError) action)`: Executes `action` only when the result is a failure.

## `SchemaError` (and subclasses)

Represents a validation failure. See [Error handling](../core-concepts/error-handling.mdx).

- `String message`: Human-readable error message.
- `SchemaContext context`: Context about where the error occurred.

**Subclasses:**
- `TypeMismatchError`: The input has the wrong Dart runtime type.
- `SchemaConstraintsError`: One or more constraint violations.
- `SchemaNestedError`: Validation failures in nested objects or arrays.
- `SchemaValidationError`: Custom refinement failures.
- `SchemaTransformError`: Decode/transform callback failures.
- `SchemaEncodeError`: Encode-path failures (non-nullable null, one-way transform, encoder threw, etc.).

## `Constraint<T>`

Base class for custom validation rules. See [Custom validation](../guides/custom-validation.mdx).

- `String constraintKey`: Unique identifier for the constraint.
- `String description`: Human-readable description.
- `Map<String, Object?> toMap()`: Serializes the constraint for debugging.

## `Validator<T>` (mixin)

Validation behavior mixin used with `Constraint<T>`.

- `bool isValid(T value)`: Returns `true` when the value passes validation.
- `String buildMessage(T value)`: Builds the validation failure message.
- `ConstraintError? validate(T value)`: Validates a value and returns an error if invalid.

## Additional schema types

Ack ships with a broad set of schema factories beyond what is listed here.
See [Schema types](../core-concepts/schemas.mdx) for the full catalogue,
including `Ack.date()`, `Ack.literal()`, and list/object combinators.

### `Ack.discriminated(...)`

Schema for polymorphic validation based on a string discriminator property.

- Branch schemas normally omit the discriminator field.
- The boundary payload must still contain the discriminator key.
- If a branch schema includes the discriminator field, it must be
  `Ack.literal(...)` matching the branch key or `Ack.enumString(...)`
  containing it. Broad, transformed, refined, or otherwise restrictive
  discriminator fields are rejected.
- Exported and generated schemas expose each branch discriminator as an exact
  literal.
- Generated subtype `parse()` and `safeParse()` methods validate through the
  union's effective branch.

### `Ack.lazy(...)`

Schema reference for recursive object graphs.

- Created using `Ack.lazy<Boundary, Runtime>(name, builder)`. `maxDepth`
  defaults to `100` and must be at least `1`; pass it explicitly only to
  override the default.
- The builder is resolved once and memoized.
- Exceeding `maxDepth` returns a validation failure during parsing, runtime
  validation, or encoding.
- `toJsonSchema()` and `toSchemaModel()` export Draft-7 `definitions` / `$ref`
  entries using the lazy `name`. The runtime-only depth limit cannot be
  represented by `$ref`, so exported schema models warn that it was omitted.
- Bare or wrapped lazy schemas cannot be used as discriminated-union branches
  because the branch discriminator must be analyzable at construction time.

## Code generation annotations

Use the [`ack_generator`](https://pub.dev/packages/ack_generator) builders in
either direction with one annotation, `@Schemable()`: on a top-level schema it
generates a model, and on a class it generates a schema. `package:ack/ack.dart`
exports `@Schemable` and the model annotations (`@AckField`, `@Optional`,
`@Required`, `@NotNull`). Constraint annotations come from
`package:ack/annotations.dart`, imported with a prefix (`as ack`, then
`@ack.Email()`) where a name clashes with another type, and `@Uri()` /
`@DateTime()` from `package:ack/format_annotations.dart`. `@AckInfer()` and
`@AckModel()` are
deprecated spellings that keep working until 2.0.0. `AckSchema` is the
runtime schema type; there is no `@AckSchema()` annotation. After adding matching
`.ack.dart` and `.ack.g.dart` part directives, run:

```bash
dart run build_runner build
```

### `@Schemable()` on a top-level schema

**Target**: Schema variables and getters

**Generates**: An immutable model class backed by the existing schema

Annotate a top-level schema variable or getter. The schema stays in your source file and remains responsible for validation and codecs.

**Supported schema types:**
- `Ack.object({...})` → immutable object models
- Primitives: `Ack.string()`, `Ack.integer()`, `Ack.double()`, `Ack.boolean()`
- Collections: `Ack.list(...)`, `Ack.map(...)` fields, and codec-backed sets
  and maps
- JSON values: `Ack.any()` fields as `Object` / `Object?`
- Enums: `Ack.literal()`, `Ack.enumString()`, `Ack.enumValues()`
- Discriminated unions: `Ack.discriminated(...)`

**Unsupported:** nullable roots, one-way transforms, `Ack.any()` and
`Ack.map()` roots, `Ack.anyOf()`, bare `Ack.instance<T>()`, and anonymous
inline objects

`name` sets the exact model class name; the class-only options are rejected
here. For `Ack.discriminated(...)` constraints, see
[Model Code Generation](../core-concepts/typesafe-schemas.mdx#schema-first-unions).

**Example:**
```dart
import 'package:ack/ack.dart';

part 'user.ack.dart';
part 'user.ack.g.dart';

@Schemable()
final userSchema = Ack.object({
  'name': Ack.string(),
  'email': Ack.string().email(),
});

// Generated:
// - final class User { ... }
// - The schema variable remains unchanged

// Usage:
final user = User.parse({'name': 'Alice', 'email': 'alice@example.com'});
print(user.name);  // Type-safe String access
print(user.email); // Type-safe String access
print(user.toJson());
```

### `@Schemable()` on a class

Concrete models and union branches must be `final class` declarations with
final stored fields. Annotated sealed union bases remain supported. Parsed
collections and captured extras are recursively unmodifiable; hand-written
constructors remain responsible for defensively copying direct inputs.

**Target**: Public, constructable classes

**Generates**: A public `<ClassName>Schema` facade backed by a private raw
`wireSchema` and typed codec, plus the `_$ClassAck` mixin for `toJson`,
`safeToJson`, `copyWith`, and deep collection-aware `==` / `hashCode` /
`toString`

Ack infers schema fields from constructor-backed fields. Required parameters,
nullable types, optional parameters, and constructor defaults determine field
presence. Use `caseStyle` for model-wide JSON naming and `@JsonKey(name: ...)`
on the field for an override; `JsonKey` comes from
`package:json_annotation/json_annotation.dart`, which the app then depends on.
Other `JsonKey` options, constructor-parameter placement, and `JsonConverter`
annotations are rejected. Instantiable models and implicit union branches must
apply the generated mixin. In `copyWith`, omission keeps the current value and
explicit `null` clears a nullable field. `copyWith` returns a typed interface,
so a wrongly typed argument is a compile-time error.

**Inferred field types:** strings, booleans, numeric types, `DateTime`, `Uri`,
`Duration`, enums, nested lists, and sets

**Constraint annotations:**

- numeric: `@Min`, `@Max`, `@MultipleOf`, `@Positive`, `@Negative`
- strings: `@MinLength`, `@MaxLength`, `@Matches`, `@Email`, `@NotEmpty`
- string formats: `@Url`, `@Uuid`, `@Date`; `@Uri()` and `@DateTime()` from
  `package:ack/format_annotations.dart` imported with a prefix
  (`@formats.Uri()`)
- collections: `@MinItems`, `@MaxItems`, `@UniqueItems`

`@Matches` replaces `@Pattern`, which remains a deprecated alias in
`package:ack/annotations.dart` only.

**Type-owned schemas:** a field whose type declares a static `schema` resolves
to it with no annotation, also per `List` / `Set` item and as a `Map` value. A
generic type declares `static AckSchema<B, T<A>> schema<A>()`, which is called
with the field's type arguments; if it declares one positional parameter typed
`AckSchema<Object, A>` per type parameter `A`, it also receives each type argument's inferred
schema. An enum's static `schema` wins over `Ack.enumValues`. The schema must
produce the field type.

Use `@AckField(schema: ...)` for a type you cannot edit; the non-generic
function must produce the field type. A no-op `@AckField()` is rejected. A
sealed base annotated with `@Schemable(discriminatorKey: ...)` generates a discriminated union from its
same-library concrete branches. Unknown properties use
`unknownProperties: AckUnknownPropertyPolicy.<policy>` (`reject` by default).
Raw `Ack.object(..., additionalProperties: true)` schemas preserve extras.
Class projection is a later step: `discard` is only for tolerant, read-only
consumers, while models that round-trip unknown keys must use `capture` and may
select the target map with `captureField`.

```dart
import 'package:ack/ack.dart';
import 'package:ack/annotations.dart';

@Schemable(caseStyle: AckCaseStyle.snake)
final class Account with _$AccountAck {
  const Account({required this.displayName, this.role = 'member'});

  @MinLength(2)
  final String displayName;
  final String role;

  static final fromJson = AccountSchema.fromJson;
}

final account = AccountSchema.parse({'display_name': 'Ada'});
print(account.toJson());
print(AccountSchema.toJsonSchema());
```

The facade exposes `schema`, `wireSchema`, `parse`, `safeParse`, `fromJson`,
`encode`, `safeEncode`, `toJsonSchema`, and `toSchemaModel`. The backing
`_accountSchema` is library-private and no public lower-camel alias is
generated. Set `schemaName: 'WireAccountSchema'` to choose the exact facade
class name.

Nested class-first fields compose through `AddressSchema.schema`. Imports with
combinators must expose both names (`show Address, AddressSchema`). A
schema-first `@Schemable()` declaration may also compose the facade explicitly,
and class-first models can use schema-first generated model types on a clean
build.

A class-first `@Schemable()` cannot share a class with `@JsonSerializable()`,
and `name` is rejected on a class. Class-first value roots, automatic recursive
graphs, and undiscriminated `anyOf` shapes
are unsupported; use a schema-first `@Schemable()` schema and named `Ack.lazy` for those
cases. See
[Model Code Generation](../core-concepts/typesafe-schemas.mdx) for the complete
tutorial and comparison.

### `EnumSchema<T>`

Schema for mapping enum `.name` strings at the boundary to typed enum values at
runtime. Created using `Ack.enumValues(List<T> values)` where `T extends Enum`;
`encode` and `safeEncode` map typed enum values back to their names.

### `AnyOfSchema`

Schema for union types; the value must match one of several schemas. Created using `Ack.anyOf(List<AckSchema> schemas)`.

### `DiscriminatedObjectSchema`

Schema for polymorphic validation based on a discriminator field.

- Created using `Ack.discriminated<T extends Object>(...)` with
  `discriminatorKey` and `schemas`.
- `effectiveBranch(String discriminatorValue)`: Returns the branch schema with
  the discriminator injected as the exact branch literal. Generated subtypes use
  this to validate a specific branch.

### `AckSchemaModel`

Canonical export model for Ack schemas.

- Created by `schema.toSchemaModel()`.
- Represents the boundary/export shape, JSON-compatible defaults, discriminator metadata, target-independent constraints, and export warnings.
- `schema.toSchemaModel().toJsonSchema()` returns the same Draft-7 map as `schema.toJsonSchema()`.
- Adapters that need a JSON map should call `schema.toJsonSchema()`; adapters for non-JSON targets should convert from `AckSchemaModel` rather than traversing `AckSchema` subclasses directly.

### `AnySchema`

Accepts any non-null JSON-safe value without further validation.

- Created by `Ack.any()`.
- Useful for dynamic payloads or pass-through metadata.
- Parsing returns a detached, recursively unmodifiable snapshot of the input;
  encoding returns the runtime value unchanged after validating it.

### `MapSchema<ValueBoundary, ValueRuntime>`

Schema for a JSON object with arbitrary string keys whose values all match
`valueSchema`.

- Created by `Ack.map(valueSchema)`.
- Boundary and runtime types are `Map<String, ValueBoundary?>` and
  `Map<String, ValueRuntime?>`. Nullability is a runtime flag in Ack, so the
  static value type is nullable; a value is `null` only when `valueSchema` is
  nullable.
- Parse runs `valueSchema` on every value and encode on every non-null value,
  including codecs; errors report the value's key path.
- Exported to JSON Schema as
  `{"type": "object", "additionalProperties": <value schema>}`.

### `CodecSchema<Boundary, Runtime>`

Schema that decodes boundary values into runtime values and encodes runtime
values back to the boundary representation. Use `encode` / `safeEncode` for the
reverse direction. See [Codecs](../core-concepts/codecs.mdx).

- `schema.transform<R>(R Function(Runtime) transformer)`: one-way transform
  (parse only; `encode` fails with a one-way error).
- `schema.codec<R>({required R Function(Runtime) decode, required Runtime Function(R) encode, AckSchema<dynamic, R>? output})`:
  bidirectional codec; the optional `output` schema validates the runtime value.
- `Ack.codec<Boundary, InputRuntime, Runtime>({required input, required decode, required encode, output})`:
  builds a codec from an `input` schema.

**Built-in codecs:**

- `Ack.date()` → `CodecSchema<String, DateTime>` (ISO `YYYY-MM-DD` ↔ local-midnight `DateTime`)
- `Ack.datetime()` → `CodecSchema<String, DateTime>` (ISO 8601 ↔ UTC
  `DateTime`; leap-second strings are rejected because Dart cannot represent
  them)
- `Ack.uri()` → `CodecSchema<String, Uri>` (absolute URI string ↔ `Uri`)
- `Ack.duration()` → `CodecSchema<int, Duration>` (milliseconds ↔ `Duration`)
- `Ack.enumCodec(List<T> values)` → `CodecSchema<String, T>` (enum `.name` ↔ enum value)

### Optional schemas

Every schema can be marked optional via the `optional({bool value = true})` fluent API.

- `schema.optional()` sets `isOptional` to `true` without wrapping the schema.
- `schema.optional(value: false)` clears the optional flag.
- Optional affects object-field presence only; combine with `.nullable()` to also allow explicit `null`.

*See the [Schema types](../core-concepts/schemas.mdx) guide for detailed usage and examples.*
