diff --git a/packages/typeforge/.gitignore b/packages/typeforge/.gitignore
new file mode 100644
index 0000000..3cceda5
--- /dev/null
+++ b/packages/typeforge/.gitignore
@@ -0,0 +1,7 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
+
+# Avoid committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
diff --git a/packages/typeforge/CHANGELOG.md b/packages/typeforge/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/packages/typeforge/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/packages/typeforge/README.md b/packages/typeforge/README.md
new file mode 100644
index 0000000..03e8764
--- /dev/null
+++ b/packages/typeforge/README.md
@@ -0,0 +1,147 @@
+
+
+# protevus_typeforge
+
+[![Build Status](https://travis-ci.org/conduit.dart/dart-codable.svg?branch=master)](https://travis-ci.org/conduit.dart/dart-codable)
+
+A library for encoding and decoding dynamic data into Dart objects.
+
+## Basic Usage
+
+Data objects extend `Coding`:
+
+```dart
+class Person extends Coding {
+ String name;
+
+ @override
+ void decode(KeyedArchive object) {
+ // must call super
+ super.decode(object);
+
+ name = object.decode("name");
+ }
+
+ @override
+ void encode(KeyedArchive object) {
+ object.encode("name", name);
+ }
+}
+```
+
+An object that extends `Coding` can be read from JSON:
+
+```dart
+final json = json.decode(...);
+final archive = KeyedArchive.unarchive(json);
+final person = Person()..decode(archive);
+```
+
+Objects that extend `Coding` may also be written to JSON:
+
+```dart
+final person = Person()..name = "Bob";
+final archive = KeyedArchive.archive(person);
+final json = json.encode(archive);
+```
+
+`Coding` objects can encode or decode other `Coding` objects, including lists of `Coding` objects and maps where `Coding` objects are values. You must provide a closure that instantiates the `Coding` object being decoded.
+
+```dart
+class Team extends Coding {
+
+ List members;
+ Person manager;
+
+ @override
+ void decode(KeyedArchive object) {
+ super.decode(object); // must call super
+
+ members = object.decodeObjects("members", () => Person());
+ manager = object.decodeObject("manager", () => Person());
+ }
+
+ @override
+ void encode(KeyedArchive object) {
+ object.encodeObject("manager", manager);
+ object.encodeObjects("members", members);
+ }
+}
+```
+
+## Dynamic Type Casting
+
+Types with primitive type arguments (e.g., `List` or `Map`) are a particular pain point when decoding. Override `castMap` in `Coding` to perform type coercion.
+You must import `package:protevus_typeforge/cast.dart as cast` and prefix type names with `cast`.
+
+```dart
+import 'package:protevus_typeforge/cast.dart' as cast;
+class Container extends Coding {
+ List things;
+
+ @override
+ Map> get castMap => {
+ "things": cast.List(cast.String)
+ };
+
+ @override
+ void decode(KeyedArchive object) {
+ super.decode(object);
+
+ things = object.decode("things");
+ }
+
+ @override
+ void encode(KeyedArchive object) {
+ object.encode("things", things);
+ }
+}
+
+```
+
+
+## Document References
+
+`Coding` objects may be referred to multiple times in a document without duplicating their structure. An object is referenced with the `$key` key.
+For example, consider the following JSON:
+
+```json
+{
+ "components": {
+ "thing": {
+ "name": "The Thing"
+ }
+ },
+ "data": {
+ "$ref": "#/components/thing"
+ }
+}
+```
+
+In the above, the decoded value of `data` inherits all properties from `/components/thing`:
+
+```json
+{
+ "$ref": "#/components/thing",
+ "name": "The Thing"
+}
+```
+
+You may create references in your in-memory data structures through the `Coding.referenceURI`.
+
+```dart
+final person = Person()..referenceURI = Uri(path: "/teams/engineering/manager");
+```
+
+The above person is encoded as:
+
+```json
+{
+ "$ref": "#/teams/engineering/manager"
+}
+```
+
+You may have cyclical references.
+
+See the specification for [JSON Schema](http://json-schema.org) and the `$ref` keyword for more details.
+
diff --git a/packages/typeforge/analysis_options.yaml b/packages/typeforge/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/packages/typeforge/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+# rules:
+# - camel_case_types
+
+# analyzer:
+# exclude:
+# - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/packages/typeforge/lib/cast.dart b/packages/typeforge/lib/cast.dart
new file mode 100644
index 0000000..7433900
--- /dev/null
+++ b/packages/typeforge/lib/cast.dart
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/// The `cast` library provides a collection of utilities for type casting and conversion in Dart.
+///
+/// This library exports several modules that offer different casting functionalities:
+/// - `base_cast.dart`: Contains base casting operations.
+/// - `primitive_cast.dart`: Provides casting methods for primitive data types.
+/// - `collection_cast.dart`: Offers casting utilities for collections.
+/// - `special_cast.dart`: Includes casting operations for special data types.
+/// - `utility_cast.dart`: Contains additional utility functions for casting.
+/// - `constants.dart`: Defines constants used across the casting operations.
+///
+/// These modules collectively provide a comprehensive set of tools for handling
+/// various type conversion scenarios in Dart applications.
+library cast;
+
+export 'src/base_cast.dart';
+export 'src/primitive_cast.dart';
+export 'src/collection_cast.dart';
+export 'src/special_cast.dart';
+export 'src/utility_cast.dart';
+export 'src/constants.dart';
diff --git a/packages/typeforge/lib/codable.dart b/packages/typeforge/lib/codable.dart
new file mode 100644
index 0000000..1c15775
--- /dev/null
+++ b/packages/typeforge/lib/codable.dart
@@ -0,0 +1,27 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/// The `codable` library provides functionality for encoding and decoding objects.
+///
+/// This library exports several core components:
+/// - `referenceable.dart`: Defines objects that can be referenced.
+/// - `coding.dart`: Contains encoding and decoding interfaces.
+/// - `keyed_archive.dart`: Implements a key-value storage for encoded objects.
+/// - `list_archive.dart`: Implements a list-based storage for encoded objects.
+/// - `reference_resolver.dart`: Handles resolving references within encoded data.
+///
+/// These components work together to provide a robust system for object serialization
+/// and deserialization, supporting both simple and complex data structures.
+library codable;
+
+export 'src/referenceable.dart';
+export 'src/coding.dart';
+export 'src/keyed_archive.dart';
+export 'src/list_archive.dart';
+export 'src/reference_resolver.dart';
diff --git a/packages/typeforge/lib/src/base_cast.dart b/packages/typeforge/lib/src/base_cast.dart
new file mode 100644
index 0000000..93d1729
--- /dev/null
+++ b/packages/typeforge/lib/src/base_cast.dart
@@ -0,0 +1,118 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'dart:core' as core;
+import 'dart:core' hide Map, String, int;
+
+/// Represents an exception thrown when a cast operation fails.
+///
+/// This class is used to provide detailed information about the context
+/// and reason for a failed cast operation.
+///
+/// [context] represents the location or scope where the cast failed.
+/// [key] is an optional identifier for the specific element that failed to cast.
+/// [message] provides additional details about the failure.
+class FailedCast implements core.Exception {
+ dynamic context;
+ dynamic key;
+ core.String message;
+ FailedCast(this.context, this.key, this.message);
+ @override
+ core.String toString() {
+ if (key == null) {
+ return "Failed cast at $context: $message";
+ }
+ return "Failed cast at $context $key: $message";
+ }
+}
+
+/// An abstract class representing a type cast operation.
+///
+/// This class defines the structure for implementing type casting
+/// from dynamic types to a specific type T.
+///
+/// The [cast] method is the public API for performing the cast,
+/// while [safeCast] is the internal implementation that can be
+/// overridden by subclasses to define specific casting behavior.
+///
+/// Usage:
+/// ```dart
+/// class MyCustomCast extends Cast {
+/// @override
+/// MyType _cast(dynamic from, String context, dynamic key) {
+/// // Custom casting logic here
+/// }
+/// }
+/// ```
+abstract class Cast {
+ /// Constructs a new [Cast] instance.
+ ///
+ /// This constructor is declared as `const` to allow for compile-time
+ /// constant instances of [Cast] subclasses. This can be beneficial for
+ /// performance and memory usage in certain scenarios.
+ const Cast();
+
+ /// Performs a safe cast operation from a dynamic type to type T.
+ ///
+ /// This method wraps the [safeCast] method with additional error handling:
+ /// - If a [FailedCast] exception is thrown, it's rethrown as-is.
+ /// - For any other exception, it's caught and wrapped in a new [FailedCast] exception.
+ ///
+ /// Parameters:
+ /// [from]: The value to be cast.
+ /// [context]: A string describing the context where the cast is performed.
+ /// [key]: An optional identifier for the specific element being cast.
+ ///
+ /// Returns:
+ /// The cast value of type T.
+ ///
+ /// Throws:
+ /// [FailedCast]: If the cast fails, either from [safeCast] or from wrapping another exception.
+ T _safeCast(dynamic from, core.String context, dynamic key) {
+ try {
+ return safeCast(from, context, key);
+ } on FailedCast {
+ rethrow;
+ } catch (e) {
+ throw FailedCast(context, key, e.toString());
+ }
+ }
+
+ /// Performs a safe cast operation from a dynamic type to type T.
+ ///
+ /// This method is a convenience wrapper around [_safeCast] that provides
+ /// a default context of "toplevel" and a null key.
+ ///
+ /// Parameters:
+ /// [from]: The value to be cast.
+ ///
+ /// Returns:
+ /// The cast value of type T.
+ ///
+ /// Throws:
+ /// [FailedCast]: If the cast operation fails.
+ T cast(dynamic from) => _safeCast(from, "toplevel", null);
+
+ /// Performs a safe cast operation from a dynamic type to type T.
+ ///
+ /// This method should be implemented by subclasses to define the specific
+ /// casting behavior for the type T.
+ ///
+ /// Parameters:
+ /// [from]: The value to be cast.
+ /// [context]: A string describing the context where the cast is performed.
+ /// [key]: An optional identifier for the specific element being cast.
+ ///
+ /// Returns:
+ /// The cast value of type T.
+ ///
+ /// Throws:
+ /// [FailedCast]: If the cast operation fails.
+ T safeCast(dynamic from, core.String context, dynamic key);
+}
diff --git a/packages/typeforge/lib/src/coding.dart b/packages/typeforge/lib/src/coding.dart
new file mode 100644
index 0000000..2c2cc69
--- /dev/null
+++ b/packages/typeforge/lib/src/coding.dart
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'package:meta/meta.dart';
+import 'package:protevus_typeforge/cast.dart' as cast;
+import 'package:protevus_typeforge/codable.dart';
+
+/// Abstract class representing a coding mechanism.
+///
+/// This class provides a framework for encoding and decoding objects.
+/// It includes a [referenceURI] property and a [castMap] getter for type casting.
+///
+/// The [decode] method is used to populate the object's properties from a [KeyedArchive].
+/// It must be called by subclasses, hence the @mustCallSuper annotation.
+///
+/// The [encode] method is abstract and must be implemented by subclasses to define
+/// how the object should be encoded into a [KeyedArchive].
+abstract class Coding {
+ /// The URI reference for this coding object.
+ ///
+ /// This property holds a [Uri] that can be used as a reference or identifier
+ /// for the coded object. It may represent the location or source of the data,
+ /// or serve as a unique identifier within a larger system.
+ ///
+ /// The [referenceURI] is typically set during decoding and can be accessed
+ /// or modified as needed. It may be null if no reference is available or required.
+ Uri? referenceURI;
+
+ /// A map of property names to their corresponding cast functions.
+ ///
+ /// This getter returns a [Map] where the keys are strings representing
+ /// property names, and the values are [cast.Cast] functions for those properties.
+ /// The cast functions are used to convert decoded values to their appropriate types.
+ ///
+ /// By default, this getter returns `null`, indicating that no custom casting
+ /// is required. Subclasses can override this getter to provide specific
+ /// casting behavior for their properties.
+ ///
+ /// Returns `null` if no custom casting is needed, or a [Map] of property
+ /// names to cast functions if custom casting is required.
+ Map>? get castMap => null;
+
+ /// Decodes the object from a [KeyedArchive].
+ ///
+ /// This method is responsible for populating the object's properties from the
+ /// provided [KeyedArchive]. It performs two main actions:
+ ///
+ /// 1. Sets the [referenceURI] of this object to the [referenceURI] of the
+ /// provided [KeyedArchive].
+ /// 2. Applies any necessary type casting to the values in the [KeyedArchive]
+ /// using the [castMap] defined for this object.
+ ///
+ /// This method is marked with [@mustCallSuper], indicating that subclasses
+ /// overriding this method must call the superclass implementation.
+ ///
+ /// [object] The [KeyedArchive] containing the encoded data to be decoded.
+ @mustCallSuper
+ void decode(KeyedArchive object) {
+ referenceURI = object.referenceURI;
+ object.castValues(castMap);
+ }
+
+ /// Encodes the object into a [KeyedArchive].
+ ///
+ /// This abstract method must be implemented by subclasses to define
+ /// how the object should be encoded into a [KeyedArchive]. The implementation
+ /// should write all relevant properties of the object to the provided [object].
+ ///
+ /// [object] The [KeyedArchive] to which the object's data should be encoded.
+ ///
+ /// Note that the [referenceURI] of the object is not automatically written
+ /// to the [KeyedArchive]. See note in [KeyedArchive._encodedObject].
+ void encode(KeyedArchive object);
+}
diff --git a/packages/typeforge/lib/src/collection_cast.dart b/packages/typeforge/lib/src/collection_cast.dart
new file mode 100644
index 0000000..ced998b
--- /dev/null
+++ b/packages/typeforge/lib/src/collection_cast.dart
@@ -0,0 +1,129 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'dart:core' as core;
+import 'dart:core' hide Map, String, int;
+import 'package:protevus_typeforge/cast.dart';
+
+/// A cast operation for converting dynamic values to [core.Map].
+///
+/// This class extends [Cast>] and implements the [safeCast] method
+/// to perform type checking and conversion to [core.Map].
+///
+/// The class uses two separate [Cast] instances:
+/// - [_key] for casting the keys of the input map to type K
+/// - [_value] for casting the values of the input map to type V
+///
+/// The [safeCast] method checks if the input [from] is already a [core.Map].
+/// If it is, it creates a new map, casting each key-value pair using the
+/// respective [_key] and [_value] casts. If not, it throws a [FailedCast]
+/// exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final mapCast = Map(StringCast(), IntCast());
+/// final result = mapCast.cast({"a": 1, "b": 2}); // Returns Map
+/// mapCast.cast("not a map"); // Throws FailedCast
+/// ```
+class Map extends Cast> {
+ final Cast _key;
+ final Cast _value;
+ const Map(Cast key, Cast value)
+ : _key = key,
+ _value = value;
+ @override
+ core.Map safeCast(dynamic from, core.String context, dynamic key) {
+ if (from is core.Map) {
+ final result = {};
+ for (final key in from.keys) {
+ final newKey = _key.safeCast(key, "map entry", key);
+ result[newKey] = _value.safeCast(from[key], "map entry", key);
+ }
+ return result;
+ }
+ return throw FailedCast(context, key, "not a map");
+ }
+}
+
+/// A cast operation for converting dynamic values to [core.Map].
+///
+/// This class extends [Cast>] and implements the [safeCast] method
+/// to perform type checking and conversion to [core.Map].
+///
+/// The class uses a [Cast] instance [_value] for casting the values of the input map to type V.
+///
+/// The [safeCast] method checks if the input [from] is already a [core.Map].
+/// If it is, it creates a new map with [core.String] keys and values of type V,
+/// casting each value using the [_value] cast. If not, it throws a [FailedCast]
+/// exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final stringMapCast = StringMap(IntCast());
+/// final result = stringMapCast.cast({"a": 1, "b": 2}); // Returns Map
+/// stringMapCast.cast("not a map"); // Throws FailedCast
+/// ```
+class StringMap extends Cast> {
+ final Cast _value;
+ const StringMap(Cast value) : _value = value;
+ @override
+ core.Map safeCast(
+ dynamic from,
+ core.String context,
+ dynamic key,
+ ) {
+ if (from is core.Map) {
+ final result = {};
+ for (final core.String key in from.keys as core.Iterable) {
+ result[key] = _value.safeCast(from[key], "map entry", key);
+ }
+ return result;
+ }
+ return throw FailedCast(context, key, "not a map");
+ }
+}
+
+/// A cast operation for converting dynamic values to [core.List].
+///
+/// This class extends [Cast>] and implements the [safeCast] method
+/// to perform type checking and conversion to [core.List].
+///
+/// The class uses a [Cast] instance [_entry] for casting each element of the input list to type E.
+///
+/// The [safeCast] method checks if the input [from] is already a [core.List].
+/// If it is, it creates a new list of nullable E elements, casting each non-null
+/// element using the [_entry] cast and preserving null values. If not, it throws
+/// a [FailedCast] exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final listCast = List(IntCast());
+/// final result = listCast.cast([1, 2, null, 3]); // Returns List
+/// listCast.cast("not a list"); // Throws FailedCast
+/// ```
+class List extends Cast> {
+ final Cast _entry;
+ const List(Cast entry) : _entry = entry;
+ @override
+ core.List safeCast(dynamic from, core.String context, dynamic key) {
+ if (from is core.List) {
+ final length = from.length;
+ final result = core.List.filled(length, null);
+ for (core.int i = 0; i < length; ++i) {
+ if (from[i] != null) {
+ result[i] = _entry.safeCast(from[i], "list entry", i);
+ } else {
+ result[i] = null;
+ }
+ }
+ return result;
+ }
+ return throw FailedCast(context, key, "not a list");
+ }
+}
diff --git a/packages/typeforge/lib/src/constants.dart b/packages/typeforge/lib/src/constants.dart
new file mode 100644
index 0000000..b0cd0af
--- /dev/null
+++ b/packages/typeforge/lib/src/constants.dart
@@ -0,0 +1,74 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'package:protevus_typeforge/cast.dart';
+
+/// A constant instance of [AnyCast] that can be used for casting any dynamic value.
+///
+/// This constant provides a convenient way to use the [AnyCast] functionality
+/// without needing to create a new instance each time. It can be used in situations
+/// where type-checking is not required, and you want to allow any type to pass through.
+///
+/// Example usage:
+/// ```dart
+/// final result = any.cast(someValue); // Returns someValue unchanged, regardless of its type
+/// ```
+const any = AnyCast();
+
+/// A constant instance of [BoolCast] that can be used for casting dynamic values to [core.bool].
+///
+/// This constant provides a convenient way to use the [BoolCast] functionality
+/// without needing to create a new instance each time. It can be used to perform
+/// boolean type checking and casting operations.
+///
+/// Example usage:
+/// ```dart
+/// final result = bool.cast(true); // Returns true
+/// bool.cast("not a bool"); // Throws FailedCast
+/// ```
+const bool = BoolCast();
+
+/// A constant instance of [IntCast] that can be used for casting dynamic values to [core.int].
+///
+/// This constant provides a convenient way to use the [IntCast] functionality
+/// without needing to create a new instance each time. It can be used to perform
+/// integer type checking and casting operations.
+///
+/// Example usage:
+/// ```dart
+/// final result = int.cast(42); // Returns 42
+/// int.cast("not an int"); // Throws FailedCast
+/// ```
+const int = IntCast();
+
+/// A constant instance of [DoubleCast] that can be used for casting dynamic values to [core.double].
+///
+/// This constant provides a convenient way to use the [DoubleCast] functionality
+/// without needing to create a new instance each time. It can be used to perform
+/// double type checking and casting operations.
+///
+/// Example usage:
+/// ```dart
+/// final result = double.cast(3.14); // Returns 3.14
+/// double.cast("not a double"); // Throws FailedCast
+/// ```
+const double = DoubleCast();
+
+/// A constant instance of [StringCast] that can be used for casting dynamic values to [core.String].
+///
+/// This constant provides a convenient way to use the [StringCast] functionality
+/// without needing to create a new instance each time. It can be used to perform
+/// string type checking and casting operations.
+///
+/// Example usage:
+/// ```dart
+/// final result = string.cast("Hello"); // Returns "Hello"
+/// string.cast(42); // Throws FailedCast
+/// ```
+const string = StringCast();
diff --git a/packages/typeforge/lib/src/keyed_archive.dart b/packages/typeforge/lib/src/keyed_archive.dart
new file mode 100644
index 0000000..8fef8a1
--- /dev/null
+++ b/packages/typeforge/lib/src/keyed_archive.dart
@@ -0,0 +1,580 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'dart:collection';
+import 'package:protevus_typeforge/cast.dart' as cast;
+import 'package:protevus_typeforge/codable.dart';
+
+/// A container for a dynamic data object that can be decoded into [Coding] objects.
+///
+/// A [KeyedArchive] is a [Map], but it provides additional behavior for decoding [Coding] objects
+/// and managing JSON Schema references ($ref) through methods like [decode], [decodeObject], etc.
+///
+/// You create a [KeyedArchive] by invoking [KeyedArchive.unarchive] and passing data decoded from a
+/// serialization format like JSON and YAML. A [KeyedArchive] is then provided as an argument to
+/// a [Coding] subclass' [Coding.decode] method.
+///
+/// final json = json.decode(...);
+/// final archive = KeyedArchive.unarchive(json);
+/// final person = Person()..decode(archive);
+///
+/// You may also create [KeyedArchive]s from [Coding] objects so that they can be serialized.
+///
+/// final person = Person()..name = "Bob";
+/// final archive = KeyedArchive.archive(person);
+/// final json = json.encode(archive);
+///
+/// This class extends [Object] and mixes in [MapBase], allowing it to be used as a Map.
+/// It also implements [Referenceable], providing functionality for handling references within the archive.
+///
+/// The constructor is not typically used directly; instead, use the [KeyedArchive.unarchive]
+/// or [KeyedArchive.archive] methods to create instances of [KeyedArchive].
+class KeyedArchive extends Object
+ with MapBase
+ implements Referenceable {
+ /// Use [unarchive] instead.
+ KeyedArchive(this._map) {
+ _recode();
+ }
+
+ /// Unarchives [data] into a [KeyedArchive] that can be used by [Coding.decode] to deserialize objects.
+ ///
+ /// Each [Map] in [data] (including [data] itself) is converted to a [KeyedArchive].
+ /// Each [List] in [data] is converted to a [ListArchive]. These conversions occur for deeply nested maps
+ /// and lists.
+ ///
+ /// If [allowReferences] is true, JSON Schema references will be traversed and decoded objects
+ /// will contain values from the referenced object. This flag defaults to false.
+ KeyedArchive.unarchive(this._map, {bool allowReferences = false}) {
+ _recode();
+ if (allowReferences) {
+ resolveOrThrow(ReferenceResolver(this));
+ }
+ }
+
+ /// Archives a [Coding] object into a [Map] that can be serialized into formats like JSON or YAML.
+ ///
+ /// Note that the return value of this method, as well as all other [Map] and [List] objects
+ /// embedded in the return value, are instances of [KeyedArchive] and [ListArchive]. These types
+ /// implement [Map] and [List], respectively.
+ ///
+ /// If [allowReferences] is true, JSON Schema references in the emitted document will be validated.
+ /// Defaults to false.
+ static Map archive(
+ Coding root, {
+ bool allowReferences = false,
+ }) {
+ final archive = KeyedArchive({});
+ root.encode(archive);
+ if (allowReferences) {
+ archive.resolveOrThrow(ReferenceResolver(archive));
+ }
+ return archive.toPrimitive();
+ }
+
+ /// Private constructor that creates an empty [KeyedArchive].
+ ///
+ /// This constructor initializes the internal [_map] with an empty Map.
+ /// It's intended for internal use within the [KeyedArchive] class.
+ KeyedArchive._empty() : _map = {};
+
+ /// A reference to another object in the same document.
+ ///
+ /// This property represents a URI reference to another object within the same document.
+ /// It is used to establish relationships between objects in a hierarchical structure.
+ ///
+ /// Assign values to this property using the default [Uri] constructor and its path argument.
+ /// This property is serialized as a [Uri] fragment, e.g. `#/components/all`.
+ ///
+ /// Example:
+ ///
+ /// final object = new MyObject()
+ /// ..referenceURI = Uri(path: "/other/object");
+ /// archive.encodeObject("object", object);
+ ///
+ Uri? referenceURI;
+
+ /// The internal map that stores the key-value pairs of this [KeyedArchive].
+ ///
+ /// This map is used to store the actual data of the archive. It is of type
+ /// `Map` to allow for flexibility in the types of values
+ /// that can be stored. The keys are always strings, representing the names
+ /// of the properties, while the values can be of any type.
+ ///
+ /// This map is manipulated by various methods of the [KeyedArchive] class,
+ /// such as the [] operator, decode methods, and encode methods. It's also
+ /// used when converting the archive to primitive types or when resolving
+ /// references.
+ Map _map;
+
+ /// Stores the inflated (decoded) object associated with this archive.
+ ///
+ /// This property is used to cache the decoded object after it has been
+ /// inflated from the archive data. It allows for efficient retrieval
+ /// of the decoded object in subsequent accesses, avoiding repeated
+ /// decoding operations.
+ ///
+ /// The type is [Coding?] to accommodate both null values (when no object
+ /// has been inflated yet) and any object that implements the [Coding] interface.
+ Coding? _inflated;
+
+ /// A reference to another [KeyedArchive] object.
+ ///
+ /// This property is used to handle JSON Schema references ($ref).
+ /// When a reference is resolved, this property holds the referenced [KeyedArchive] object.
+ /// It allows the current archive to access values from the referenced object
+ /// when a key is not found in the current archive's map.
+ KeyedArchive? _objectReference;
+
+ /// Typecast the values in this archive.
+ ///
+ /// Prefer to override [Coding.castMap] instead of using this method directly.
+ ///
+ /// This method will recursively type values in this archive to the desired type
+ /// for a given key. Use this method (or [Coding.castMap]) for decoding `List` and `Map`
+ /// types, where the values are not `Coding` objects.
+ ///
+ /// You must `import 'package:codable/cast.dart' as cast;`.
+ ///
+ /// Usage:
+ ///
+ /// final dynamicObject = {
+ /// "key": ["foo", "bar"]
+ /// };
+ /// final archive = KeyedArchive.unarchive(dynamicObject);
+ /// archive.castValues({
+ /// "key": cast.List(cast.String)
+ /// });
+ ///
+ /// // This now becomes a valid assignment
+ /// List key = archive.decode("key");
+ ///
+ /// This method takes a [schema] parameter of type `Map?`, which defines
+ /// the types to cast for each key in the archive. If [schema] is null, the method returns
+ /// without performing any casting. The method uses a flag [_casted] to ensure it only
+ /// performs the casting once. It creates a [cast.Keyed] object with the provided schema
+ /// and uses it to cast the values in both the main [_map] and the [_objectReference] map
+ /// (if it exists). This ensures type safety and consistency across the entire archive structure.
+ void castValues(Map? schema) {
+ if (schema == null) {
+ return;
+ }
+ if (_casted) return;
+ _casted = true;
+ final caster = cast.Keyed(schema);
+ _map = caster.cast(_map);
+
+ if (_objectReference != null) {
+ _objectReference!._map = caster.cast(_objectReference!._map);
+ }
+ }
+
+ /// A flag indicating whether the values in this archive have been cast.
+ ///
+ /// This boolean is used to ensure that the [castValues] method is only
+ /// called once on this archive. It is set to true after the first call
+ /// to [castValues], preventing redundant type casting operations.
+ bool _casted = false;
+
+ /// Sets the value associated with the given [key] in this [KeyedArchive].
+ ///
+ /// This operator allows you to assign values to keys in the archive as if it were a regular map.
+ /// The [key] must be a [String], and [value] can be of any type.
+ ///
+ /// Example:
+ /// archive['name'] = 'John Doe';
+ /// archive['age'] = 30;
+ ///
+ /// Note that this method directly modifies the internal [_map] of the archive.
+ /// It does not perform any type checking or conversion on the [value].
+ @override
+ void operator []=(covariant String key, dynamic value) {
+ _map[key] = value;
+ }
+
+ /// Retrieves the value associated with the given [key] from this [KeyedArchive].
+ ///
+ /// This operator allows you to access values in the archive as if it were a regular map.
+ /// The [key] must be a [String].
+ ///
+ /// If the key is found in the current archive's map, its value is returned.
+ /// If not found and this archive has an [_objectReference], it attempts to retrieve
+ /// the value from the referenced object.
+ ///
+ /// Example:
+ /// var name = archive['name'];
+ /// var age = archive['age'];
+ ///
+ /// Returns the value associated with [key], or null if the key is not found.
+ @override
+ dynamic operator [](covariant Object key) => _getValue(key as String);
+
+ /// Returns an [Iterable] of all the keys in the archive.
+ ///
+ /// This getter provides access to all the keys stored in the internal [_map]
+ /// of the [KeyedArchive]. It allows iteration over all keys without exposing
+ /// the underlying map structure.
+ ///
+ /// Returns: An [Iterable] containing all the keys in the archive.
+ @override
+ Iterable get keys => _map.keys;
+
+ /// Removes all entries from this [KeyedArchive].
+ ///
+ /// After this call, the archive will be empty.
+ /// This method directly calls the [clear] method on the internal [_map].
+ @override
+ void clear() => _map.clear();
+
+ /// Removes the entry for the given [key] from this [KeyedArchive] and returns its value.
+ ///
+ /// This method removes the key-value pair associated with [key] from the internal map
+ /// of this [KeyedArchive]. If [key] was in the archive, its associated value is returned.
+ /// If [key] was not in the archive, null is returned.
+ ///
+ /// The [key] should be a [String], as this is a [KeyedArchive]. However, the method
+ /// accepts [Object?] to comply with the [MapBase] interface it implements.
+ ///
+ /// Returns the value associated with [key] before it was removed, or null if [key]
+ /// was not in the archive.
+ @override
+ dynamic remove(Object? key) => _map.remove(key);
+
+ /// Converts this [KeyedArchive] to a primitive [Map].
+ ///
+ /// This method recursively converts the contents of the archive to primitive types:
+ /// - [KeyedArchive] instances are converted to [Map]
+ /// - [ListArchive] instances are converted to [List]
+ /// - Other values are left as-is
+ ///
+ /// This is useful when you need to serialize the archive to a format like JSON
+ /// that doesn't support custom object types.
+ ///
+ /// Returns a new [Map] containing the primitive representation
+ /// of this archive.
+ Map toPrimitive() {
+ final out = {};
+ _map.forEach((key, val) {
+ if (val is KeyedArchive) {
+ out[key] = val.toPrimitive();
+ } else if (val is ListArchive) {
+ out[key] = val.toPrimitive();
+ } else {
+ out[key] = val;
+ }
+ });
+ return out;
+ }
+
+ /// Retrieves the value associated with the given [key] from this [KeyedArchive].
+ ///
+ /// This method first checks if the key exists in the current archive's internal map.
+ /// If found, it returns the associated value.
+ /// If the key is not found in the current archive, and this archive has an [_objectReference],
+ /// it attempts to retrieve the value from the referenced object recursively.
+ ///
+ /// Parameters:
+ /// [key] - The string key to look up in the archive.
+ ///
+ /// Returns:
+ /// The value associated with the [key] if found, or null if the key is not present
+ /// in either the current archive or any referenced archives.
+ dynamic _getValue(String key) {
+ if (_map.containsKey(key)) {
+ return _map[key];
+ }
+
+ return _objectReference?._getValue(key);
+ }
+
+ /// Recodes the internal map of this [KeyedArchive].
+ ///
+ /// This method performs the following operations:
+ /// 1. Creates a [cast.Map] caster for string keys and any values.
+ /// 2. Iterates through all keys in the internal map.
+ /// 3. For each key-value pair:
+ /// - If the value is a [Map], it's converted to a [KeyedArchive].
+ /// - If the value is a [List], it's converted to a [ListArchive].
+ /// - If the key is "$ref", it sets the [referenceURI] by parsing the value.
+ ///
+ /// This method is called during initialization to ensure proper structure
+ /// and typing of the archive's contents.
+ void _recode() {
+ const caster = cast.Map(cast.string, cast.any);
+ final keys = _map.keys.toList();
+ for (final key in keys) {
+ final val = _map[key];
+ if (val is Map) {
+ _map[key] = KeyedArchive(caster.cast(val));
+ } else if (val is List) {
+ _map[key] = ListArchive.from(val);
+ } else if (key == r"$ref") {
+ referenceURI = Uri.parse(Uri.parse(val.toString()).fragment);
+ }
+ }
+ }
+
+ /// Validates and resolves references within this [KeyedArchive] and its nested objects.
+ ///
+ /// This method is automatically invoked by both [KeyedArchive.unarchive] and [KeyedArchive.archive].
+ @override
+ void resolveOrThrow(ReferenceResolver coder) {
+ if (referenceURI != null) {
+ _objectReference = coder.resolve(referenceURI!);
+ if (_objectReference == null) {
+ throw ArgumentError(
+ "Invalid document. Reference '#${referenceURI!.path}' does not exist in document.",
+ );
+ }
+ }
+
+ _map.forEach((key, val) {
+ if (val is KeyedArchive) {
+ val.resolveOrThrow(coder);
+ } else if (val is ListArchive) {
+ val.resolveOrThrow(coder);
+ }
+ });
+ }
+
+ /// Decodes a [KeyedArchive] into an object of type [T] that extends [Coding].
+ ///
+ /// This method is responsible for inflating (decoding) an object from its archived form.
+ /// If the [raw] archive is null, the method returns null.
+ ///
+ /// If the archive has not been inflated before (i.e., [_inflated] is null),
+ /// it creates a new instance using the [inflate] function, decodes the archive
+ /// into this new instance, and caches it in [_inflated] for future use.
+ ///
+ /// Parameters:
+ /// [raw]: The [KeyedArchive] containing the encoded object data.
+ /// [inflate]: A function that returns a new instance of [T].
+ ///
+ /// Returns:
+ /// The decoded object of type [T], or null if [raw] is null.
+ T? _decodedObject(
+ KeyedArchive? raw,
+ T Function() inflate,
+ ) {
+ if (raw == null) {
+ return null;
+ }
+
+ if (raw._inflated == null) {
+ raw._inflated = inflate();
+ raw._inflated!.decode(raw);
+ }
+
+ return raw._inflated as T?;
+ }
+
+ /// Returns the object associated with [key] in this [KeyedArchive].
+ ///
+ /// If [T] is inferred to be a [Uri] or [DateTime],
+ /// the associated object is assumed to be a [String] and an appropriate value is parsed
+ /// from that string.
+ ///
+ /// If this object is a reference to another object (via [referenceURI]), this object's key-value
+ /// pairs will be searched first. If [key] is not found, the referenced object's key-values pairs are searched.
+ /// If no match is found, null is returned.
+ T? decode(String key) {
+ final v = _getValue(key);
+ if (v == null) {
+ return null;
+ }
+
+ if (T == Uri) {
+ return Uri.parse(v.toString()) as T;
+ } else if (T == DateTime) {
+ return DateTime.parse(v.toString()) as T;
+ }
+
+ return v as T?;
+ }
+
+ /// Decodes and returns an instance of [T] associated with the given [key] in this [KeyedArchive].
+ ///
+ /// [inflate] must create an empty instance of [T]. The value associated with [key]
+ /// must be a [KeyedArchive] (a [Map]). The values of the associated object are read into
+ /// the empty instance of [T].
+ T? decodeObject(String key, T Function() inflate) {
+ final val = _getValue(key);
+ if (val == null) {
+ return null;
+ }
+
+ if (val is! KeyedArchive) {
+ throw ArgumentError(
+ "Cannot decode key '$key' into '$T', because the value is not a Map. Actual value: '$val'.",
+ );
+ }
+
+ return _decodedObject(val, inflate);
+ }
+
+ /// Decodes and returns a list of objects of type [T] associated with the given [key] in this [KeyedArchive].
+ ///
+ /// [inflate] must create an empty instance of [T]. The value associated with [key]
+ /// must be a [ListArchive] (a [List] of [Map]). For each element of the archived list,
+ /// [inflate] is invoked and each object in the archived list is decoded into
+ /// the instance of [T].
+ List? decodeObjects(String key, T? Function() inflate) {
+ final val = _getValue(key);
+ if (val == null) {
+ return null;
+ }
+ if (val is! List) {
+ throw ArgumentError(
+ "Cannot decode key '$key' as 'List<$T>', because value is not a List. Actual value: '$val'.",
+ );
+ }
+
+ return val
+ .map((v) => _decodedObject(v as KeyedArchive?, inflate))
+ .toList()
+ .cast();
+ }
+
+ /// Decodes and returns a map of objects of type [T] associated with the given [key] in this [KeyedArchive].
+ ///
+ /// [inflate] must create an empty instance of [T]. The value associated with [key]
+ /// must be a [KeyedArchive] (a [Map]), where each value is a [T].
+ /// For each key-value pair of the archived map, [inflate] is invoked and
+ /// each value is decoded into the instance of [T].
+ Map? decodeObjectMap(
+ String key,
+ T Function() inflate,
+ ) {
+ final v = _getValue(key);
+ if (v == null) {
+ return null;
+ }
+
+ if (v is! Map) {
+ throw ArgumentError(
+ "Cannot decode key '$key' as 'Map', because value is not a Map. Actual value: '$v'.",
+ );
+ }
+
+ return {
+ for (var k in v.keys) k: _decodedObject(v[k] as KeyedArchive?, inflate)
+ };
+ }
+
+ /// Encodes a [Coding] object into a [Map] representation.
+ ///
+ /// This method creates a [KeyedArchive] from the given [object] and returns its
+ /// internal map representation. If the [object] has a [referenceURI], it is
+ /// encoded as a '$ref' key in the resulting map.
+ ///
+ /// If [object] is null, this method returns null.
+ ///
+ /// Note: There is a known limitation where overridden values from a reference
+ /// object are not currently being emitted. This is due to the complexity of
+ /// handling cyclic references between objects.
+ ///
+ /// Parameters:
+ /// [object]: The [Coding] object to be encoded.
+ ///
+ /// Returns:
+ /// A [Map] representation of the [object], or null if [object] is null.
+ Map? _encodedObject(Coding? object) {
+ if (object == null) {
+ return null;
+ }
+
+ final json = KeyedArchive._empty()
+ .._map = {}
+ ..referenceURI = object.referenceURI;
+ if (json.referenceURI != null) {
+ json._map[r"$ref"] = Uri(fragment: json.referenceURI!.path).toString();
+ } else {
+ object.encode(json);
+ }
+ return json;
+ }
+
+ /// Encodes [value] into this object for [key].
+ ///
+ /// This method adds a key-value pair to the internal map of the [KeyedArchive].
+ /// The [key] is always a [String], while [value] can be of any type.
+ ///
+ /// If [value] is null, no value is encoded and the [key] will not be present
+ /// in the resulting archive.
+ void encode(String key, dynamic value) {
+ if (value == null) {
+ return;
+ }
+
+ if (value is DateTime) {
+ _map[key] = value.toIso8601String();
+ } else if (value is Uri) {
+ _map[key] = value.toString();
+ } else {
+ _map[key] = value;
+ }
+ }
+
+ /// Encodes a [Coding] object into this object for [key].
+ ///
+ /// This method takes a [Coding] object [value] and encodes it into the archive
+ /// under the specified [key]. If [value] is null, no action is taken and the method returns early.
+ ///
+ /// The encoding process involves:
+ /// 1. Checking if the [value] is null.
+ /// 2. If not null, it uses the private [_encodedObject] method to convert the [Coding] object
+ /// into a format suitable for storage in the archive.
+ /// 3. The encoded object is then stored in the archive's internal map ([_map]) using the provided [key].
+ ///
+ /// This method is useful for adding complex objects that implement the [Coding] interface
+ /// to the archive, allowing for structured data storage and later retrieval.
+ ///
+ /// Parameters:
+ /// [key]: A [String] that serves as the identifier for the encoded object in the archive.
+ /// [value]: A [Coding] object to be encoded and stored. Can be null.
+ ///
+ /// Example:
+ /// ```dart
+ /// final person = Person(name: "John", age: 30);
+ /// archive.encodeObject("person", person);
+ /// ```
+ void encodeObject(String key, Coding? value) {
+ if (value == null) {
+ return;
+ }
+
+ _map[key] = _encodedObject(value);
+ }
+
+ /// Encodes a list of [Coding] objects into this archive for the given [key].
+ ///
+ /// This invokes [Coding.encode] on each object in [value] and adds the list of objects
+ /// to this archive for the key [key].
+ void encodeObjects(String key, List? value) {
+ if (value == null) {
+ return;
+ }
+
+ _map[key] = ListArchive.from(value.map((v) => _encodedObject(v)).toList());
+ }
+
+ /// Encodes a map of [Coding] objects into this archive for the given [key].
+ ///
+ /// This invokes [Coding.encode] on each value in [value] and adds the map of objects
+ /// to this archive for the key [key].
+ void encodeObjectMap(String key, Map? value) {
+ if (value == null) return;
+ final object = KeyedArchive({});
+ value.forEach((k, v) {
+ object[k] = _encodedObject(v);
+ });
+
+ _map[key] = object;
+ }
+}
diff --git a/packages/typeforge/lib/src/list_archive.dart b/packages/typeforge/lib/src/list_archive.dart
new file mode 100644
index 0000000..778bd00
--- /dev/null
+++ b/packages/typeforge/lib/src/list_archive.dart
@@ -0,0 +1,233 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'dart:collection';
+import 'package:protevus_typeforge/codable.dart';
+
+/// A list of values in a [KeyedArchive].
+///
+/// This object is a [List] that has additional behavior for encoding and decoding [Coding] objects.
+/// It provides functionality to store and manipulate a list of dynamic values, with special handling
+/// for nested maps and lists. The class implements [Referenceable], allowing it to resolve references
+/// within its contents when used in conjunction with a [ReferenceResolver].
+///
+/// The [ListArchive] can be created empty or initialized from an existing list. When initialized
+/// from a list, it automatically converts nested maps and lists to [KeyedArchive] and [ListArchive]
+/// instances respectively, providing a consistent interface for complex nested structures.
+///
+/// This class is particularly useful when working with serializable data structures that may
+/// contain nested objects or arrays, as it preserves the structure while allowing for easy
+/// manipulation and serialization.
+class ListArchive extends Object
+ with ListBase
+ implements Referenceable {
+ /// The internal list that stores the dynamic values of this [ListArchive].
+ ///
+ /// This list can contain various types of elements, including primitive types,
+ /// [KeyedArchive] instances (for nested maps), and other [ListArchive] instances
+ /// (for nested lists). It is used to maintain the structure and order of the
+ /// archived data while providing the necessary functionality for the [ListArchive].
+ final List _inner;
+
+ /// Creates an empty [ListArchive].
+ ///
+ /// This constructor initializes a new [ListArchive] instance with an empty internal list.
+ /// The resulting [ListArchive] is ready to accept new elements through its various
+ /// list manipulation methods inherited from [ListBase].
+ ListArchive() : _inner = [];
+
+ /// Creates a [ListArchive] from an existing [List] of dynamic values.
+ ///
+ /// This constructor takes a [List] of dynamic values as input and initializes
+ /// a new [ListArchive] instance. It processes each element of the input list,
+ /// converting any nested [Map] to [KeyedArchive] and nested [List] to [ListArchive].
+ /// This conversion is done using the [_toAtchiveType] function.
+ ///
+ /// The resulting [ListArchive] maintains the structure of the original list
+ /// but with enhanced functionality for handling nested data structures.
+ ///
+ /// Parameters:
+ /// [raw]: The input [List] of dynamic values to be converted into a [ListArchive].
+ ///
+ /// Returns:
+ /// A new [ListArchive] instance containing the processed elements from the input list.
+ ListArchive.from(List raw)
+ : _inner = raw.map(_toAtchiveType).toList();
+
+ /// Returns the element at the specified [index] in the list.
+ ///
+ /// This operator overrides the default list indexing behavior to access
+ /// elements in the internal [_inner] list.
+ ///
+ /// Parameters:
+ /// [index]: An integer index of the element to retrieve.
+ ///
+ /// Returns:
+ /// The element at the specified [index] in the list.
+ ///
+ /// Throws:
+ /// [RangeError] if the [index] is out of bounds.
+ @override
+ dynamic operator [](int index) => _inner[index];
+
+ /// Returns the length of the internal list.
+ ///
+ /// This getter overrides the [length] property from [ListBase] to provide
+ /// the correct length of the internal [_inner] list.
+ ///
+ /// Returns:
+ /// An integer representing the number of elements in the [ListArchive].
+ @override
+ int get length => _inner.length;
+
+ /// Sets the length of the internal list.
+ ///
+ /// This setter overrides the [length] property from [ListBase] to allow
+ /// modification of the internal [_inner] list's length. Setting the length
+ /// can be used to truncate the list or extend it with null values.
+ ///
+ /// Parameters:
+ /// [length]: The new length to set for the list.
+ ///
+ /// Throws:
+ /// [RangeError] if [length] is negative.
+ /// [UnsupportedError] if the list is fixed-length.
+ @override
+ set length(int length) {
+ _inner.length = length;
+ }
+
+ /// Sets the value at the specified [index] in the list.
+ ///
+ /// This operator overrides the default list indexing assignment behavior to
+ /// modify elements in the internal [_inner] list.
+ ///
+ /// Parameters:
+ /// [index]: An integer index of the element to set.
+ /// [val]: The new value to be assigned at the specified [index].
+ ///
+ /// Throws:
+ /// [RangeError] if the [index] is out of bounds.
+ /// [UnsupportedError] if the list is fixed-length.
+ @override
+ void operator []=(int index, dynamic val) {
+ _inner[index] = val;
+ }
+
+ /// Adds a single element to the end of this list.
+ ///
+ /// This method overrides the [add] method from [ListBase] to add an element
+ /// to the internal [_inner] list.
+ ///
+ /// Parameters:
+ /// [element]: The element to be added to the list. Can be of any type.
+ ///
+ /// The list grows by one element.
+ @override
+ void add(dynamic element) {
+ _inner.add(element);
+ }
+
+ /// Adds all elements of the given [iterable] to the end of this list.
+ ///
+ /// This method overrides the [addAll] method from [ListBase] to add multiple
+ /// elements to the internal [_inner] list.
+ ///
+ /// Parameters:
+ /// [iterable]: An [Iterable] of elements to be added to the list. The elements
+ /// can be of any type.
+ ///
+ /// The list grows by the length of the [iterable].
+ @override
+ void addAll(Iterable iterable) {
+ _inner.addAll(iterable);
+ }
+
+ /// Converts the [ListArchive] to a list of primitive values.
+ ///
+ /// This method traverses the [ListArchive] and converts its contents to a list
+ /// of primitive values. It recursively processes nested [KeyedArchive] and
+ /// [ListArchive] instances, ensuring that the entire structure is converted
+ /// to basic Dart types.
+ ///
+ /// Returns:
+ /// A [List] containing the primitive representation of the [ListArchive].
+ /// - [KeyedArchive] instances are converted to [Map]s.
+ /// - [ListArchive] instances are converted to [List]s.
+ /// - Other values are left as-is.
+ ///
+ /// This method is useful for serialization purposes or when you need to
+ /// convert the [ListArchive] to a format that can be easily serialized
+ /// or transmitted.
+ List toPrimitive() {
+ final out = [];
+ for (final val in _inner) {
+ if (val is KeyedArchive) {
+ out.add(val.toPrimitive());
+ } else if (val is ListArchive) {
+ out.add(val.toPrimitive());
+ } else {
+ out.add(val);
+ }
+ }
+ return out;
+ }
+
+ /// Resolves references within this [ListArchive] using the provided [ReferenceResolver].
+ ///
+ /// This method iterates through all elements in the internal list ([_inner]) and
+ /// resolves references for nested [KeyedArchive] and [ListArchive] instances.
+ /// It's part of the [Referenceable] interface implementation, allowing for
+ /// deep resolution of references in complex nested structures.
+ ///
+ /// Parameters:
+ /// [coder]: A [ReferenceResolver] used to resolve references within the archive.
+ ///
+ /// Throws:
+ /// May throw exceptions if reference resolution fails, as implied by the method name.
+ ///
+ /// This method is typically called during the decoding process to ensure all
+ /// references within the archive structure are properly resolved.
+ @override
+ void resolveOrThrow(ReferenceResolver coder) {
+ for (final i in _inner) {
+ if (i is KeyedArchive) {
+ i.resolveOrThrow(coder);
+ } else if (i is ListArchive) {
+ i.resolveOrThrow(coder);
+ }
+ }
+ }
+}
+
+/// Converts a dynamic value to an archive type if necessary.
+///
+/// This function takes a dynamic value and converts it to an appropriate archive type:
+/// - If the input is a [Map], it's converted to a [KeyedArchive].
+/// - If the input is a [List], it's converted to a [ListArchive].
+/// - For all other types, the input is returned as-is.
+///
+/// This function is used internally by [ListArchive] to ensure that nested structures
+/// (maps and lists) are properly converted to their respective archive types when
+/// creating a new [ListArchive] instance.
+///
+/// Parameters:
+/// [e]: The dynamic value to be converted.
+///
+/// Returns:
+/// The input value converted to an appropriate archive type, or the original value
+/// if no conversion is necessary.
+dynamic _toAtchiveType(dynamic e) {
+ if (e is Map) {
+ return KeyedArchive(e);
+ } else if (e is List) {
+ return ListArchive.from(e);
+ }
+ return e;
+}
diff --git a/packages/typeforge/lib/src/primitive_cast.dart b/packages/typeforge/lib/src/primitive_cast.dart
new file mode 100644
index 0000000..62c2df1
--- /dev/null
+++ b/packages/typeforge/lib/src/primitive_cast.dart
@@ -0,0 +1,108 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'dart:core' as core;
+import 'dart:core' hide Map, String, int;
+import 'package:protevus_typeforge/cast.dart';
+
+/// A cast operation for converting dynamic values to [core.int].
+///
+/// This class extends [Cast] and implements the [safeCast] method
+/// to perform type checking and conversion to [core.int].
+///
+/// The [safeCast] method checks if the input [from] is already a [core.int].
+/// If it is, it returns the value unchanged. If not, it throws a [FailedCast]
+/// exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final intCast = IntCast();
+/// final result = intCast.cast(42); // Returns 42
+/// intCast.cast("not an int"); // Throws FailedCast
+/// ```
+class IntCast extends Cast {
+ const IntCast();
+ @override
+ core.int safeCast(dynamic from, core.String context, dynamic key) =>
+ from is core.int
+ ? from
+ : throw FailedCast(context, key, "$from is not an int");
+}
+
+/// A cast operation for converting dynamic values to [core.double].
+///
+/// This class extends [Cast] and implements the [safeCast] method
+/// to perform type checking and conversion to [core.double].
+///
+/// The [safeCast] method checks if the input [from] is already a [core.double].
+/// If it is, it returns the value unchanged. If not, it throws a [FailedCast]
+/// exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final doubleCast = DoubleCast();
+/// final result = doubleCast.cast(3.14); // Returns 3.14
+/// doubleCast.cast("not a double"); // Throws FailedCast
+/// ```
+class DoubleCast extends Cast {
+ const DoubleCast();
+ @override
+ core.double safeCast(dynamic from, core.String context, dynamic key) =>
+ from is core.double
+ ? from
+ : throw FailedCast(context, key, "$from is not an double");
+}
+
+/// A cast operation for converting dynamic values to [core.String].
+///
+/// This class extends [Cast] and implements the [safeCast] method
+/// to perform type checking and conversion to [core.String].
+///
+/// The [safeCast] method checks if the input [from] is already a [core.String].
+/// If it is, it returns the value unchanged. If not, it throws a [FailedCast]
+/// exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final stringCast = StringCast();
+/// final result = stringCast.cast("Hello"); // Returns "Hello"
+/// stringCast.cast(42); // Throws FailedCast
+/// ```
+class StringCast extends Cast {
+ const StringCast();
+ @override
+ core.String safeCast(dynamic from, core.String context, dynamic key) =>
+ from is core.String
+ ? from
+ : throw FailedCast(context, key, "$from is not a String");
+}
+
+/// A cast operation for converting dynamic values to [core.bool].
+///
+/// This class extends [Cast] and implements the [safeCast] method
+/// to perform type checking and conversion to [core.bool].
+///
+/// The [safeCast] method checks if the input [from] is already a [core.bool].
+/// If it is, it returns the value unchanged. If not, it throws a [FailedCast]
+/// exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final boolCast = BoolCast();
+/// final result = boolCast.cast(true); // Returns true
+/// boolCast.cast("not a bool"); // Throws FailedCast
+/// ```
+class BoolCast extends Cast {
+ const BoolCast();
+ @override
+ core.bool safeCast(dynamic from, core.String context, dynamic key) =>
+ from is core.bool
+ ? from
+ : throw FailedCast(context, key, "$from is not a bool");
+}
diff --git a/packages/typeforge/lib/src/reference_resolver.dart b/packages/typeforge/lib/src/reference_resolver.dart
new file mode 100644
index 0000000..ba4651b
--- /dev/null
+++ b/packages/typeforge/lib/src/reference_resolver.dart
@@ -0,0 +1,79 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'package:protevus_typeforge/codable.dart';
+
+/// A class for resolving references within a document structure.
+///
+/// This class provides functionality to resolve references within a document
+/// represented by a [KeyedArchive]. It allows for navigation through the
+/// document structure using URI-style references.
+///
+/// The [ReferenceResolver] is particularly useful in scenarios where you need
+/// to traverse complex, nested document structures and resolve references
+/// to specific parts of the document.
+///
+/// Usage:
+/// ```dart
+/// final document = KeyedArchive(...); // Your document structure
+/// final resolver = ReferenceResolver(document);
+/// final resolved = resolver.resolve(Uri.parse('#/definitions/child'));
+/// ```
+///
+/// The [resolve] method is the primary way to use this class. It takes a [Uri]
+/// reference and returns the corresponding [KeyedArchive] from the document,
+/// or null if the reference cannot be resolved.
+class ReferenceResolver {
+ /// Creates a new [ReferenceResolver] instance.
+ ///
+ /// The [ReferenceResolver] is used to resolve references within a document
+ /// structure represented by a [KeyedArchive].
+ ///
+ /// Parameters:
+ /// [document] - The document to resolve references within. This
+ /// [KeyedArchive] represents the entire document structure that will be
+ /// used to resolve references.
+ ReferenceResolver(this.document);
+
+ /// The document to resolve references within.
+ ///
+ /// This [KeyedArchive] represents the entire document structure
+ /// that will be used to resolve references.
+ final KeyedArchive document;
+
+ /// Resolves a reference URI to a [KeyedArchive] within the document.
+ ///
+ /// This method takes a [Uri] [ref] and traverses the document structure
+ /// to find the corresponding [KeyedArchive]. It uses the path segments
+ /// of the URI to navigate through the nested structure of the document.
+ ///
+ /// Parameters:
+ /// [ref] - A [Uri] representing the reference to resolve.
+ ///
+ /// Returns:
+ /// A [KeyedArchive] corresponding to the resolved reference, or null
+ /// if the reference cannot be resolved within the document structure.
+ ///
+ /// Example:
+ /// If [ref] is '#/definitions/child', this method will attempt to
+ /// navigate to document['definitions']['child'] and return the
+ /// corresponding [KeyedArchive].
+ KeyedArchive? resolve(Uri ref) {
+ final folded = ref.pathSegments.fold(document,
+ (KeyedArchive? objectPtr, pathSegment) {
+ if (objectPtr != null) {
+ return objectPtr[pathSegment] as KeyedArchive?;
+ } else {
+ return null;
+ }
+ });
+
+ return folded;
+ }
+}
diff --git a/packages/typeforge/lib/src/referenceable.dart b/packages/typeforge/lib/src/referenceable.dart
new file mode 100644
index 0000000..57746ff
--- /dev/null
+++ b/packages/typeforge/lib/src/referenceable.dart
@@ -0,0 +1,46 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'package:protevus_typeforge/codable.dart';
+
+/// This abstract class serves as a contract for objects that need to be
+/// resolved based on references. It defines a common interface for resolution
+/// operations, allowing for consistent handling of referenceable objects
+/// throughout the system.
+///
+/// Implementations of this class should ensure that:
+/// - They provide a meaningful implementation of [resolveOrThrow].
+/// - They handle potential errors during resolution and throw appropriate exceptions.
+/// - They interact correctly with the provided [ReferenceResolver].
+///
+/// Example usage:
+/// ```dart
+/// class ConcreteReferenceable implements Referenceable {
+/// @override
+/// void resolveOrThrow(ReferenceResolver resolver) {
+/// // Implementation of reference resolution
+/// }
+/// }
+abstract class Referenceable {
+ /// Resolves the references within this object using the provided [resolver].
+ ///
+ /// This method is responsible for resolving any references or dependencies
+ /// that this object might have. It should use the [resolver] to look up and
+ /// resolve these references.
+ ///
+ /// If the resolution process encounters any errors or fails to resolve
+ /// necessary references, this method should throw an appropriate exception.
+ ///
+ /// Parameters:
+ /// [resolver]: The [ReferenceResolver] instance to use for resolving references.
+ ///
+ /// Throws:
+ /// An exception if the resolution process fails or encounters errors.
+ void resolveOrThrow(ReferenceResolver resolver);
+}
diff --git a/packages/typeforge/lib/src/special_cast.dart b/packages/typeforge/lib/src/special_cast.dart
new file mode 100644
index 0000000..2833060
--- /dev/null
+++ b/packages/typeforge/lib/src/special_cast.dart
@@ -0,0 +1,109 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'dart:async' as async;
+import 'dart:core' as core;
+import 'dart:core' hide Map, String, int;
+import 'package:protevus_typeforge/cast.dart';
+
+/// A cast operation that attempts to cast a dynamic value to either type S or type T.
+///
+/// This class extends [Cast] and provides a mechanism to attempt casting
+/// to two different types in sequence. It first tries to cast to type S using the
+/// [_left] cast, and if that fails, it attempts to cast to type T using the [_right] cast.
+///
+/// The [safeCast] method first attempts to use the [_left] cast. If it succeeds, the result
+/// is returned. If it fails (by throwing a [FailedCast] exception), the method then
+/// attempts to use the [_right] cast and returns its result.
+///
+/// This class is useful when you have a value that could be one of two different types
+/// and you want to handle both cases.
+///
+/// Usage:
+/// ```dart
+/// final oneOfCast = OneOf(IntCast(), StringCast());
+/// final resultInt = oneOfCast.cast(42); // Returns 42 as int
+/// final resultString = oneOfCast.cast("hello"); // Returns "hello" as String
+/// oneOfCast.cast(true); // Throws FailedCast
+/// ```
+class OneOf extends Cast {
+ final Cast _left;
+ final Cast _right;
+ const OneOf(Cast left, Cast right)
+ : _left = left,
+ _right = right;
+ @override
+ dynamic safeCast(dynamic from, core.String context, dynamic key) {
+ try {
+ return _left.safeCast(from, context, key);
+ } on FailedCast {
+ return _right.safeCast(from, context, key);
+ }
+ }
+}
+
+/// A cast operation that applies a transformation function after casting to an intermediate type.
+///
+/// This class extends [Cast] and combines two operations:
+/// 1. Casting the input to type S using the [_first] cast operation.
+/// 2. Applying a transformation function [_transform] to convert the result from S to T.
+///
+/// [S] is the intermediate type after the first cast.
+/// [T] is the final type after applying the transformation.
+///
+/// The [safeCast] method first uses [_first] to cast the input to type S,
+/// then applies [_transform] to convert the result to type T.
+///
+/// This class is useful when you need to perform a cast followed by a type conversion
+/// or when you want to apply some transformation logic after casting.
+///
+/// Usage:
+/// ```dart
+/// final stringLengthCast = Apply((s) => s.length, StringCast());
+/// final result = stringLengthCast.cast("hello"); // Returns 5
+/// ```
+class Apply extends Cast {
+ final Cast _first;
+ final T Function(S) _transform;
+ const Apply(T Function(S) transform, Cast first)
+ : _transform = transform,
+ _first = first;
+ @override
+ T safeCast(dynamic from, core.String context, dynamic key) =>
+ _transform(_first.safeCast(from, context, key));
+}
+
+/// A cast operation for converting dynamic values to [async.Future].
+///
+/// This class extends [Cast>] and implements the [safeCast] method
+/// to perform type checking and conversion to [async.Future].
+///
+/// The class uses a [Cast] instance [_value] for casting the value inside the Future to type E.
+///
+/// The [safeCast] method checks if the input [from] is already an [async.Future].
+/// If it is, it returns a new Future that applies the [_value] cast to the result of the original Future.
+/// If not, it throws a [FailedCast] exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final futureCast = Future(IntCast());
+/// final result = futureCast.cast(Future.value(42)); // Returns Future
+/// futureCast.cast("not a future"); // Throws FailedCast
+/// ```
+class Future extends Cast> {
+ final Cast _value;
+ const Future(Cast value) : _value = value;
+ @override
+ async.Future safeCast(dynamic from, core.String context, dynamic key) {
+ if (from is async.Future) {
+ return from.then(_value.cast);
+ }
+ return throw FailedCast(context, key, "not a Future");
+ }
+}
diff --git a/packages/typeforge/lib/src/utility_cast.dart b/packages/typeforge/lib/src/utility_cast.dart
new file mode 100644
index 0000000..4016dde
--- /dev/null
+++ b/packages/typeforge/lib/src/utility_cast.dart
@@ -0,0 +1,77 @@
+/*
+ * This file is part of the Protevus Platform.
+ *
+ * (C) Protevus
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import 'dart:core' as core;
+import 'dart:core' hide Map, String, int;
+import 'package:protevus_typeforge/cast.dart';
+
+/// A cast operation that accepts and returns any dynamic value without modification.
+///
+/// This class extends [Cast] and provides a no-op cast operation.
+/// It's useful when you want to allow any type to pass through without
+/// performing any type checking or transformation.
+///
+/// The [safeCast] method simply returns the input value as-is, regardless of its type.
+///
+/// Example usage:
+/// ```dart
+/// final anyCast = AnyCast();
+/// final result = anyCast.cast(someValue); // Returns someValue unchanged
+/// ```
+class AnyCast extends Cast {
+ const AnyCast();
+ @override
+ dynamic safeCast(dynamic from, core.String context, dynamic key) => from;
+}
+
+/// A cast operation for converting dynamic values to [core.Map] with specific key-value casts.
+///
+/// This class extends [Cast>] and implements the [safeCast] method
+/// to perform type checking and conversion to [core.Map] based on a predefined
+/// map of key-specific casts.
+///
+/// The class uses a [core.Map>] to define custom casts for specific keys.
+/// Keys not present in this map will be cast as-is.
+///
+/// The [keys] getter provides access to the keys of the internal cast map.
+///
+/// The [safeCast] method checks if the input [from] is a [core.Map]. If it is,
+/// it creates a new map, applying the specific casts for keys present in [_map]
+/// and preserving other key-value pairs as-is. If not, it throws a [FailedCast]
+/// exception with appropriate context information.
+///
+/// Usage:
+/// ```dart
+/// final keyedCast = Keyed({
+/// 'age': IntCast(),
+/// 'name': StringCast(),
+/// });
+/// final result = keyedCast.cast({'age': 30, 'name': 'John', 'city': 'New York'});
+/// // Returns Map with 'age' as int, 'name' as String, and 'city' preserved as-is
+/// ```
+class Keyed extends Cast> {
+ Iterable get keys => _map.keys;
+ final core.Map> _map;
+ const Keyed(core.Map> map) : _map = map;
+ @override
+ core.Map safeCast(dynamic from, core.String context, dynamic key) {
+ final core.Map result = {};
+ if (from is core.Map) {
+ for (final K key in from.keys as core.Iterable) {
+ if (_map.containsKey(key)) {
+ result[key] = _map[key]!.safeCast(from[key], "map entry", key);
+ } else {
+ result[key] = from[key] as V;
+ }
+ }
+ return result;
+ }
+ throw FailedCast(context, key, "not a map");
+ }
+}
diff --git a/packages/typeforge/pubspec.yaml b/packages/typeforge/pubspec.yaml
new file mode 100644
index 0000000..c21dd3c
--- /dev/null
+++ b/packages/typeforge/pubspec.yaml
@@ -0,0 +1,17 @@
+name: protevus_typeforge
+description: The Typeforge (casting) package for the Protevus Platform
+version: 0.0.1
+homepage: https://protevus.com
+documentation: https://docs.protevus.com
+repository: https://git.protevus.com/protevus/platform
+
+environment:
+ sdk: '>=3.4.0 <4.0.0'
+
+# Add regular dependencies here.
+dependencies:
+ meta: ^1.3.0
+
+dev_dependencies:
+ lints: ^3.0.0
+ test: ^1.24.0
diff --git a/packages/typeforge/test/decode_test.dart b/packages/typeforge/test/decode_test.dart
new file mode 100644
index 0000000..93ca841
--- /dev/null
+++ b/packages/typeforge/test/decode_test.dart
@@ -0,0 +1,464 @@
+import 'dart:convert';
+import 'package:protevus_typeforge/cast.dart' as cast;
+import 'package:protevus_typeforge/codable.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group("Primitive decode", () {
+ test("Can decode primitive type", () {
+ final archive = getJSONArchive({"key": 2});
+ final int? val = archive.decode("key");
+ expect(val, 2);
+ });
+
+ test("Can decode List type", () {
+ final archive = getJSONArchive({
+ "key": [1, "2"]
+ });
+ final List? l = archive.decode("key");
+ expect(l, [1, "2"]);
+ });
+
+ test("Can decode Map", () {
+ final archive = getJSONArchive({
+ "key": {"key": "val"}
+ });
+ final KeyedArchive? d = archive.decode("key");
+ expect(d, {"key": "val"});
+ });
+
+ test("Can decode URI", () {
+ final archive = getJSONArchive({"key": "https://host.com"});
+ final Uri? d = archive.decode("key");
+ expect(d!.host, "host.com");
+ });
+
+ test("Can decode DateTime", () {
+ final date = DateTime.now();
+ final archive = getJSONArchive({"key": date.toIso8601String()});
+ final DateTime? d = archive.decode("key");
+ expect(d!.isAtSameMomentAs(date), true);
+ });
+
+ test("If value is null, return null from decode", () {
+ final archive = getJSONArchive({"key": null});
+ final int? val = archive.decode("key");
+ expect(val, isNull);
+ });
+
+ test("If archive does not contain key, return null from decode", () {
+ final archive = getJSONArchive({});
+ final int? val = archive.decode("key");
+ expect(val, isNull);
+ });
+ });
+
+ group("Primitive map decode", () {
+ test("Can decode Map from Map", () {
+ final archive = getJSONArchive({
+ "key": {"key": "val"}
+ });
+ archive.castValues({"key": const cast.Map(cast.string, cast.string)});
+ final Map? d = archive.decode("key");
+ expect(d, {"key": "val"});
+ });
+
+ test("Can decode Map>", () {
+ final archive = getJSONArchive({
+ "key": {
+ "key": ["val"]
+ }
+ });
+ archive.castValues(
+ {"key": const cast.Map(cast.string, cast.List(cast.string))},
+ );
+ final Map>? d = archive.decode("key");
+ expect(d, {
+ "key": ["val"]
+ });
+ });
+
+ test("Can decode Map> where elements are null", () {
+ final archive = getJSONArchive({
+ "key": {
+ "key": [null, null]
+ }
+ });
+ archive.castValues(
+ {"key": const cast.Map(cast.string, cast.List(cast.string))},
+ );
+ final Map>? d = archive.decode("key");
+ expect(d, {
+ "key": [null, null]
+ });
+ });
+
+ test("Can decode Map>>", () {
+ final archive = getJSONArchive({
+ "key": {
+ "key": {
+ "key": ["val", null]
+ }
+ }
+ });
+ archive.castValues({
+ "key": const cast.Map(
+ cast.string,
+ cast.Map(cast.string, cast.List(cast.string)),
+ )
+ });
+ final Map>>? d = archive.decode("key");
+ expect(d, {
+ "key": {
+ "key": ["val", null]
+ }
+ });
+ });
+ });
+
+ group("Primitive list decode", () {
+ test("Can decode List from List", () {
+ final archive = getJSONArchive({
+ "key": ["val", null]
+ });
+ archive.castValues({"key": const cast.List(cast.string)});
+ final List? d = archive.decode("key");
+ expect(d, ["val", null]);
+ });
+
+ test("Can decode List