poly_registry 2.0.0 copy "poly_registry: ^2.0.0" to clipboard
poly_registry: ^2.0.0 copied to clipboard

Type-safe polymorphic registry and map converter for Dart. No code generation.

poly_registry #

A type-safe polymorphic registry for Dart. Look up objects by key or Dart type and convert polymorphic maps using registered converters — no code generation required.

pub.dev License: MIT

Features #

  • 🔑 Look up entries by key or by Dart type
  • 🔄 Built-in polymorphic Map conversion via MapConverterRegistry
  • 🛡️ Configurable duplicate key handling — keep first, keep last, or throw
  • 🧩 Extensible — build your own registry on top of PolyRegistry or ConverterRegistry

Motivation #

I've been writing Flutter apps for a while, and the same pattern kept showing up across every project: a Map<String, Function> buried somewhere in the codebase, doing the job of a registry.

It usually starts small — a map of JSON deserializers, or a map of route handlers. You add a key, wire up a function, move on. But as the project grows, that map grows with it. Then you need a second one for a different purpose. Then a third. Before long you have several of these scattered across the codebase, each with slightly different error handling, each reinventing the same lookup logic, and none of them type-safe enough to catch mistakes at compile time.

I kept extracting the same abstraction by hand in project after project until it became obvious it should just be a package. poly_registry is that abstraction — a typed, reusable registry that handles lookup, duplicate key strategies, and polymorphic conversion in one place, so I never have to write another raw Map<String, Function> again.

Installation #

Add poly_registry to your pubspec.yaml:

dependencies:
  poly_registry: ^1.0.0

Then run:

dart pub get

Quick start #

The most common use case is polymorphic Map deserialization — converting a Map object to one of several concrete types based on a discriminator field:

// 1. Define your models
abstract class JobPayload {
  Map<String, dynamic> toMap();
}

const typeKey = 'type';

class VideoPayload extends JobPayload {
  VideoPayload({required this.filePath});

  factory VideoPayload.fromMap(Map<dynamic, dynamic> from) => VideoPayload(filePath: from['filePath'] as String);

  final String filePath;

  static const type = 'video';

  @override
  Map<String, dynamic> toMap() => {typeKey: type, 'filePath': filePath};
}

class DataSyncPayload extends JobPayload {
  DataSyncPayload({required this.endpoint});

  factory DataSyncPayload.fromMap(Map<dynamic, dynamic> from) => DataSyncPayload(endpoint: from['endpoint'] as String);

  final String endpoint;

  static const type = 'dataSync';

  @override
  Map<String, dynamic> toMap() => {typeKey: type, 'endpoint': endpoint};
}

// 2. Create the registry
final registry = MapConverterRegistry<JobPayload>.prepare(
  typeKey: typeKey,
  entries: [
    InlineMapConverter(VideoPayload.type, VideoPayload.fromMap),
    InlineMapConverter(DataSyncPayload.type, DataSyncPayload.fromMap),
  ],
);

// 3. Use it.
final videoPayload = registry.convert({
  typeKey: 'video',
  'filePath': '/tmp/video.mp4',
});

print(videoPayload); // Instance of 'VideoPayload'

final dataSyncPayload = registry.convert({
  typeKey: 'dataSync',
  'endpoint': '/sync',
});

print(dataSyncPayload); // Instance of 'DataSyncPayload'

final List<JobPayload> payloadList = [
  VideoPayload(filePath: '/tmp/video.mp4'),
  DataSyncPayload(endpoint: '/sync'),
];
final List<Map<String, dynamic>> mapList = payloadList.map((e) => e.toMap()).toList();

final restoredList = registry.convertAll(mapList).toList();
print(restoredList.length); // 2


Usage #

Basic registry #

Use PolyRegistry to store and retrieve any objects that have a unique key:

class RouteHandler {
  const RouteHandler(this.handle);

  final void Function() handle;
}

final registry = PolyRegistry<String, RouteHandler>(
  entries: {
    'home': RouteHandler(() => print('Home')),
    'profile': RouteHandler(() => print('Profile')),
  },
);

registry.getByKey('home').handle(); // Home
print(registry.contains('profile')); // true

Batch conversion #

final items = [
  {'type': 'video', 'filePath': '/tmp/a.mp4'},
  {'type': 'dataSync', 'endpoint': 'https://api.example.com'},
  {'type': 'unknown'}, // no converter registered — will be skipped
];

final registry = MapConverterRegistry<JobPayload>.prepare(
  typeKey: 'type',
  entries: [
    InlineMapConverter(VideoPayload.type, VideoPayload.fromMap),
    InlineMapConverter(DataSyncPayload.type, DataSyncPayload.fromMap),
  ],
);

// Skip failures and handle errors
final payloads = registry.convertAll(items, (item, error, stack) {
  print('Failed: $item — $error');
});

// Or throw on first failure
final payloads = registry.convertAllOrThrow(items);

Duplicate key strategies #

MapConverterRegistry<JobPayload>.prepare(
  typeKey: 'type',
  entries: [
    InlineMapConverter(VideoPayload.type, VideoPayload.fromMap),
    InlineMapConverter(VideoPayload.type, VideoPayload.fromMap),
  ],
  duplicateStrategy: DuplicateKeyStrategy.keepFirst, // default
  // duplicateStrategy: DuplicateKeyStrategy.keepLast,
  // duplicateStrategy: DuplicateKeyStrategy.throwError,
  onDuplicate: (entry) => print('Duplicate key: ${entry.registryKey}'),
);

Runtime registration #

registry.register('settings', RouteHandler(() => print('Settings')));
registry.unregister('settings');

Custom registry for any format #

Extend ConverterRegistry to support any source format beyond Map:

abstract class ProtobufConverter<TO> extends PolyConverter<Uint8List, TO> {}

class ProtobufRegistry<TO> extends ConverterRegistry<int, Uint8List, TO, ProtobufConverter<TO>> {
  ProtobufRegistry({required super.entries});

  @override
  int extractRegistryKey(Uint8List from) => from[0]; // message type from first byte
}

License #

MIT

0
likes
160
points
184
downloads

Documentation

API reference

Publisher

verified publisherastonio.com

Weekly Downloads

Type-safe polymorphic registry and map converter for Dart. No code generation.

Repository (GitHub)
View/report issues

License

MIT (license)

More

Packages that depend on poly_registry